• Python(9)面对对象高级编程



    此文章参考廖雪峰官网:面向对象高级编程 - 廖雪峰的官方网站 (liaoxuefeng.com)

    一、使用__slots__

    • 正常情况下,当我们定义了一个,并且根据创建了实例后,我们可以给这些实例绑定任何属性和方法,就这是动态语言的灵活性,下面来看案例:

      - 创建一个类
      >>> class Student(object):
      ...     pass
      ... 
      
      - 创建一个实例,并且绑定属性'name'
      >>> zhangsan = Student()
      >>> zhangsan.name = 'zhangsan' 
      >>> zhangsan.name
      'zhangsan'
      
      - 还可以绑定方法
      >>> def set_age(self,age):
      ...     self.age = age
      ... 
      >>> from types import MethodType  #导入模块
      >>> zhangsan.set_age = MethodType(set_age,zhangsan)  #给zhangsan实例绑定方法 
      >>> zhangsan.set_age(22) 
      >>> zhangsan.age
      22
      
      - 上面的'zhangsan'实例绑定的方法,只对'zhangsan'本身生效,如果根据'Student'类再次创建实例,'新创建的实例是没有这些方法的'
      >>> lisi = Student() 
      >>> lisi.set_age
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Student' object has no attribute 'set_age'
      >>> lisi.set_age(33)    #无法调用
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Student' object has no attribute 'set_age'
          
      - 如果想让上面的方法绑定到所有根据'Student'类创建的实例,我们可以直接给'Student类'绑定方法
      >>> def set_score(self,score):
      ...     self.score = score
      ... 
      >>> Student.set_score = set_score
      >>> lisi.set_score(99) 
      >>> lisi.score      
      99
      >>> zhangsan.set_score(98) 
      >>> zhangsan.score
      98
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
    • 通常情况下,上面的set_score方法是可以直接定义到中的,但是动态绑定允许我们在程序运行的过程中动态的为类添加功能,这在静态语言中很难实现

    • 当我们需要限制实例的属性,例如,只允许对Student类创建的实例添加nameage属性,想要达到这种效果,我们可以在定义类的时候,定义一个特殊的__slots__变量,来限制实例可添加的属性:

      - 可以看到在使用'__slots__'变量后,创建的实例'zhangsan'只可以绑定特点的属性
      >>> class Student(object):
      ...     __slots__ = ('name','age')
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.name = 'zhangsan' 
      >>> zhangsan.age = 22
      >>> zhangsan.score = 98  
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Student' object has no attribute 'score'
          
      - 要注意的是,类中的'__slots__'变量'只对当前类的实例有效,对继承的子类是无效的'
      >>> class Test_Student(Student):
      ...     pass
      ... 
      >>> lisi = Test_Student()
      >>> lisi.score = 98
      >>> lisi.score
      98
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

      注意:在类中的__slots__变量只对当前类创建的实例有效,对继承的子类是无效的

    二、使用装饰器——@property

    • 在绑定属性时,如果直接把属性暴露出去,这样虽然写起来简单,但是无法检查参数,导致可以随意修改属性,例如:

      >>> class Student(object):
      ...     pass
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.score = 98 
      >>> zhangsan.score
      98
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 可以看到,上面的zhangsan实例的score属性是可以随意定义、修改的,在实际环境中,肯定不允许这样随意进行定义、修改,为了限制score属性的范围,我们可以在类中通过添加set_score()方法来限制score属性,然后再添加一个get_score()方法来获取score,这种方式在之前的私有变量中提到过,下面来看案例:

      - 在类中使用if语句进行判断,限制score属性,可以看到在根据类创建实例后,实例的'score'属性无法随意定义、修改了
      >>> class Student(object):         
      ...     def get_score(self):
      ...             return self.score
      ...     def set_score(self,value):
      ...             if not isinstance(value,int):  #使用isinstance判断传入参数value的类型是否为int类型
      ...                     raise ValueError('score not is int')  #使用raise抛出异常
      ...             if value < 0 or value > 100:
      ...                     raise ValueError('score must between 0 — 100!!')  
      ...             self.score = value
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.set_score('98') 
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 6, in set_score
      ValueError: score not is int
      >>> zhangsan.set_score(120)  
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 8, in set_score
      ValueError: score must between 0100!!
      >>> zhangsan.set_score(98)  
      >>> zhangsan.score
      98
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
    • 虽然上面创建类的方式可以实现效果,但是调用方法略显复杂,没有直接使用属性那么简单

    • 之前有说过装饰器这个概念,装饰器可以动态的给函数添加功能,而在Python中,对于类的方法,装饰器一样有效,Python内置的@property装饰器就是负责把一个方法变成属性进行调用

      - '@property'的实现比较复杂,来看下面的'Student'类,想要把一个'获取属性的方法'变成属性,只需要加上'@property'即可,而在下面,还可以看到'@property.setter',这是创建的另一个装饰器,负责把一个'赋值属性的方法'变成属性,于是,我们就有拥有了一个可控的属性操作
      >>> class Student(object):
      ...     @property
      ...     def score(self):
      ...             return self._score
      ...     @score.setter
      ...     def score(self,value):
      ...             if not isinstance(value,int):
      ...                     raise ValueError('score not is int!')
      ...             if value < 0 or value > 100:
      ...                     raise ValueError('score must between 0 - 100!!') 
      ...             self._score = value
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.score = 98  #进行赋值操作,实际调用的是第二个score方法,也就是zhangsan.set_score(98)这样的
      >>> zhangsan.score      #同样的,进行获取操作,实际调用的是第一个score方法,也就是zhangsan.get_score()这样的
      98
      >>> zhangsan.score = '98'   #可以看到在赋值时不符合属性要求会报错
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 8, in score
      ValueError: score not is int!
      >>> zhangsan.score = 120 
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 10, in score
      ValueError: score must between 0 - 100!!
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 上面的代码中,单独的@property其实就是getter方法,并且是只读属性的,例如:
      - 下面只使用了'getter'的装饰器,可以看到,除了在创建实例的时候可以传入'self._age'的值,创建后直接修改'zhangsan''age'属性值会报错,这就是'只读属性'
      >>> class Student(object):
      ...     def __init__(self,age):
      ...             self._age = age
      ...     @property
      ...     def age(self):
      ...             return print(self._age) 
      ... 
      >>> zhangsan = Student(22) 
      >>> zhangsan.age
      22
      >>> zhangsan.age = 25
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: can't set attribute 'age'
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 而上面代码中的@score.setter其实就是setter方法,也就是用来修改属性的值的,例如:
      - 可以看到在使用'@age.setter'后,可以随意的去修改'self._age'的值
      >>> class Student(object):
      ...     @property
      ...     def age(self):
      ...             return print(self._age)
      ...     @age.setter
      ...     def age(self,value):
      ...             self._age = value
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.age = 22
      >>> zhangsan.age
      22
      >>> zhangsan.age = 25
      >>> zhangsan.age      
      25
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 注意:

      要特别注意的是,属性的方法名千万不要和实例变量重名,否则会造成无限递归,导致栈溢出报错,例如:

      >>> class Student(object):
      ...     @property
      ...     def age(self):
      ...             return print(self._age)  #这里设置为self._age,就是因为如果不加下划线,那么变量就跟方法名冲突了
      ...     @age.setter
      ...     def age(self,value):
      ...             self._age = value
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 其实这个上面装饰器达到的效果可以添加多个方法来实现,例如添加get_ageset_age等,但是最终调用的是方法,如zhangsan.get_age(),而使用装饰器后,可以看到调用的是属性zhangsan.age

    三、多重继承(多继承)

    • 在之前说面向对象编程时,说到过三大基本特性之一的继承特性,继承是面向对象编程的一个重要方式,因为通过继承,子类就可以继承并扩展父类的功能,而一个类是可以继承多个父类的,这种继承就叫做多重继承,也就是多继承,下面来看一个案例,理解多继承的作用:

    • 假如我们要实现以下四种动物:

      1. Dog——狗
      2. Bat——蝙蝠
      3. Parrot——鹦鹉
      4. Ostrich——鸵鸟
    • 首先可以根据特性进行分类,例如通过哺乳动物和鸟类动物,可以这样分:
      在这里插入图片描述

    • 而如果通过“能跑”和“能飞”来说,可以这样分:
      在这里插入图片描述

    • 如果在分的细点,就需要设置更多的层次,例如,哺乳动物中能飞能跑的,鸟类中能飞能跑的

    在这里插入图片描述

    • 如果还要增加“宠物类”和“非宠物类”的话,类的数量会呈指数增长,这样明显是不行的,正确的做法就是使用多重继承通过多重继承,一个子类就可以同时获得多个父类的所有功能,例如:

      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      #最上层的Animal类
      class Animal(object):
          pass
      
      #第二层的哺乳动物和鸟类
      class Mammal(Animal):
          pass
      
      class Bird(Animal):
          pass
      
      #第三层的各种动物
      class Dog(Mammal):
          pass
      
      class Bat(Mammal):
          pass
      
      class Parrot(Bird):
          pass
      
      class Ostrich(Bird):
          pass
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 下面,我们要给动物加上各种功能,例如:
      - 添加跑和飞的类
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      #添加跑和飞的类
      class Runnable(object):
          def run(self):
              return print('Running...')
      
      class Flyable(object):
          def fly(self):
              return print('Flying...')
          
      #最上层的Animal类
      class Animal(object):
          pass
      
      #第二层的哺乳动物和鸟类
      class Mammal(Animal):
          pass
      
      class Bird(Animal):
          pass
      
      #第三层的各种动物
      class Dog(Mammal):
          pass
      
      class Bat(Mammal):
          pass
      
      class Parrot(Bird):
          pass
      
      class Ostrich(Bird):
          pass
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 可以利用多重继承的特性让动物继承多个类,例如:
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      #添加跑和飞的类
      class Runnable(object):
          def run(self):
              return print('Running...')
      
      class Flyable(object):
          def fly(self):
              return print('Flying...')
          
      #最上层的Animal类
      class Animal(object):
          pass
      
      #第二层的哺乳动物和鸟类
      class Mammal(Animal):
          pass
      
      class Bird(Animal):
          pass
      
      #第三层的各种动物
      class Dog(Mammal,Runnable):
          pass
      
      class Bat(Mammal,Flyable):
          pass
      
      class Parrot(Bird,Flyable):
          pass
      
      class Ostrich(Bird,Runnable):
          pass
      
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
    • 多重继承这种让类继承多个类的设计,也叫做Mixln,是一种常见设置,只允许单一继承的语言,例如java是不能使用Mixln设计的

    四、定制类

    • 在看到类似于__xx__这样的变量或者函数时要知道,这种函数是有特殊用途的,例如可以限制实例添加属性的__slots__变量,以及判断字符长度的__len__()函数,除了这些,Python的类还有许多这样有特殊用途的函数,可以帮助我们定义类
    • 下面来看几个常用的定义方法:

    __str__

    • 我们先来定义一个Student类,并且打印一个实例:

      >>> class Student(object):
      ...     def __init__(self,name):
      ...             self.name = name
      ... 
      >>> print(Student('lisi'))   #打印lisi实例
      <__main__.Student object at 0x0000028800FF61D0>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
    • 可以看到在打印实例时,输出了一堆<__main__.Student object at 0x0000028800FF61D0>,这样不易阅读,我们可以在类中添加一个__str__()方法,从而使输出更容易阅读,例如:

      >>> class Student(object):       
      ...     def __init__(self,name):
      ...             self.name = name 
      ...     def __str__(self):
      ...             return 'Student object (name is : %s)' % self.name 
      ... 
      >>> print(Student('lisi')) 
      Student object (name is : lisi)
      
      - 通过'__str__()'方法,我们可以自定义想要输出的内容,在打印实例的时候会输出,但是只有直接使用'print'时才会输出,赋值给变量的话,则还是原来的
      >>> lisi = Student('lisi') 
      >>> lisi
      <__main__.Student object at 0x00000208C62F64A0>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
    • 在赋值变量后,输出的还是原来的不易阅读的输出,这是因为直接显示变量调用的不是__str__(),而是__repr__(),这两个方法的区别是==__str__()返回用户看到的字符串==,而==__repr__()返回程序开发者看到的字符串,即__repr__()是为了调试服务的==

    • 解决上面的方法就是再定义一个__repr__()方法,但是通常可以写成__repr__ = __str__

      >>> class Student(object):
      ...     def __init__(self,name):
      ...             self.name = name
      ...     def __str__(self):
      ...             return 'Student object (name is : %s)' % self.name
      ...     __repr__ = __str__
      ... 
      >>> print(Student('lisi')) 
      Student object (name is : lisi)
      >>> lisi = Student('lisi') 
      >>> print(lisi) 
      Student object (name is : lisi)
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

    __iter__

    • 如果一个类想像字典或者元组那样被用于for循环,那么就必须要添加一个__iter__()方法,该方法会返回一个迭代对象,Python的for循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出,下面来看一个案例:

      - 斐波那契数列:
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      class Fib(object):
          def __init__(self):
              self.a,self.b = 0,1  #初始化定义两属性的值
          def __iter__(self):  #__iter__返回实例本身
              return self
          def __next__(self):  
              self.a,self.b = self.b,self.a + self.b  
              if self.a > 100:
                  raise StopIteration()
              return self.a
      
      for i in Fib():
          print(i)
          
      #输出
      1
      1
      2
      3
      5
      8
      13
      21
      34
      55
      89
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29

    __getitem__

    • 上面的Fib实例虽然能够用于for循环,但是把实例直接当作列表来用还是不行,也就是说一些类似于下标的操作是无法执行的,例如:

      >>> class Fib(object):
      ...     def __init__(self):
      ...             self.a,self.b = 0 , 1
      ...     def __iter__(self):
      ...             return self
      ...     def __next__(self):
      ...             self.a,self.b = self.b,self.a + self.b
      ...             if self.a > 100:
      ...                     raise StopIteration()
      ...             return self.a
      ... 
      >>> for i in Fib():
      ...     print(i) 
      ... 
      1
      1
      2
      3
      5
      8
      13
      21
      34
      55
      89
      >>> Fib()[5]   #报错了
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      TypeError: 'Fib' object is not subscriptable
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
    • 如果想要使创建的实例可以像列表那样使用下标的话,可以再类中添加__getitem__()方法:

      >>> class Fib(object):
      ...     def __getitem__(self,n):
      ...             a,b = 1,1
      ...             for i in range(n):
      ...                     a,b = b,a + b
      ...             return a
      ... 
      >>> f = Fib()
      >>> Fib()
      <__main__.Fib object at 0x000002C6A39A79D0>
      >>> Fib()[0]   #可以使用下标获取元素
      1
      >>> Fib()[5] 
      8 
      >>> f[5] 
      8
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
    • 列表还有一种切片方法,例如:

      >>> list(range(100))[96:100] 
      [96, 97, 98, 99]
      >>> Fib()[5:6]  
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 4, in __getitem__
      TypeError: 'slice' object cannot be interpreted as an integer
      >>> f[5:6] 
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 4, in __getitem__
      TypeError: 'slice' object cannot be interpreted as an integer
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 可以看到列表的切片方法是不适用于实例的,原因是__getitem__()传入的参数可能是一个int类型的,也可能是一个slice切片对象,所以需要做判断,判断传入参数是int还是slice

      >>> class Fib(object):
      ...     def __getitem__(self,n):
      ...             if isinstance(n,int):
      ...                     a,b = 1, 1
      ...                     for i in range(n):
      ...                             a,b = b,a + b
      ...                     return a
      ...             if isinstance(n,slice):
      ...                     start = n.start    #这是slice带的方法
      ...                     stop = n.stop
      ...                     if start is None:
      ...                             start = 0
      ...                     a,b = 1 , 1
      ...                     L = []
      ...                     for i in range(stop):
      ...                             if i >= start:
      ...                                     L.append(a)
      ...                             a,b = b,a + b
      ...                     return L
      ... 
      >>> f = Fib()
      >>> f[0:5] 
      [1, 1, 2, 3, 5]
      >>> f[5:6] 
      [8]
      >>> f[0:5:2]  #但是没有对step跳数参数做处理
      [1, 1, 2, 3, 5]
      >>> f[-1:] 	#也无法对负数做处理
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 15, in __getitem__
      TypeError: 'NoneType' object cannot be interpreted as an integer
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
    • 可以从上面的案例看出,如果想要正确实现一个__getitem__()方法,还需做很多事情,例如跳数和负数等

    • 此外,如果把对象看做是字典,那么__getitem__()的参数也可能是一个可以作为的对象,例如str,与之对应的是__setitem__()方法,把对象视为列表或者字典来对集合赋值,还有一个__delitem__()方法,用于删除指定元素,通过这些方法,可以使我们在创建类时表现的和Python自带的列表、元组、字典没什么区别

    __getattr__

    • 正常情况下,当我们调用类的方法或者属性时,如果不存在,那么就会报错,例如:

      >>> class Student(object):
      ...     def __init__(self):
      ...             self.name = 'zhangsan' 
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.name   
      'zhangsan'
      >>> zhangsan.score
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'Student' object has no attribute 'score'
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 可以看到错误信息就是在说没有找到score这个属性,想要避免这种错误,除了手动定义一个score属性之外,在Python中还可以在类中添加一个__getattr__()方法,这个方法可以动态返回一个属性,下面来看案例:

      >>> class Student(object):
      ...     def __init__(self):
      ...             self.name = 'zhangsan' 
      ...     def __getattr__(self,value):
      ...             if value == 'score':
      ...                     return 98
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.name
      'zhangsan'
      >>> zhangsan.score
      98
      >>> zhangsan.abc  #调用不存在的属性,并且在__getattr__方法里也没有的,就会返回None 
      >>> if zhangsan.abc == None:
      ...     print("abc is None!!") 
      ... 
      abc is None!!
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 我们可以发现,在添加__getattr__()方法后,创建的实例除了可以直接调用name,还可以调用不存在的属性score,这是因为在调用不存在的属性时,Python解释器会试图使用__getattr__(self,'score')来尝试获得属性,这样就可以返回score的值

    • 并且还可以返回函数:

      >>> class Student(object):
      ...     def __getattr__(self,attr):
      ...             if attr == 'age':
      ...                     return lambda:98
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.age  #返回的是一个函数
      <function Student.__getattr__.<locals>.<lambda> at 0x00000205B6373E20>
      >>> zhangsan.age() #相同的调用方式需要加括号,表示调用函数
      98
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
    • 需要注意的是,只有在没有找到指定属性的情况下,才会调用__getattr__()方法,已经存在的属性,例如上面案例中,在类里提前定义的name属性,是不会去__getattr__()方法里去找的,会直接输出

    • 在上面的案例可以发现,在使用__getattr__()方法后,调用除了类里、__getattr__()方法里出现的属性时,都会返回None,这是因为定义的__getattr__()方法默认返回的就是None,而返回的数据,我们可以通过raise抛出异常来修改,例如:

      >>> class Student(object):
      ...     def __getattr__(self,attr):
      ...             if attr == 'age':
      ...                     return 98
      ...             raise AttributeError('Student object has no attribute %s' % attr) 
      ... 
      >>> zhangsan = Student()
      >>> zhangsan.age
      98
      >>> zhangsan.aaa
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 5, in __getattr__
      AttributeError: Student object has no attribute aaa  #调用不存在的属性会抛出异常
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
    • 利用__getattr__()方法,我们可以把一个类的所有属性和方法的调用全部动态化处理,不需要任何其他手段,这种动态调用可以针对完全动态的情况做调用

    __call__

    • 一个对象实例可以有自己的方法和属性,当我们调用实例方法的时候,我们使用instance.metod()来调用,而在Python中,是可以直接在实例本身上调用的

    • 在创建类时,只需要添加一个__call__()方法,就可以直接对实例进行调用,例如:

      >>> class Student(object):
      ...     def __init__(self,name):
      ...             self.name = name 
      ...     def __call__(self):
      ...             print('name is : %s' % self.name) 
      ... 
      >>> zhangsan = Student('zhangsan') 
      >>> zhangsan()  #可以看到把直接调用了实例,后面加了括号
      name is : zhangsan
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
    • 可以看到,在添加__call__()方法后,可以直接调用实例本身,然后会执行__call__()方法下的代码

    • __call__()方法还可以定义参数,对实例进行直接调用和对一个函数直接调用是一样的,所以完全可以把对象看作成函数,把函数看作成对象,因为两者之间其实没有什么太大的区别

    • 如果把对象看成函数,那么函数本身其实也是可以在运行期间动态创建出来的,这是因为类的实例都是在运行期间创建出来了,这样的话,对象和函数的界限就变得模糊了

    • 那么该如何判断一个变量是对象还是函数呢,其实在更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable可调用的对象,例如函数和上面定义的带有__call__()方法的类实例

      • 使用callable()函数可以检查一个对象是否是可调用的,如果返回True,对象仍然可能调用失败,但是如果返回的是False,那么调用绝对会失败

      • 对于函数、方法、lambda函式、类以及有__call__()方法的类实例,callable()函数都会返回True,语法就是callable(object)

      • 下面来看几个例子:

        >>> class Student(object):
        ...     def __init__(self,name):
        ...             self.name = name 
        ...     def __call__(self):
        ...             print("name is : %s " % self.name) 
        ... 
        >>> callable(Student)   
        True
        >>> zhangsan = Student('zhangsan') 
        >>> zhangsan()   
        name is : zhangsan 
        >>> callable(zhangsan) 
        True
        >>> callable('aaa')    
        False
        >>> callable(123)   
        False
        >>> callable(None) 
        False
        
        - 可以看到类和实例返回的都是'True',其他的字符串、数字、None返回的都是'False'
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21

    五、使用枚举类

    这个小结,写的不是很全,建议可以去看其他关于枚举类的资源

    • 当我们需要定义常量时,有一个办法就是使用大写变量通过整数来定义,例如:

      - 月份
      JAN = 1
      FEB = 2
      MAR = 3
      ...
      NOV = 11
      DEC = 12
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    • 这样定义的好处肯定就是简单,缺点就是类型是数字,并且仍然是变量

    • 更好的方法就是为这样的枚举类型定义一个class类类型,然后每一个常量都是类的一个唯一实例,而在Python中,提供了Enum类来实现这个功能

      - 这样我们就获得了'Month'类型的枚举类,可以看到,下面直接使用'Month.Jan'来引用一个常量
    >>> from enum import Enum
    >>> Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
    >>> Month.Jan
    <Month.Jan: 1>
    >>> Month.Mar
    <Month.Mar: 3>
    >>> Month.Dec
    <Month.Dec: 12>
    
    - 还可以利用for循环枚举它的全部成员
    >>> for name,number in Month.__members__.items():
    ...     print(name,'=>',number,',',number.value)  
    ... 
    Jan => Month.Jan , 1
    Feb => Month.Feb , 2
    Mar => Month.Mar , 3
    Apr => Month.Apr , 4
    May => Month.May , 5
    Jun => Month.Jun , 6
    Jul => Month.Jul , 7
    Aug => Month.Aug , 8
    Sep => Month.Sep , 9
    Oct => Month.Oct , 10
    Nov => Month.Nov , 11
    Dec => Month.Dec , 12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 上面的number.value中,value属性是自动赋值给成员的int常量的,默认是从1开始计算
    • 如果需要更加精确的控制枚举类型,可以从Enum派生出自定义类
    >>> from enum import Enum,unique
    >>> @unique         #@unique装饰器可以帮助检查保证没有重复值
     ... class Weekday(Enum):
     ...     Sun = 0
     ...     Mon = 1
     ...     Tue = 2
     ...     Wed = 3 
     ...     Thu = 4
     ...     Fri = 5
     ...     Sat = 6
     ... 
    >>> day_1 = Weekday.Mon
    >>> day_1
    >>> <Weekday.Mon: 1>
    >>> print(day_1) 
    >>> Weekday.Mon
    >>> print(Weekday.Tue) 
    Weekday.Tue
    >>> print(Weekday['Tue']) 
    Weekday.Tue
    >>> print(day_1 == Weekday.Mon) 
    True
    >>> print(day_1 == Weekday(1))  
    True
    >>> Weekday(7) 
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "C:\Users\RZY\AppData\Local\Programs\Python\Python310\lib\enum.py", line 385, in __call__
        return cls.__new__(cls, value)
      File "C:\Users\RZY\AppData\Local\Programs\Python\Python310\lib\enum.py", line 710, in __new__
        raise ve_exc
    ValueError: 7 is not a valid Weekday
    >>> Weekday(6) 
    <Weekday.Sat: 6>
    >>> for name,number in Weekday.__members__.items():
    ...     print(name, '=>', number) 
    ... 
    Sun => Weekday.Sun
    Mon => Weekday.Mon
    Tue => Weekday.Tue
    Wed => Weekday.Wed
    Thu => Weekday.Thu
    Fri => Weekday.Fri
    Sat => Weekday.Sat
    
    - 从上面可以看出,既可以用成员名称引用枚举常量,也可以直接根据value的值获得枚举常量。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    六、使用元类

    • 元类就是用来创建类的类

    type()

    • type就是Python在背后用来创建所有类的元类

    • Python中一切皆对象,包括整数、字符串、函数以及类,它们全部都是对象,而且它们都是从一个类创建而来,这个类就是type,type就是Python的内建元类,当然了,也可以创建自己的元类。

    • 动态语言和静态语言最大的区别就是函数和类的定义,静态语言是在编译时定义的,而动态语言时运行时动态创建的,例如:

      - 想要定义一个'Hello'的类,可以写一个'hello.py'的模块,以下是在linux环境下
      [root@centos-1 ~]# vim hello.py
      #!/usr/bin/env python3
      # -*- coding: utf-8 -*-
      class Hello(object):
      	def __init__(self,name='zhangsan'):
      		self.name = name
      
      	def hello(self):
      		print('Hello %s' % self.name)
      
      #保存退出
      [root@centos-1 ~]# python3
      Python 3.9.9 (main, May 13 2022, 15:23:56) 
      [GCC 4.8.5 20150623 (Red Hat 4.8.5-16)] on linux
      Type "help", "copyright", "credits" or "license" for more information.
      >>> from hello import Hello
      >>> zhangsan = Hello()
      >>> zhangsan.name
      'zhangsan'
      >>> zhangsan.hello()
      Hello zhangsan
      >>> lisi = Hello('lisi')
      >>> lisi.name
      'lisi'
      >>> lisi.hello()
      Hello lisi
      >>> type(Hello)  #查看Hello类的类型,可以看到类型是type类型的
      <class 'type'>
      >>> type(zhangsan)   #查看zhangsan实例的类型,可以看到是hello.Hello,前面的hello即hello.py模块,后面的Hello即模块中的Hello类
      <class 'hello.Hello'>
      >>> type(lisi)
      <class 'hello.Hello'>
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
    • 在Python中,类的定义是运行时动态创建的,而创建类的方法就是使用type()函数

    • type()函数既可以返回一个对象的类型,也可以创建出新的类型(类),无需使用class关键字创建,例如:

      >>> def fn(self,name='zhangsan'):  #定义fn函数
      ...     print('Hello %s' % name)
      ... 
      >>> Hello = type('Hello',(object,),dict(hello=fn)) #使用type创建Hello类
      >>> zhangsan = Hello()
      >>> zhangsan.hello()
      Hello zhangsan
      >>> type(Hello)
      <class 'type'>
      >>> type(zhangsan)
      <class '__main__.Hello'>
      
      - 注释:
      type('Hello',(object,),dict(hello=fn))传入的参数分别是:
      1'Hello' : 这是创建的类的名称
      2'(object,)' : 创建的类继承的父类,如果只有一个父类,要记得'元组在只有单元素时后面要加逗号'
      3'dict(hello=fn)' : 创建的类中,方法可以和函数绑定在一起,这里把创建的'fn'函数绑定到创建类的'hello'方法上,可以看到最终实例调用的是'hello'方法,这里使用的是键值的字典格式,这段可以之间写成字典,也可以使用dict转换一下
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
    • 可以看到,通过type创建的类和直接使用class关键字创建的类是一样的,这是因为Python解释器在遇到类的定义时,其实只是扫描一下类定义的语法,然后直接调用type()函数进行创建的

    扩展——metaclass

    • 除了上面使用的type()函数动态创建类以外,要控制类的创建行为,还可以使用metaclassmetaclass直译就是元类,对于metaclass来说,廖雪峰大神的解释是这样的:

      • 正常在定义完类之后,我们就可以根据这个类去创建出实例,步骤是:定义类——>创建实例

      • 但是如果想创建出类呢,那么就必须根据metaclass创建出,步骤就变成了:定义metaclass——>创建类——>创建实例可以看做成类是metaclass创建出来的实例

    • metaclass是Python面向对象里最难理解的,也是最难的魔术代码,正常情况下是不会碰到需要metaclass的情况,所以这里写的是扩展,下面来简单看一下案例:

      - 定义类'ListMetaclass'类,按照默认习惯,metaclass的类名总是以'Metaclass'结尾的
      >>> class ListMetaclass(type):
      ...     def __new__(cls, name, bases, attrs):
      ...             attrs['add'] = lambda self, value: self.append(value) 
      ...             return type.__new__(cls, name, bases, attrs)
      ... 
      >>> class MyList(list, metaclass=ListMetaclass):  #继承父类list
      ...     pass
      ... 
      
      
      #注释:
      当我们传入关键字参数'metaclass'后,魔术就生效了,它指示Python解释器在创建'MyList'类时,要通过'ListMetaclass.__new__()'来创建,我们可以修改类的定义,例如,加上新的方法然后,返回修改后的定义
      其中'__new__'方法接收到的参数依次为:
      1'cls' : 当前准备创建类的对象
      2'name' : 类的名字,也就是'MyList'
      3'bases' : 类继承的父类集合,也就是'list'
      4'attrs' : 创建类的方法集合,这里写的是' attrs['add'] = lambda self, value: self.append(value) ',其中'attrs['add']''add'就是方法名称,后面的lambda是方法的代码,使用lambda匿名函数,实现的其实就是往实例对象里面添加元素,实例对象是一个列表
      
      
      
      - 下面来测试一下'MyList'是否可以调用'add'方法,可以看到调用成功,但是普通的list并没有add方法
      >>> L = MyList()
      >>> L.add(1) 
      >>> L #可以看到,实例对象是一个列表
      [1]    
      >>> L2 = list()
      >>> L2.add(1) 
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
      AttributeError: 'list' object has no attribute 'add'
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31

    七、扩展——ORM对象关系映射

    这是廖雪峰大神‘面向对象高级编程’的最后一个小节,这个有点难,有兴趣可以去官网看看,这里不写具体代码了

    • ORM全称Object Relational Mapping,即对象-关系映射,就是把关系型数据库的一行数据映射为一个对象,一个类对应一个表,这样写代码会更加简单,不需要直接操作SQL语句

    • 要编写一个ORM框架,所有的类只能动态定义,因为只有使用者才能够根据表的结构定义出对应的类

  • 相关阅读:
    消除“数据烟囱”,瓴羊港如何打破壁垒将多数据融通成大数据?
    springBoot自动装配
    02-MySQL-基础-增删改查
    【lesson9】进程
    【oracle数据库】最全最详细的数据库查询
    00、数组及字符串常用的 API(详细剖析)
    Linux·设备
    SpringMVC基础篇(三)
    Mybatis-Plus入门实践
    夯实算法-每日温度
  • 原文地址:https://blog.csdn.net/rzy1248873545/article/details/124884864