• Python面向对象详解


    1. 类和对象

    1.1 面向对象概述

    1)面向对象的优点

    • 面向对象编程,将一类相似功能函数的集合在一起,可以让代码更清晰,更合理化
    • 站在上帝视角看问题,类其实就是某种事物的公共的模板,对象就是从这个具体模板实例化出来的

    2)类和对象

    • 类:具有相同属性和功能的一类事物
    • 对象:就是类的具体表现形式

    3)面向对象&实例化

    • 面向对象设计:将一类具体事物的数据和动作整合到一起,即面向对象设计
    • 面向对象编程:用定义 类+实例/对象 的方式去实现面向对象的设计
    • 实例化:由类生产对象的过程叫实例化,类实例化的结果就是一个对象,或者叫做一个实例(实例=对象)

    4)用函数模拟面向对象

    思路:函数嵌套函数,外部函数的结尾调用内部函数。而内部函数会返回一个字典,这个字典包含了一切内容。

    # 分析:调用dog函数时,会直接执行init函数,init函数的返回值是dog1字典的地址 
    def dog(name, gender, type):        # 这里dog函数返回的就是dog1的字典
        def bark(dog):                  # 这里必须将返回的字典作为参数传给这个函数,以拿到字典中的内容
            print("A dog [%s], is barking......" % dog['name'])
    
        def eat(dog):
            print("A dog [%s] , is eating......" % dog['type'])
    
        def init(name, gender, type):   # 利用了函数的作用域和字典的数据类型,将对象函数和功能函数之间建立起联系    
            dog1 = {                    # 这个字典中包含了一切的内容
                'name': name,
                'gender': gender,
                'type': type,
                'bark': bark, 
                'eat': eat,
            }
            return dog1                 # 返回字典的地址
    
                                        # 这一步才开始执行第一个函数----> init(),这个函数进行初始化
        return init(name, gender, type) # 将这个dog1字典作为整个大的dog函数的返回值
    
    d1 = dog("xiaoming", "male", "black_dog")  # 将初始化之后的dog保存为一个d1对象
    # 其实就是将一个个函数或变量的地址保存为字典类型中的键值对,以建立起相应的联系
    d2 = dog("alex", "female", "fool_dog")
    
    # 这里的d1和d2对象其实就是一个保存了函数和变量对应地址的键值对
    d1['bark'](d1)  # 获取d1字典中的bark键对应的bark函数的地址,然后将d1作为bark函数的参数,执行bark函数
    d2['eat'](d2)

    1.2 类的结构与使用

    1)类的本质

    • 类:数据属性 + 函数属性
    • 类的本质可以近似于一个字典,这个字典中包含了所有的数据属性和函数属性

    2)类的结构在大的方向上分为两部分

    • 静态变量
    • 动态方法
    class Human:             # 类名使用驼峰式命名风格,首字母大写,私有类可以用一个下划线开头
        """
        庆丰帝
        """
    mind = '那叫一个牛X' # 第一部分:静态属性 属性 静态变量 静态字段 def work(self): # 第二部分:方法 函数 动态属性 print('十里山路不换肩')

    3)类的定义和调用

    class Chinese(object):
        color = "yellow"      # 这里定义一个数据属性
    
        def make_money():     # 定义函数属性
            print("Can make money...")
    
        def impolite(self):
            print("Do something is impolite")
    
    print(Chinese.color)      # 这种加上 . 的调用形式,本质上就是在属性字典中调用
    Chinese.make_money()
    
    print(dir(Chinese))       # 查看所有的变量(列表的形式)
    
    print(Chinese.__dict__)   # 查看属性字典(包含数据属性和函数属性)
    print(Chinese.__dict__["color"])
    Chinese.__dict__["make_money"]()       # 这样就相当于直接去调用make_money函数
    Chinese.__dict__["impolite"](Chinese)  # 这样调用impolite函数,因为这个函数中接受一个self参数,所以要将Chinese传入
    
    print(Chinese.__name__)   # 显示类名
    print(Chinese.__doc__)    # 类中的文档
    print(Chinese.__module__) # 显示类所在的模块

    1.3 对象和实例化

    1)对象

    • 对象是从类中出来的,只要是类名加上(),这就是实例化的过程,就会实例化一个对象。

    2)实例化的过程

    • 在内存中开辟一个对象空间
    • 自动执行类中的__init__方法,并将这个对象空间(内存地址)传给__init__() 方法的第一个位置参数self
    • 在__init__方法中通过 self 给对象空间添加属性

    3)关于self

    • 类中的方法一般都是通过对象执行的(除去类方法,静态方法外),并且对象执行这些方法都会主动将对象空间传给方法中的第一个参数self
    • self就是自身,self指的就是类实例化的那个对象。谁将类实例化了,self指的就是谁(self就是实例本身)

    1.4 数据属性和函数属性的调用

    类就犹如一个函数,里面的方法拥有自己的局部作用域,当调用的属性在init中没有找到时,就会到init方法的上一层继续找

    类和函数不同的地方是,如果在类中没有找到,就会报错,而函数则继续往外在全局作用域中继续找

    • 实例的字典中并不包含方法,只包含属性
    • 实例只有数据属性(在init的作用域中),它的函数属性是进入上一个作用域,在类中寻找的
    class Chinese(object):
        color = "yellow"                           # 这里定义一个数据属性
    
        def __init__(self, name, age, gender):     # 类在调用的时候,会自动先触发__init__函数的运行
            # 这里的self和p1指向的是同一个内存地址同一个空间,以下就是通过self给这个对象空间封装3个属性
            self.mingzi = name                     # 在类中用一个变量来接受传给init方法的值
            self.nianji = age
            self.xingbie = gender
    
        def make_money():                          # 定义函数属性
            print("Can make lots of money...")
    
        def impolite(self):
            print("Do something is impolite")
    
    p1 = Chinese('hgzero', 21, 'male')            # 这里将自身p1传递给self,然后后面的参数依次传递给init方法
    # 创建了一个Chinese的实例p1 , 这个过程叫做类的实例化
    
    
    print(p1.__dict__)        # 其实init方法中的变量和值将会以字典的形式作为整个类的返回值,这里用__dict__显示它的字典
    
    print(Chinese.__dict__)   # 类的字典中包含所有方法、属性
    
    Chinese.make_money()      # 可以不实例对象而直接调用类中不包含self的方法
    Chinese.impolite(p1)      # 当调用含有self的方法时,必须要将实例后的对象作为参数传递进去
    
    print(p1.__dict__['xingbie'])
    print(p1.mingzi)          # 其实这种调用方式就是从dict去找
    
    print(p1.color)
    # 调用时先从init的字典中去找,若没找到再到类的字典中去找,这是若再没找到就会报错
    
    p1.make_money()           # 当实例去调用方法时,会自动将自身(p1)作为第一个参数传递给被调用的方法(就是传递给self)
    # 这里因为类中的make_money这个方法没有self,而python在处理的时候却会传递给它一个p1,所以会报错

    1.5 类属性的增删改查

    • 实例的字典中保存数据属性,不保存函数属性
    • 实例之间公用函数属性
    class Chinese(object):
        color = "yellow"  # 这是一个类属性   
    
        def __init__(self, name, age, gender):
            self.mingzi = name  
            self.nianji = age
            self.xingbie = gender
    
        def make_money(self):     
            print("Can make lots of money...")
    
        def impolite(self):
            print("Do something is impolite")
    
        def eat_food(self, food):
            print("The %s is eating %s" % (self.mingzi, food))
    
    p1 = Chinese("hgzero", 21, "male") # 因为实例的字典中只保存数据属性,而不保存函数属性,所以,实例之间公用函数属性
    p1.eat_food("Apple")
    
    p2 = Chinese("hg", 23, "male")
    p2.eat_food("Banana")
    
    # 查看类属性
    print(Chinese.color)
    
    # 增加
    Chinese.language = "HanYu"
    print(Chinese.language)
    
    # 修改类属性
    Chinese.color = "black"
    print(Chinese.color)
    
    # 删除类属性
    del Chinese.color
    
    # 给类增加一个函数属性
    def eating(self, food):
        print("Now, %s is eating %s" %(self.mingzi, food))
    # 在类中添加一个eat的方法,其地址指向eating函数的地址
    Chinese.eat = eating 
    
    p1.eat("baozi")

    1.6 实例属性的增删改查

    class Chinese(object):
        color = "yellow"  # 这是一个类属性   
    
        def __init__(self, name, gender):
            self.mingzi = name  
            self.xingbie = gender
    
        def make_money(self):     
            print("Can make lots of money...")
    
        def impolite(self):
            print("Do something is impolite")
    
        def eat_food(self, food):
            print("The %s is eating %s" % (self.mingzi, food))
    
    
    p1 = Chinese("hgzero", "male") # 创建一个实例
    print(p1.__dict__)             # 查看实例的字典
    
    # 查看
    print(p1.mingzi)
    
    # 增加
    p1.age = 21                    # 这里的age是被加入到了实例的字典中
    print(p1.__dict__)
    print(p1.age)
    
    #-----------------------------不建议去直接修改属性字典结构-------------------------------
    # 这些属性的添加和修改删除无非就是通过操作底部的字典实现的,但是不建议直接去修改属性字典结构
    p1.__dict__['sex'] = 'male'
    print(p1.__dict__)
    print(p1.sex)
    # ----------------------------------------------------------------------------------
    
    # 修改
    p1.age = 23
    print(p1.__dict__)
    print(p1.age)
    
    # 删除
    del p1.age
    print(p1.__dict__)
    • 为实例添加一个函数属性(不常用)
    # 为实例属性添加一个函数属性
    def test(self):
        print("A test")
    
    p1.test = test
    # p1.test() # 若这样写会报错,因为class只有在实例调用类的方法时才会自动将自身作为第一个参数传递进去
    # 而这里创建的test方法是属于实例自己的,所以class不会将自身传递进去
    p1.test(p1) 

    1.7 对于属性的查找

    • 查找规则
      • 对象查找属性的顺序:对象空间栈 ——> 类空间栈 ——> 父类空间栈
      • 类名查找属性的顺序:本类空间栈 ——> 父类空间栈 ——> ...
    • 特别注意:
      • 当不用用点 . 的方法调用变量时,会直接跳出类,到全局作用域查找这个变量
      • 使用点 . 的形式调用变量时,才会在类中一层一层的寻找
    country = "America"
    
    class Chinese:
        country = "China"
        def __init__(self, name):
            self.name = name
            print("--->", country)  # ---这里需要注意,这里的country会直接调用类外面的country
                                    #     因为它没有用 . 的形式调用(没有使用self的形式),所以既不是类属性,也不是实例属性
        def play_ball(self, ball):
            print('%s now is play %s' %(self.name, ball))
    
    p1 = Chinese('hgzerowzh')
    print(p1.country)      # 这里在实例中没有找到country,所以到类中找到了country
    
    p1.country = "Japan"   # 这里只是在实例中增加了country的属性
    print(Chinese.country) # 这里访问的是类中的country
    print(p1.country)
    
    # 注意:当不是用 . 的方法调用变量时,会跳出类,到全局作用域查找这个变量
    # 使用 . 的形式调用变量时,才会在类中一层层的寻找

    2. 类与类之间的关系

    类与类之间存在以下关系:

    • 依赖关系
    • 关联关系
    • 组合关系
    • 聚合关系
    • 实现关系
    • 继承关系

    2.1 依赖关系

    • 依赖关系就是将一个类的对象或者类名传到另一个类的方法使用。
    class Elphant:
        def __init__(self, name):
            self.name = name
        def open(self,obj1):
            '''开门'''
            print('大象要开门了,开')
            obj1.open_door()
        def close(self):
            '''关门'''
            print('大象要关门了,关')
    
    class Refrigerator:
        def open_door(self):
            print("冰箱门被打开了")
        def close_door(self):
            print("冰箱门被关上了")
    
    # 先实例化一个大象类和一个冰箱类
    elphant1 = Elphant('大象')
    haier = Refrigerator()
    # 将冰箱对象作为参数传给大象对象的open方法
    elphant1.open(haier)

    2.2 关联关系

    • 两种事物必须是互相关联的,但是在某些特殊情况下是可以更改和更换的。
    • 当在逻辑上出现了,我需要你,你还得属于我,这种逻辑就是关联关系。

    老师和学校的相互依赖:

    class School:
        def __init__(self,name,address):
            self.name = name
            self.address = address
            self.teacher_list = []
        def append_teacher(self,teacher):
            self.teacher_list.append(teacher)
    class Teacher: def __init__(self,name,school): self.name = name self.school = school
    s1
    = School('日本','早稻田') s2 = School('美国','哈佛') s3 = School('英国','牛津')
    t1
    = Teacher('张三',s1) t2 = Teacher('李四',s2) t3 = Teacher('日天',s3)
    s1.append_teacher(t1) s1.append_teacher(t2) s1.append_teacher(t3)
    # print(s1.teacher_list) # for teacher in s1.teacher_list: # print(teacher.name)

    2.3 聚合关系

    • 属于关联关系的一种特例,侧重点是XXX和XXX聚合成XXX,各自有各自的声明周期。
    • 组合关系和聚合关系,代码上差别不大。

    2.4 组合关系

    • 组合关系是关联关系中的一种特例,组合关系比聚合还要紧密。
    • 将一个类的对象封装到另一个类的对象的属性中就叫组合。
    class Gamerole:
        def __init__(self,name,ad,hp):
            self.name = name
            self.ad = ad
            self.hp = hp
        def attack(self,p1):
            p1.hp -= self.ad
            print('%s攻击%s,%s掉了%s血,还剩%s血'%(self.name,p1.name,p1.name,self.ad,p1.hp))
        def equip_weapon(self,wea):
            self.wea = wea  # 组合:给一个对象封装一个属性改属性是另一个类的对象
    class Weapon:
        def __init__(self,name,ad):
            self.name = name
            self.ad = ad
        def weapon_attack(self,p1,p2):
            p2.hp = p2.hp - self.ad - p1.ad
            print('%s 利用 %s 攻击了%s,%s还剩%s血'
                  %(p1.name,self.name,p2.name,p2.name,p2.hp))
    # 实例化三个人物对象:
    barry = Gamerole('太白',10,200)
    panky = Gamerole('金莲',20,50)
    pillow = Weapon('绣花枕头',2)
    # 给人物装备武器对象。
    barry.equip_weapon(pillow)
    # 开始攻击
    barry.wea.weapon_attack(barry,panky)

    3. 类的继承

    3.1 面向对象的继承

    1)继承

    继承(inheritance)是面向对象软件技术中的一个概念。

    继承可以使得子类具有父类中的各种属性和方法,而不需要再次编写相同的代码。

    在子类继承父类的同时,可以重新定义某些属性,并重写某些方法。即覆盖父类中的原有属性和方法,使其获得与父类不同的功能。

    为子类追加新的属性和方法也是常见的做法。

    一般静态的面向对象编程语言,继承属于静态的,即子类的行为在编译期就已经决定,无法在执行期间扩充。

    2)继承的优点

    • 增加了类的耦合性(耦合性不宜多,宜精)
    • 减少了重复代码
    • 使得代码更加规范化,合理化

    3.2 继承的分类

    1)继承的分类

    • 单继承
    • 多继承

    2)py2和py3的异同

    python2版本中有两种类:

    • 经典类:经典类在基类的根什么都不写
    • 新式类:新式类的特点是基类的根是object类

    python3中使用的都是新式类,如果基类什么都不继承,则默认继承object

    3.3 继承的执行顺序

    1)单继承浅显的原则

    • 实例化对象必须执行__init__方法,类中如果没有,则从父类找,父类没有,从object类中找
    • 实例化对象执行方法时,先在自己的类中找,自己的类中没有才能执行父类中的方法
    class Aniaml(object):
        type_name = '动物类'
        def __init__(self,name,sex,age):
                self.name = name
                self.age = age
                self.sex = sex
        def eat(self):
            print(self)
            print('吃东西')
    class Person(Aniaml):
        def eat(self):
            print('%s 吃饭'%self.name)
    class Cat(Aniaml):
        pass
    class Dog(Aniaml):
        pass
    p1 = Person('barry','',18)
    # 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。
    p1.eat()
    # 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。

    2)深层理解继承的顺序

    浅显理解:

    • 深度优先(在python2中类后面加上object就是新式类,不加就是经典类)
    • 广度优先

    继承顺序的原理:(MRO列表)

    • MRO(Method Resolution Order)方法解析顺序
    • 对于定义的每一个类,python都会计算出一个方法解析序列(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表
    • 为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类位置

    所谓的继承的顺序就是合并所有父类的MRO列表并遵循以下三条法则:

    • 子类先于父类被检查
    • 多个父类会根据它们在列表中的顺序被检查
    • 如果对下一个类存在两个合法的选择,则选择第一个父类(而不再继续选择第二个父类了)

    3)MRO的计算法则

    MRO是一个有序列表,在类被创建时就计算出来。

    merge操作是C3算法的核心:C3算法,多继承查找规则

    mro(B) = mro( B(A1, A2, A3 …) )
    = [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )

    merge的操作法则:

    如计算merge( [E,O], [C,E,F,O], [C] )
    有三个列表 :  ①       ②        ③
    1 merge不为空,取出第一个列表列表①的表头E,进行判断 各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
    2 取出列表②的表头C,进行判断 C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除 merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
    3 进行下一次新的merge操作 ......

    merge计算示例:

    mro(A) = mro( A(B,C) )
    
    原式= [A] + merge( mro(B),mro(C),[B,C] )
      mro(B) = mro( B(D,E) )
             = [B] + merge( mro(D), mro(E), [D,E] )  # 多继承
             = [B] + merge( [D,O] , [E,O] , [D,E] )  # 单继承mro(D(O))=[D,O]
             = [B,D] + merge( [O] , [E,O]  ,  [E] )  # 拿出并删除D,然后对第二个列表的开头E进行判断
             = [B,D,E] + merge([O] ,  [O])           # 拿出并在所有列表开头删除E
             = [B,D,E,O]
      mro(C) = mro( C(E,F) )
             = [C] + merge( mro(E), mro(F), [E,F] )
             = [C] + merge( [E,O] , [F,O] , [E,F] )
             = [C,E] + merge( [O] , [F,O]  ,  [F] )  # 跳过O,拿出并删除
             = [C,E,F] + merge([O] ,  [O])
             = [C,E,F,O]
    
    原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
        = [A,B] + merge( [D,E,O], [C,E,F,O],   [C])
        = [A,B,D] + merge( [E,O], [C,E,F,O],   [C])  # 跳过E,但是不删除E
        = [A,B,D,C] + merge([E,O],  [E,F,O])
        = [A,B,D,C,E] + merge([O],    [F,O])         # 跳过O
        = [A,B,D,C,E,F] + merge([O],    [O])
        = [A,B,D,C,E,F,O]
    ---------------------

    计算图示:

    3.4 在子类中执行父类的方法

    1)方法一:如果想要在子类中执行父类的方法,需要在子类的方法中写上:

    • 父类.func(对象, 其他参数)
    class Aniaml(object):
        type_name = '动物类'
        def __init__(self,name,sex,age):
                self.name = name
                self.age = age
                self.sex = sex
        def eat(self):
            print('吃东西')
    class Person(Aniaml):
        def __init__(self,name,sex,age,mind):
            Aniaml.__init__(self,name,sex,age)  # 方法一
            self.mind = mind
        def eat(self):
            super().eat()
            print('%s 吃饭'%self.name)
    class Cat(Aniaml):
        pass
    class Dog(Aniaml):
        pass
    
    p1 = Person('春哥','laddboy',18,'有思想')
    print(p1.__dict__)

    2)方法二:利用super

    • super().func(参数)
    class Aniaml(object):
        type_name = '动物类'
        def __init__(self,name,sex,age):
                self.name = name
                self.age = age
                self.sex = sex
        def eat(self):
            print('吃东西')
    class Person(Aniaml):
        def __init__(self,name,sex,age,mind):
            # super(Person,self).__init__(name,sex,age)   # 方法二
          # super(__class__,self).__init__(name,sex,age) # 方法二,__class__变量就是当前类
            super().__init__(name,sex,age)  # 方法二
            self.mind = mind
        def eat(self):
            super().eat()
            print('%s 吃饭'%self.name)
    class Cat(Aniaml):
        pass
    class Dog(Aniaml):
        pass
    p1 = Person('春哥','laddboy',18,'有思想') print(p1.__dict__)

    3)特别注意

    super中的self指的是子类中self,也就是子类的对象,这个self被传入了父类中,并作为父类的self:如果父类的方法调用实例属性,那么就是在调用子类中的self字典中的属性,所以这个子类self字典必须要包含所有父类init方法中的属性;如果父类中的方法想通过self调用实例方法,因为这里的self是子类的对象,所以只能调用子类中的方法。

    子类在继承的时候,子类中的方法最好不要和父类中的方法重名,因为万一父类中的方法调用了父类本身的方法,如果子类中的方法和父类想调用的这个方法重名,那么父类通过self调用的方法就会变成了子类中和父类重名的方法(因为继承的时候传给父类的self是子类的对象)。

    3.5 super的本质

    super方法的本质:

    • super方法其实并不是继承,它只是一个查找MRO列表的函数(直接调用类而已);
    • 将当前类传入super函数,super函数就会找到MRO列表中的当前函数的下一个类,然后将这个类作为返回值返回;

    注意:

    • 如果super不加参数,则它指的就是本类,如super.func(),这里的super其实就是本类;
    • 然后因为有了继承的关系,在子类中没有找到func方法就到父类的作用域中找,造成了super代指父类的假象;
    class A():
        def go(self):
            print("go A")
    class B(A):
        def go(self):
            super(B,self).go()
            print("go B")
    class C(A):
        def go(self):
            super(C,self).go()
            print("go C")
    class D(B,C):
        def go(self):
            super(D,self).go()
            print("go D")
    d = D()
    d.go()
    # 这里的运行结果顺序是 A-C-B-D
    
    # 原因:
    #   super函数的类似实现:
    #      super接收两个参数,
    #      第一个是类名,第二个是一个实例对象(MRO列表就是通过这个实例对象计算出来的)
    #      这里实例要是最初的那个类的实例,以得到完整的MRO列表
    def super(cls, inst): 
        mro = inst.__class__.mro()   # 这里通过传入的那个实例来找到它的类,然后再拿到类的mro列表
        return mro[mro.index(cls)+1] # 这里返回的是super参数中的那个类在MRO列表中的下一个类

    3.6 接口继承

    1)接口继承

    接口继承就是定义一个接口(父类),它不实现它自己的方法,但所继承它的子类必须实现父类中的方法(实现了归一化设计)

    但是python中没有对接口继承进行限定,需要导入一个abc模块作为辅助

    2)接口继承的实质

    接口继承的实质就是做一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象,这在程序设计上,叫做 归一化

    import abc
    class All_file(metaclass=abc.ABCMeta):  # 定义了一个接口类(不实现自己的方法,但继承它的子类必须实现它的全部接口方法)
        @abc.abstractmethod  # 定义一个接口方法
        def read(self):
            pass
    
        @abc.abstractmethod
        def write(self):
            pass
    
    class Disk(All_file):
        def read(self):
            print('Disk read')
    
        def write(self):
            print('Disk write')
    
    
    class Cdrom(All_file):
        def read(self):
            print("Cdrom read")
    
        def write(self):
            pritn("Cdrom write")
    
    class Mem(All_file):
        def read(self):
            print("Mem read")
    
        def write(self):
            print("Mem write")
    
    m1 = Mem()
    m1.read()
    m1.write()

    4. 封装&继承&多态

    4.1 封装

    1)封装概述

    封装,就是将内容封装到某个地方,以后再去调用被封装在某处的内容。

    综上所述,面向对象的封装,其实就是使用构造方法将内容封装到对象中,然后通过对象直接或者self间接的获取被封装的内容。

    2)三个层面的封装

    • 第一个层面的封装:类就是麻袋,这本身就是一种封装
    • 第二个层面的封装:类中定义私有的、只有在类的内部使用,外部无法访问的  __变量
    • 第三个层面的封装:明确区分内外,内部的实现逻辑,外部无法知晓,并且为封装到内部的逻辑提供一个访问接口给外部使用(这才是真正的封装)
    class People:
        _start_1 = "this is one"   # 单下划线开始的成员变量叫做【保护变量】,意思是只有类对象和子类对象能访问这些变量,
        # 但python对保护变量只是约定,没有限定外部一定不能访问
    
        __start_2 = "this is two"  # 双下划线开头的是【私有成员】,只有类对象自己能访问,连子类对象也不能访问到这个数据
        def __init__(self, id, name, age, salary):
            self.id = id
            self.name = name
            self.age = age
            self.salary = salary
    
        def get_id(self):
            print("I am privaty method, I find method is [%s]" % self.id)
    
        # 访问函数,通过这个函数去访问内部私有成员(这就是外部访问内部私有成员的接口)
        def get_start(self):
            print(self.__start_2) # 类的内部可以访问到私有成员
    
    
    p1 = People('1778', "hgzero", '21', 1000000)
    # print(People.__dict__)
    print(p1._start_1) 
    
    # print(p1.__start_2)  # 以__开头的变量之所以不能访问,是因为python在内部进行了重命名, 以一个下划线+类名__变量名  命名
    print(p1._People__start_2) # python对__start_2重命名后的变量

    4.2 继承

    子类可以拥有父类中除了私有属性外的其他所有内容。 

    从代码层面上来看,两个类具有相同的功能或者特征的时候,可以采用继承的形式,提取一个父类,这个父类中编写两个类相同的部分,然后两个类分别继承这个类就可以了。

    4.3 多态

    多态的概念指出了对象如何通过它们共同的属性和动作来操作及访问,而不需要考虑它们具体的类。

    class H2O:
         def __init__(self, name, temperature):
             self.name = name
             self.temperature = temperature
    
         def turn_ice(self):
             if self.temperature < 0:
                 print('[%s] 温度太低结冰了' % self.name)
             elif self.temperature > 0 and self.temperature < 100:
                 print('[%s] 液化成水' % self.name)
             elif self.temperature > 100:
                 print('[%s] 温度太高变成了水蒸气' % self.name)
    
    class Water(H2O):
         pass
    
    class Ice(H2O):
         pass
    
    class Steam(H2O):
         pass
    
    w1 = Water('', 25)
    i1 = Ice('', -20)
    s1 = Steam("蒸汽", 3000)
    
    def func(obj):
        obj.turn_ice()
    
    func(w1)  # 统一的接口,不同的实现
    func(i1)
    func(s1)
    
    # w2 = Water('水', 101)
    # func(w2)

    5. 类的组成成员

    5.1 类的私有成员

    • xx:公有变量
    • _x:单前置下划线,私有化属性或方法,from somemodule import *禁止导入,类对象和子类可以访问
    • __xx:双前置下划线,避免与子类中的属性命名冲突,无法在外部直接访问(名字重整所以访问不到)
    • __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ , __ 不要自己发明这样的名字
    • xx_:单后置下划线,用于避免与Python关键词的冲突

    1)每一个类的成员都有两种形式

    • 共有成员:在任何地方都能访问
    • 私有成员:只有在类的内部才能访问

    2)普通字段(对象属性)

    • 共有普通字段:对象可以访问,类内部可以访问,派生类中可以访问
    • 私有普通字段:仅类内部可以访问

    3)静态字段(静态属性)

    • 公有静态字段:类可以访问,类内部可以访问,派生类中可以访问
    • 私有静态字段:仅类内部可以访问

    4)方法

    • 共有方法:对象可以访问,类内部可以访问,派生类中可以访问
    • 私有方法:仅类内部可以访问
    class A:
        company_name = '日天company'  # 静态变量(静态字段)
        __iphone = '1353333xxxx'     # 私有静态变量(私有静态字段)
    def __init__(self,name,age): # 特殊方法 self.name = name # 对象属性(普通字段) self.__age = age # 私有对象属性(私有普通字段)
    def func1(self): # 普通方法 pass
    def __func(self): # 私有方法 print(666)
    @classmethod
    # 类方法 def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print('类方法')
    @staticmethod
    # 静态方法 def static_func(): """ 定义静态方法 ,无默认参数""" print('静态方法')
    @property
    # 属性 def prop(self): pass
    • 以__开头的成员之所以不能访问,是因为python在内部进行了重命名, 以一个   xx._类名__成员变量名  命名

    5.2 静态属性

    1)什么是静态属性

    静态属性可以使得函数就像属性一样被调用,使得这个函数成为一个属性(调用时不要加括号)

    将类的函数定义成属性以后,对象再去使用时根本无法觉察出自己是执行了一个函数然后计算出来的

    这种特性的使用方式遵循了统一访问原则

    2)三种访问方式

    由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除

    class Room:
        def __init__(self, name, owner, width, length, height):
            self.name = name
            self.owner = owner
            self.width = width
            self.length = length
            self.height = height
    
    
        # 静态属性  
        @property   # 使得函数就像属性一样被调用,使得这个函数成为一个属性(调用时不要加括号)
        def cal_area(self):
            print('%s 住的 %s 总面积是 %s' %(self.owner, self.name, self.width*self.length))
            return self.width*self.length
    
        @cal_area.setter  # 对它所修饰的静态属性进行赋值操作时触发
        def cal_area(self, val): # self就是调用它的那个实例,val就是传给他的那个值
             print('set的时候运行', val)
    
        @cal_area.deleter # 对它所修饰的静态属性进行删除操作时触发
        def cal_area(self):
            print('del的时候运行')
    
    #   注意:同一个静态方法的函数名必须相同
    
    r1 = Room("厕所", "alex", 100, 100, 10000)
    r2 = Room('公共厕所', "linghaifeng", 1, 1, 1)
    
    print(r1.cal_area)
    print(r2.cal_area)
    r1.cal_area = 'hgzero' # 赋值操作
    del r1.cal_area        # 删除操作
    
    print(r1.name)
    print(r2.name)
    
    # ==================================================================================
    # =================用property的内置属性来实现对静态属性的操作==========================
    
    class Room:
     
        def get_cal_area(self):
            print('get的时候运行')
    
        def set_cal_area(self, val): 
             print('set的时候运行', val)
    
        def del_cal_area(self):
            print('del的时候运行')
    
        cal_area = property(get_cal_area, set_cal_area, del_cal_area) # 这种写法一定要按照get、set、del的顺序来写
    
    f1 = Room()
    f1.cal_area
    f1.cal_area = 'wzh'
    del f1.cal_area

    5.3 类的其他成员

    • 方法包括:普通方法、静态方法、类方法;这三种方法在内存中都归属于类;

    1)实例方法

    • 定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法)
    • ​调用:只能由实例对象调用

    2)类方法

    • ​ 定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法)
    • ​ 调用:实例对象和类对象都可以调用
    class Room:
        tag = 1   # 类属性,直接定义在类的作用域之下了
        def __init__(self, name, owner, width, length, height):
            self.name = name
            self.owner = owner
            self.width = width
            self.length = length
            self.height = height
     
        @property  
        def cal_area(self):
            print('%s 住的 %s 总面积是 %s' %(self.owner, self.name, self.width*self.length))
            return self.width*self.length
    
        def test(self):
            print('from test', self.name)
    
        # def tell_info(self):  # 这样每当调用这个方法时都要传入一个实例,但有时的需求是不传实例直接调用类中的变量
        #     print('----->', self.tag)
    
        @classmethod  # 类方法,被调用时会自动将类名传入作为第一个参数cls
        def tell_info(cls, x):
            print(cls)
            print('----->', cls.tag, x)
    
    print(Room.tag)
    Room.tell_info(10) # 类方法在调用时会自动将类本身作为第一个参数传入给cls
    
    
    # ----------------------实例能调用类方法,但是没人这么用--------------------------
    r1 = Room("厕所", "alex", 100, 100, 10000)
    r2 = Room('公共厕所', "linghaifeng", 1, 1, 1)
    
    r1.tell_info(20) # 实例也能调用类方法
    # -----------------------------------------------------------------------------

    3)静态方法

    • 静态方法主要用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系;
    • 在静态方法中,不会涉及到类中的属性和方法的操作;
    • 静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护;
    class Room:
        tag = 1
        def __init__(self, name, owner, width, length, height):
            self.name = name
            self.owner = owner
            self.width = width
            self.length = length
            self.height = height
     
        @property  
        def cal_area(self):
            print('%s 住的 %s 总面积是 %s' %(self.owner, self.name, self.width*self.length))
            return self.width*self.length
    
        def test(self):
            print('from test', self.name)
    
        @classmethod  # 类方法,被调用时会自动将类名传入作为第一个参数cls
        def tell_info(cls, x):
            print(cls)
            print('----->', cls.tag, x)
    
        @staticmethod # 静态方法(类的工具包),只是名义上归类管理,不能使用类变量和实例变量
        def wash_body(a, b, c):
            print('%s %s %s正在washing' %(a, b, c))
    
    
    Room.wash_body("alex", "linghaifeng", "wupeiqi")

    5.4 isinstance与issubclass

    1)isinstance(a, b)

    • 判断a是否是b类(或者b类的派生类)实例化的对象
    class A:
        pass
    class B(A):
        pass
    class C(B):
        pass
    print(issubclass(B,A))
    print(issubclass(C,A))

    2)issubclass

    • 判断a类是否是b类(或者b的派生类)的派生类
    from collections import Iterable
    print(isinstance([1,2,3], list))     # True
    print(isinstance([1,2,3], Iterable)) # True
    print(issubclass(list,Iterable))     # True
    # 由上面的例子可得,这些可迭代的数据类型,list str tuple dict等 都是 Iterable的子类

    6. 反射

    • 反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)

    6.1 hasattr&getattr&setattr&delattr

    class BlackMedium:
        feture = "Ugly"
        def __init__(self, name, addr):
            self.name = name
            self.addr = addr
    
        def sell_hourse(self):
            print("[%s] 在卖房子" % self.name)
    
        def rent_hourse(self):
            print("[%s] 在租房子" % self.name)
    
    b1 = BlackMedium("Place", "北京") 
    
    # hasattr
    hasattr(b1, 'name')        # 检测b1能不能调用name这个属性
    hasattr(b1, 'sell_hourse') # 检测b1能不能调用sell_hourse这个方法
    
    # getattr
    print(getattr(b1, 'name'))
    func = getattr(b1, 'rent_hourse')         # 通过getattr拿到这个属性或者方法,如果没有则报错
    func()
    getattr(b1, 'hire_house', "hgzero_house") # 可以指定第三个参数,若找不到就返回第三个参数的值
    
    # setattr
    setattr(b1, 'sb', True)            # 将sb=True添加到b1中去,也可以改变已有的变量
    setattr(b1, 'func' , lambda x:x+1) # 添加一个匿名函数
    setattr(b1, 'func2', lambda self:self.name+' sb')
    print(b1.func(10))
    print(b1.func2(b1))
    
    # delattr
    delattr(b1, 'sb') # 将b1中的sb变量删除

    6.2 双下划线开头的getattr&setattr&delattr

    class BlackMedium:
        feture = "Ugly"
        def __init__(self, name, addr):
            self.name = name
            self.addr = addr
    
        def sell_hourse(self):
            print("[%s] 在卖房子" % self.name)
    
        def rent_hourse(self):
            print("[%s] 在租房子" % self.name)
    
        # 以下3个方法类的实例使用时才会触发
        def __getattr__(self, item): # 当调用一个不存在的属性或者方法时,就会触发__getattr__方法
            print("执行__getattr__")
    
        def __setattr__(self, key, value): # 实例化时一设置属性就会触发这个方法运行,注意这里传入的是一个键值对
            print('__setattr__执行')
            # self.key = value   # 这样设置的话,也是在设置属性,又会调用这个方法,陷入无限递归
            self.__dict__[key] = value  # 为了不陷入无限递归,可以直接在__dict__中设置属性
    
        def __delattr__(self, item):
            self.__dict__.pop(item) # 这样直接操作底层字典,也是为了避免陷入无限递归
    
    b1 = BlackMedium("Place", "北京") 
    
    # 双下划线开头的__getattr__()
    print(b1.hgzero)                 # 当调用一个不存在的属性或者方法时,就会触发__getattr__方法
    
    # 双下划线开头的__delattr__()
    del b1.feture                    # 删除某一属性或者方法时,就会触发__delattr__方法
    
    # 双下划线开头的__setattr__()
    b2 = BlackMedium("Place", "东京") # 初始化时,只要一设置属性,就会触发__setattr__方法的执行
    
    print(dir(BlackMedium))  # dir()方法可以打印出最全的类中的属性和方法
    • 使用示例
    class Foo:
        def __init__(self, name):
            self.name = name
    
        def  __getattr__(self, item):
            print('要找的属性【%s】不存在' % item)
    
        def __setattr__(self, k, v): # 这里的k和f是f1实例通过 . 的方式添加属性的操作传过来的键值对
            print('执行setattr', k, v)
            if type(v) is str:  # 设置一个验证的过程,验证设置的属性值为字符串类型
                print('开始设置')
                self.__dict__[k] = v
            else:
                print('必须是字符串类型')
    
        def __delattr__(self, item):
            print("执行delattr", item)
            # del self.item  # 这样的删除操作又会触发delattr函数,造成无限递归
            self.__dict__.pop(item)
    
    f1 = Foo('alex') # 这个实例化的过程在init方法中设置了name属性的值为alex,所以也会触发setattr方法
    f1.age = 18
    f1.gender = 'male'
    
    print(f1.__dict__) # 这里的字典如果为空,是因为实例化的过程触及了属性设置,而这个类中我们自己定义的setattr没有将属性添加到属性字典中
    # 系统自带的__setattr__方法会将设置的属性添加到属性字典中
    
    del f1.name

    6.3 动态导入模块

    # 当import*时,不会导入模块中的私有方法(前面加上_的方法),但是可以通过直接import __变量name 调用
    module_t = __import__('m.t') # 这里动态的导入模块,可以执行m中的t模块,但是返回值拿到的却是最顶层的m
    module_t.t.test1() # 由于上面返回的是最顶层的m,所以后面要导入时还要重新写
    
    import importlib
    importlib.import_module('m.t') # 这种动态导入模块的方式可以直接拿到最底层,现在已经定位到t了

    7. 双下划线方法

    7.1 item系列

    • __getitem__()
    • __setitem__()
    • __delitem__()
    class Foo:
        # 当进行对底层字典的操作时,才会触发下面这三个方法
        def __getitem__(self, item):       # 当以字典的形式调用不存在的属性时,就会触发gettiem方法
            print('__getitem__')
    
        def __setitem__(self, key, value): # 当以字典的形式设置属性时,就会触发setitem方法
            print('__setitem__')
            self.__dict__[key] = value
    
        def __delitem__(self, key):        # 当以字典的形式删除一个属性时,就会触发delitem方法
            print('__delitem__')
            self.__dict__.pop(key)
    
    f1 = Foo()
    print(f1.__dict__)
    
    f1['name'] = 'hgzero' # 相当于在给字典添加键值对,触发了setitem方法,然后该方法在内部将键值对添加到底层字典中
    f1['age'] = 21
    f1['gender'] = 'male'
    # 注意:只要是对字典操作的动作都会触发这些方法,然后真正对字典的操作都是由这些方法在内部完成的
    
    print(f1.__dict__)
    
    del f1.name # 这种以点的形式的操作不会触发delitem方法
    print(f1.__dict__)
    
    # 特别注意: 像这种  f1.__dict__['name'] = 'hgzero' 的调用方式不会触发这些方法
    
    print(f1.age)
    
    del f1['gender'] # 以字典的形式删除属性,会触发delitem方法
    
    f1['wzh']        # 以字典的形式操作一个不存在的属性,会触发getitem方法

    7.2 __len__ 、__hash__、__eq__

    1)__len__

    class B:
        def __len__(self):
            print(666)
    b = B()
    len(b) # len 一个对象就会触发 __len__方法。
    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
        def __len__(self):
            return len(self.__dict__)
    a = A()
    print(len(a))

    2)__hash__

    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
        def __hash__(self):
            return hash(str(self.a)+str(self.b))
    a = A()
    print(hash(a))

    3)__eq__

    class A:
        def __init__(self):
            self.a = 1
            self.b = 2
        def __eq__(self,obj):
            if  self.a == obj.a and self.b == obj.b:
                return True
    a = A()
    b = A()
    print(a == b)

    7.3 __str__、__repr__、__doc__

    1)__str__

    class Foo:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __str__(self): # print打印这个Foo的实例的时候,触发的其实就是__str__方法
            return 'str---> 名字是【%s】 , 年龄是【%s】' %(self.name, self.age) # 输出的结果都必须是字符串
        
    
    f1 = Foo('hgzero', 21)
    print(f1)  # print打印这个实例就会触发其__str__方法,或者是str()函数也会触发
    x = str(f1)
    print(x)

    2)__repr__

    class Foo2:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def __repr__(self): # repr方法在解释器中会被触发
            return 'repr---> 名字是【%s】 , 年龄是【%s】' %(self.name, self.age)
    
    f2 = Foo2('wzh', 23) 
    print(f2)
    
    # *******************************************************************************
    # 注意:当str和repr共存时,print会先去找str,若找不到str则会去找repr,若再找不到repr,就使用默认的
    
    # repr方法对解释器更友好
    
    # repr()函数得到的字符串通常可以用来重新获得该对象,repr()的输入对python比较友好
    # 通常情况下obj==eval(repr(obj))这个等式是成立的, 而str()就不行 obj==eval(str(obj)) 这样是不成立的

    3)__doc__

    class Foo:
        '描述信息'  # 这就是doc属性的内容
        pass
    
    class Bar(Foo):
        pass
    
    print(Foo. __doc__) # __doc__属性不会被继承,原因是每个类中都会自动定义一个doc,如果不写它的内容,就会自动定义为None
    print(Bar.__doc__)
    
    print(Foo.__dict__)
    print(Bar.__dict__)
    
    form lib.aa import C
    c1 = C()
    
    print(c1.__module__) # 查看c1来自于哪个模块
    print(c1.__class__)  # 查看c1是由哪个类产生的

    7.4 __call__方法

    • 对象后面加括号,触发执行;
    • __call__方法的执行是由对象后加括号触发的: 对象() 或者 类()()
    class Foo:
        def __init__(self, *args, **kwargs):
           print('这里执行了init方法')
        def __call__(self, *args, **kwargs):
            print('这里执行了call方法')
    
    f1 = Foo() # 执行__init__方法
    
    f1() # 执行__call__方法
    
    Foo() # 产生它的那个类下面的__call__方法
    
    # 对象 + 括号()  ======>  就是在执行这个对象所在的类下面的__call__方法

    7.5 __del__析构方法

    • 析构方法,当对象在内存中被释放时,自动触发执行
    • 此方法一般无须定义,Python在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的
    # 析构方法: 当对象在内存中被释放时,自动触发执行
    # 注意:此方法一般无需定义,析构函数的调用是由解释器在进行垃圾回收是自动触发执行的
    
    class Foo:
        def __init__(self, name):
            self.name = name
    
        def __del__(self):    # (析构方法)在实例被删除的时候触发
            print('执行了。。。')
    
    f1 = Foo('hgzero')
    print('-------------------执行结束--------------------')

    7.6 __new__方法

    __new__可以实现单例模式。

  • 相关阅读:
    C 语言中 static 的作用
    STM32 标准库
    STM32 HAL库与标准库的区别_浅谈句柄、MSP函数、Callback函数
    asp.net core launchsettings.json
    asp.net core mvc/api部署到iis
    依赖倒置来反转依赖
    ASP.NET Core in2020
    DDD学习一
    asp.net core学习一
    从零开始实现ASP.NET Core MVC的插件式开发
  • 原文地址:https://www.cnblogs.com/hgzero/p/13394799.html
Copyright © 2020-2023  润新知