美文网首页
Python正式课第十天

Python正式课第十天

作者: code与有荣焉 | 来源:发表于2019-11-17 15:59 被阅读0次

    面向对象编程(补充)

    注:
    设计函数三要素:干什么的,需要接受的信息(参数),返回值是什么

    • 私有属性---封装

    在实际开发中,对象 的 某些属性或方法 可能只希望 在对象的内部被使用,而 不希望在外部被访问到

    定义方式:在 定义属性或方法时,在 属性名或者方法名前 增加 两个下划线__实际开发中私有属性也不是一层不变的。

    通过自定义get,set方法提供私有属性的访问

    因为在外部不能调用__age属性,所以通过类的内部创建方法调用__age,在外部再通过调用这个方法来调用这个属性

    注意:set需要传参。

    用处:用set方式改变私有属性可以用来对属性的合理性做校验,比如传入的参数必须符合某一条件才能改变

    class Person:
        def __init__(self, name, age):
            self.name = name
            self.__age = age
    
        #定义对私有属性的get方法,获取私有属性
        def getAge(self):
            return self.__age
    
        #定义对私有属性的重新赋值的set方法,重置私有属性
        def setAge(self,age):
            self.__age = age
    
    person1 = Person("tom",19)
    person1.setAge(20)
    print(person1.name,person1.getAge())  #tom 20
    
    调用property方法提供私有属性的访问(不常用)
    class Student:
        def __init__(self, name, age):
            self.name = name
            self.__age = age
    
        #定义对私有属性的get方法,获取私有属性
        def getAge(self):
            return self.__age
    
        #定义对私有属性的重新赋值的set方法,重置私有属性
        def setAge(self,age):
            self.__age = age
    
        p = property(getAge,setAge) #注意里面getAge,setAge不能带()
    
    s1 = Student("jack",22)
    s1.p = 23 #如果使用=,则会判断为赋值,调用setAge方法。
    print(s1.name,s1.p)  #jack 23   ,直接使用s1.p会自动判断会取值,调用getAge
    print(s1.name,s1.getAge()) #jack 23,这个时候set,get方法可以单独使用。
    
    使用property标注提供私有属性的访问。(常用)

    注意:有了property后,直接使用t1.age,而不是t1.age()方法了。

    class Teacher:
        def __init__(self, name, age,speak):
            self.name = name
            self.__age = age
            self.__speak = speak
    
    
        @property      #注意1.@proterty下面默认跟的是get方法,如果设置成set会报错。
        def age(self):
            return self.__age
    
        @age.setter    #注意2.这里是使用的上面函数名.setter,不是property.setter.
        def age(self,age):
            if age > 150 and age <=0:  #还可以在setter方法里增加判断条件
                print("年龄输入有误")
            else:
                self.__age = age
    
        @property
        def for_speak(self):  #注意2.这个同名函数名可以自定义名称,一般都是默认使用属性名。
            return self.__speak
    
        @for_speak.setter
        def for_speak(self, speak):
            self.__speak = speak
    
    t1 = Teacher("herry",45,"Chinese")
    t1.age = 38    #注意4.有了property后,直接使用t1.age,而不是t1.age()方法了。
    t1.for_speak = "English"  
    
    print(t1.name,t1.age,t1.for_speak)  #herry 38 English
    
    • 将实例用作属性---对象组合(has a 关系)

    使用代码模拟实物时,你可能会发现自己给类添加的细节越来越多:属性和方法清单以及文件都越来越长。在这种情况下,可能需要将类的一部分属性和方法作为一个独立的类提取出来。你可以将大型类拆分成多个协同工作的小类。

    实例1:摆放家具

    需求

    1. 房子(House) 有 户型、总面积 和 家具名称列表
      • 新房子没有任何的家具
    2. 家具(HouseItem) 有 名字 和 占地面积,其中
      • 席梦思(bed) 占地 4 平米
      • 衣柜(chest) 占地 2 平米
      • 餐桌(table) 占地 1.5 平米
    3. 将以上三件 家具 添加 到 房子 中
    4. 打印房子时,要求输出:户型、总面积、剩余面积、家具名称列表

    剩余面积

    1. 在创建房子对象时,定义一个 剩余面积的属性,初始值和总面积相等
    2. 当调用 add_item 方法,向房间 添加家具 时,让 剩余面积 -= 家具面积

    思考:应该先开发哪一个类?

    答案 —— 家具类

    1. 家具简单
    2. 房子要使用到家具,被使用的类,通常应该先开发

    创建家具

    class HouseItem:
    
        def __init__(self, name, area):
            """
    
            :param name: 家具名称
            :param area: 占地面积
            """
            self.name = name
            self.area = area
    
        def __str__(self):
            return "[%s] 占地面积 %.2f" % (self.name, self.area)
    
    # 1\. 创建家具
    bed = HouseItem("席梦思", 4)
    chest = HouseItem("衣柜", 2)
    table = HouseItem("餐桌", 1.5)
    
    print(bed)
    print(chest)
    print(table)
    

    小结

    1. 创建了一个 家具类,使用到 initstr 两个内置方法
    2. 使用 家具类 创建了 三个家具对象,并且 输出家具信息

    创建房间

    class House:
        def __init__(self,type,area):
            self.type = type
            self.area = area
            self.remain_area = area
            #self.house_items保存的是HouseItem类型的对象
            self.house_items = []
    
        def __str__(self):
            return f"户型:{self.type}  总面积:{self.area}[剩余:{ self.remain_area}]\n家具:{self.house_items}"
    
    
        def add_item(self,house_item):
            self.house_items.append(house_item)
            self.remain_area -= house_item.area
    
    bed = HoureItem('席梦思',4)
    chest = HoureItem('衣柜',2)
    table = HoureItem('餐桌',1.5)
    
    house = House('三室一厅',123)
    print(house)
    print('-'*70)
    house.add_item(bed)
    house.add_item(chest)
    house.add_item(table)
    print(house)
    

    小结

    1. 主程序只负责创建 房子 对象和 家具 对象
    2. 让 房子 对象调用 add_item 方法 将家具添加到房子中
    3. 面积计算、剩余面积、家具列表 等处理都被 封装 到 房子类的内部
    实例2:英雄pk怪物

    Sprite.py (精灵类,一般游戏开发时把怪物、英雄什么的统一封装到这里)

    class Sprite:
        def __init__(self,name,blood=100,strength=100):
            self.name = name
            self.blood = blood
            self.strength = strength
    
        def calc_health(self):
            return self.blood
    
        def get_damage_point(self):
            return 0
    
        def take_damage(self, sprite):
    
            # damage_point = random.randint(sprite.strength - 10, sprite.strength + 10)
            damage_point = sprite.get_damage_point()
    
            self.blood -= damage_point
            print(f"{self.name} 你被{sprite.name}攻击,受到了{str(damage_point)}点伤害!还剩{str(self.blood)}滴血")
    
            if self.blood > 0:
                return False
            else:
                print(f"{self.name}你被杀死了!胜败乃兵家常事 请重新来过。")
                return True
    

    Hero.py

    import random
    
    from Sprite import Sprite
    
    
    class Hero(Sprite):
        def __init__(self,name,blood=100,strength=100):
            super().__init__(name, blood, strength)
    
        def get_damage_point(self):
            damage_point = random.randint(self.strength - 7, self.strength + 12)
            return damage_point
    

    Monster.py

    import random
    
    from Sprite import Sprite
    
    
    class Monster(Sprite):
        def __init__(self,name,blood=100,strength=100):
            super().__init__(name,blood,strength)
    
        def get_damage_point(self):
            damage_point = random.randint(self.strength - 9, self.strength + 13)
            return damage_point
    
    

    Game.py

    import time
    
    from Monster import Monster
    from Hero import Hero
    
    hero = Hero('关羽',150,20)
    mon = Monster('吕布',200,15)
    
    while True:
        #关羽攻击吕布
        isMonDie = mon.take_damage(hero)
        if isMonDie:
            break
        # 吕布攻击关羽
        isHeroDie = hero.take_damage(mon)
        if isHeroDie:
            break
    
        time.sleep(0.3)
    
    • 类属性 类方法 静态方法

    类属性

    如果在类外修改类属性,必须通过类对象去引用然后进行修改。如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性。

    # 类属性  
    class people:  
        name="Tom"    #公有的类属性   #  类属性:实例对象和类对象可以同时调用
        __age=18      #私有的类属性  
        def  __init__(self,age):  
            self.age=age   # 实例属性
    
    p=people()  
    print(p.name)   #实例对象  
    print(people.name)  #类对象  
    
    # print(p.__age)    #错误 不能在类外通过实例对象访问私有的类属性  
    print(people.__age) #错误 不能在类外同过类对象访问私有的类属性
    

    类方法
    类对象所拥有的方法,需要用修饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字),能够通过实例对象和类对象去访问。

    类方法还有一个用途就是可以对类属性进行修改

    class people:  
        country="china"  
    
        @classmethod  
        def getCountry(cls):  
            return cls.country  
    
    p=people()  
    print(p.getCountry())   #实例对象调用类方法  
    print(people.getCountry())  #类对象调用类方法
    

    静态方法
    需要通过修饰器@staticmethod来进行修饰,静态方法不需要定义参数

    class people3:  
        country="china"  
    
        @staticmethod  
        def getCountry():  
            return people3.country  
    
    p=people3()  
    print(p.getCountry())   #实例对象调用类方法  
    print(people3.getCountry())  #类对象调用类方法
    
    • 继承(A is B 关系)

    继承的概念:子类 自动拥有(继承) 父类 的所有 方法属性
    继承的语法:

    class 类名(父类名):
        pass
    
    1. 子类 继承自 父类,可以直接 享受 父类中已经封装好的方法,不需要再次开发
    2. 子类 中应该根据 职责,封装 子类特有的 属性和方法
    3. 当 父类 的方法实现不能满足子类需求时,可以对方法进行 重写(override)

    示例:
    我们看到Rectangle和Circle有同样的属性color和方法showcolor 我们可以定义一个父类Shape,将Rectangle和Circle通用的部分提取到Shape类中,然后在子类的init方法中,通过 调用super().__init__(color).把 color 传给父类的 __init()

    import math
    
    
    class Shape:
        def __init__(self, color):
            self.color = color
    
        def area(self):  # 这里可以看作是父类和子类的约定(例如重写某一个方法),
                         #也可能是父类与客户端的约定(某个方法一定会实现)
            return None
    
        def show_color(self):
            print(self.color)
    
    
    class Circle(Shape):
        def __init__(self, color, r):
            super().__init__(color)
            # Shape.__init__(self,color) #这样也行,但是不好(考虑父类Shape的名字改变了,怎么办)
            self.r = r
    
        def area(self):
            return math.pi * self.r * self.r
    
    
    
    
    class Rectangle(Shape):
        def __init__(self, color, a, b):
            super().__init__(color)
            # Shape.__init__(self, color)   #这样也行,但是不好(考虑父类Shape的名字改变了,怎么办)
            self.a, self.b = a, b
    
        def area(self):
            return self.a * self.b
    
    
    
    circle = Circle('red', 3.0)
    print(circle.area())
    circle.show_color()
    rectangle = Rectangle('blue', 2.0, 3.0)
    print(rectangle.area())
    rectangle.show_color()
    

    子类Circle和Rectangle本身并没有定义show_color方法, 从父类Shape继承了show_color方法。子类Circle和Rectangle改写(Override)了父类的area方法,分别提供了自己不同的实现。
    一定不要忘记在子类的init方法中调用super()._init_()

    • __new__方法

    python中定义的类在创建实例对象的时候,会自动执行init()方法,但是在执行init()方法之前,会执行new()方法。

    new()的作用主要有两个:
    1.在内存中为对象分配空间 2.返回对象的引用。(即对象的内存地址)
    python解释器在获得引用的时候会将其传递给init()方法中的self。
    示例:

    class A:
        def __new__(cls,*args,**kwargs):
            print('__new__')
            # return super().__new__(cls)#这里一定要返回,否则__init__()方法不会被执行
        def __init__(self):#这里的self就是new方法中的return返回值 # 可能是对象的内存地址
            print('__init__')
    
    
    a = A()
    
    • object---所有python类型的父类

    在Python中,所有类型有个隐式的父类(object),上面的代码相当于

    class A(object):
        # def __new__(cls,*args,**kwargs):
        #     print('__new__')
            # return super().__new__(cls)#这里一定要返回,否则__init__()方法不会被执行
        def __init__(self):#这里的self就是new方法中的return返回值
            print('__init__')
    
    a = A()
    
    • 单例模式

    单例模式,相当于创建一个共享区,里面对象是唯一的

    如果我们创建两个实例:

    a1 = A()
    a2 = A()
    

    那么id(a1)和id(a2)的值不一样,也就是说python在内容当中创建了两个实例对象,用了两份内存。同样的东西创建了两份

    如果想不管创建多少个实例对象,我们都让它的id是一样的。

    也就是说,先创建一个实例对象,之后不管创建多少个,返回的永远都是第一个实例对象的内存地址。可以这样实现:

    # 重写new方法很固定,返回值必须是这个
    # 这样就避免了创建多份。
    # 创建第一个实例的时候,_instance是None,那么会分配空间创建实例。
    # 此时的类属性已经被修改,_instance不再为None
    # 那么当之后实例属性被创建的时候,由于_instance不为None。
    # 则返回第一个实例对象的引用,即内存地址。
    # 这样就应用了单例模式。
    class A():
        _instance = None
        def __new__(cls,*args,**kwargs):
            if A._instance == None:
                A._instance = super().__new__(cls)  #  super().__new__(cls)就是第一个对象的内存地址
            return A._instance
    
    
    a1 = A()
    print(id(a1))
    a2 = A()
    print(id(a2))
    

    相关文章

      网友评论

          本文标题:Python正式课第十天

          本文链接:https://www.haomeiwen.com/subject/yxepictx.html