zoukankan      html  css  js  c++  java
  • 面向对象进阶

    1. 类的其他内置函数

    1.1 isinstance 和 issubclass

    1. isinstance(obj, cls)
    判断第一个参数是否是第二个参数的实例对象,返回布尔值。第一个参数为对象,第二个参数为类。

    class Animal:
        pass
    class Dog(Animal):
        pass
    
    d1 = Dog()
    print(isinstance(d1, Dog))      # True
    print(isinstance(d1, Animal))   # True
    
    # 也可以和 type 一样用来判断类型
    print(isinstance('python', str))    # True
    

    在继承关系中,一个对象的数据类型是某个子类,那么它也可以被看作是父类,反之则不行。

    2. issubclass(cls, class_or_tuple))
    判断第一个参数是否是第二个参数的子类,返回布尔值。

    class Animal:
        pass
    class Dog(Animal):
        pass
    print(issubclass(Dog, Animal))  # True
    

    1.2 __getattribute__

    __getattribute__(object, name) 在获取对象属性时,不管是否存在都会触发。

    class Animal:
        def __init__(self, name):
            self.name = name
        
        def __getattribute__(self, item):
            print('触发 getattribute')
    
    a1 = Animal('小黑')
    a1.color    # 触发 getattribute
    a1.name     # 触发 getattribute
    

    __getattr__ 同时存在时,只会触发 __getattribute__,除非抛出 AttributeError异常(其他异常不行):

    class Animal:
        def __init__(self, name):
            self.name = name
        
        def __getattr__(self, item):
            print('触发 getattr')
        
        def __getattribute__(self, item):
            print('触发 getattribute')
            raise AttributeError('触发')   # 使用异常处理 
    a1 = Animal('小黑')
    a1.color
    
    ------------
    触发 getattribute
    触发 getattr
    

    这里我们使用异常处理 raise 模拟 Python内部机制,抛出 AttributeError,使得 __getattr__ 也会被触发。

    1.3 __getitem__、__setitem__、__delitem__

    __getattr__、__setattr__、__delattr__ 类似,不同之处在于它是以字典形式访问、修改或删除,并且只要访问、修改或删除就会触发。

    class Animal:
        def __getitem__(self, item):
            print('__getitem__')
            return self.__dict__[item]
            
        def __setitem__(self, key, value):
            print('__setitem__')
            self.__dict__[key] = value
            
        def __delitem__(self, item):
            print('__delitem__')
            self.__dict__.pop(item)
            
    a1 = Animal()
    print(a1.__dict__)     # {}
    # set
    a1['name'] = 'rose'     # __setitem__
    print(a1.__dict__)     # {'name': 'rose'}
    
    # get
    print(a1['name'])   # __getitem__   rose
    
    # del
    del a1['name']     # __delitem__
    print(a1.__dict__)  # {}
    

    1.4 __str__、__repr__

    __str__ 和 __repr__ 都是用来定制对象字符串显示形式

    # L = list('abcd')
    # print(L)        # ['a', 'b', 'c', 'd']
    
    # f = open('a.txt', 'w')
    # print(f)            # <_io.TextIOWrapper name='a.txt' mode='w' encoding='cp936'>
    
    class Animal:
        pass
    
    a1 = Animal()
    print(a1)          # <__main__.Animal object at 0x00000000053E5358> 标准的对象字符串格式
    

    Python 中一切皆对象,L、f 和 a1 都是对象,但是它们的显示形式却是不一样。这是因为 Python 内部对不同的对象定制后的结果。我们可以重构 __str__ 或 __repr__ 方法,能够更友好地显示:
    重构 __str__

    class Animal:
        pass
    
        def __str__(self):
            return '自定制对象的字符串显示形式'
        
    a1 = Animal()
    print(a1)
    
    m = str(a1)     # print() 的本质是在调用 str(),而 str() 的本质是在调用 __str__()
    print(m)
    
    print(a1.__str__())  # obj.__str__()
    
    ---------
    # 自定制对象的字符串显示形式
    # 自定制对象的字符串显示形式
    # 自定制对象的字符串显示形式
    

    可以看出,我们重构 __str__ 方法后,返回的是我们定制的字符串。而 print() 和 str() 的本质都是在调用 object.__str__()

    重构 __repr__

    class Animal:
        pass
    
        def __repr__(self):
            return 'repr'
    a1 = Animal()
    print(a1)       # repr		返回 repr
    print(a1.__repr__())    # repr
    

    没有重构 __repr__ 方法时,输出 a1,结果是对象内存地址。当构建了后,输出 repr。

    __str__ 没定义时,用 __repr__ 代替输出。当两者都存在时,__str__ 覆盖 __repr__

    两者区别:

    • 打印操作,首先尝试 __str__,若类本身没有定义则使用__repr__ 代替输出,通常是一个友好的显示。
    • __repr__ 适用于所有其他环境(包括解释器),程序员在开发期间通常使用它。

    1.5 __format__

    用于类中自定制 format。

    s = '{0}{0}{0}'.format('rose')
    print(s)      # 'roseroserose'
    

    重构 __format__

    # 定义格式化字符串格式字典
    date_dic = {
        'ymd': '{0.year}-{0.month}-{0.day}',
        'mdy': '{0.month}-{0.day}-{0.year}'
    }
    
    class Date:
        def __init__(self, year, month, day):
            self.year = year
            self.month = month
            self.day = day
            
        def __format__(self, format_spec):
            # 当用户输入的格式为空或其他时,设定一个默认值
            if not format_spec or format_spec not in date_dic:
                format_spec = 'ymd'
            fm = date_dic[format_spec]
            return fm.format(self)
            
    d1 = Date(2018, 11, 28)
    # x = '{0.year}-{0.month}-{0.day}'.format(d1)
    # print(x)      # 打印 2018-11-28
    
    print(format(d1, 'ymd'))    # 2018-11-28
    print(format(d1, 'mdy'))    # 11-28-2018
    print(format(d1))   # 当为空时,输出默认格式 ymd
    

    format 实际调用的是 __format__()

    1.6 __slots__

    __slots__ 实质是一个类变量,它可以是字符串、列表、元组也可以是个可迭代对象。
    它的作用是用来覆盖类的属性字典 __dict__。我们都知道类和对象都有自己独立的内存空间,每产生一个实例对象就会创建一个属性字典。当产生成千上万个实例对象时,占用的内存空间是非常可怕的。
    当我们用定义 __slots__ 后,实例便只能通过一个很小的固定大小的数组来构建,而不是每个实例都创建一个属性字典,从而到达节省内存的目的。

    class Animal:
    #     __slots__ = 'name'
        __slots__ = ['name', 'age']
        pass
    
    a1 = Animal()
    # # print(a1.__dict__)   # AttributeError: 'Animal' object has no attribute '__dict__'
    # a1.name = 'rose'
    # print(a1.__slots__)     # name
    # print(a1.name)           # rose
    
    a1.name = 'lila'    # 设置类变量的值
    a1.age = 1
    print(a1.__slots__)        # ['name', 'age']
    print(a1.name, a1.age)      # lila 1
    

    Tips:

    • 它会覆盖 __dict__ 方法,因此依赖 __dict__ 的操作,如新增属性会报没有这个属性,因此要慎用。
    • 定义为字符串形式时,表示只有一个类变量。列表或元组,其中元素有多少个就表示有多少个类变量。

    1.7 __doc__

    用于查看类的文档字符串,不能继承。若类没有定义文档字符串,则返回 None(Python 内部自定义的)。

    class Animal:
        """动物类"""
        pass
    class Dog(Animal):
        pass
    
    print(Animal.__doc__)
    print(Dog.__doc__)
    print(Dog.__dict__)
    
    ----------
    # 动物类
    # None
    # {'__module__': '__main__', '__doc__': None}
    

    1.8 __module__ 和 __class__

    用于查看当前操作对象是在哪个模块里和哪个类里。

    # a.py
    class Cat:
        name = 'tom'
        pass
    
    # b.py
    from a import Cat
    
    c1 = Cat()
    print(c1.__module__)    # a
    print(c1.__class__)     # Cat
    

    1.9 __del__

    析构方法,当对象再内存中被释放时(如对象被删除),自动触发。
    Python 有自动回收机制,无需定义 __del__。但是若产生的对象还调用了系统资源,即有用户级和内核级两种资源,那么在使用完毕后需要用到 __del__ 来回收系统资源。

    class Foo:
        def __init__(self, name):
            self.name = name
        def __del__(self):
            print('触发 del')
    f1 = Foo('rose')        
    
    # del f1.name   # 不会触发
    del f1          # 会触发
    print('-------->')
    
    # -------->
    # 触发 del
    
    # 触发 del
    # -------->
    
    • 第一种情况时删除数据属性,不会触发,先打印 ---->,再触发 (这是由 Python 内部自动回收机制导致的,当程序执行完毕后内存自动被回收)
    • 第二种情况删除对象,触发。所有先触发后打印。

    1.10 __call__

    当对象加上括号后,触发执行它。若没有类中没有重构 __call__,则会报 not callable

    class Animal:
        def __call__(self):
            print('对象%s被执行' % self)
    
    a1 = Animal()
    a1()
    
    -------------------
    # 对象<__main__.Animal object at 0x00000000053EC278>被执行
    

    Python 一切皆对象,类也是个对象。而类加括号能调用执行,那么它的父类(object)中必然也会有个 __call__ 方法。

    1.11 __iter__ 和 __next__ 实现迭代器协议

    • 迭代器协议:对象必须有一个 __next__或next() 方法,执行该方法可返回迭代项的下一项,直到超出边界,引发 StopIteration 终止迭代。
    • 可迭代对象:指的是实现了迭代器协议的对象,其内部必须要有一个 __iter__ 方法。
    • for 循序其实质是调用对象内部的 __inter__ 方法,它能够自动捕捉 StopIteration 异常,因此超出边界也不会报错。

    因此可迭代对象内部必须有一个 __iter__ 和 __next__ 方法。

    class Dog:
        def __init__(self, age):
            self.age = age
            
    d1 = Dog(1)
    for i in d1:
        print(i)  # TypeError: 'Dog' object is not iterable
    

    对象 d1 中没有 __iter__() 所有它是不可迭代对象

    class Dog:
        def __init__(self, age):
            self.age = age
            
        def __iter__(self):
    #         return None   # iter() returned non-iterator of type 'NoneType'
            return self     # 需要返回可迭代类型
        
        def __next__(self):
            if self.age >= 6:
                raise StopIteration('终止迭代~')
            self.age += 1
            return self.age
            
    d1 = Dog(1)
    # print(d1.__next__())      # 2
    # print(d1.__next__())      # 3
    
    for i in d1:    # obj = d1.__iter__()
        print(i)  # obj.__next__()
    
    ----------
    # 2
    # 3
    # 4
    # 5
    # 6
    

    对象 d1 实现 __iter__ 和 __next__ 后变成可迭代对象。

    1.12 迭代器协议实现斐波拉契数列

    class Fib:
        def __init__(self, n):
            self._x = 0
            self._y = 1
            self.n = n
    
        def __iter__(self):
            return self
    
        def __next__(self):
            self._x, self._y = self._y, self._x + self._y
            if self._x > self.n:
                raise StopIteration('终止')
            return self._x
        
    f1 = Fib(13)
    for i in f1:
        print(i)
    
    ------------------
    # 1
    # 1
    # 2
    # 3
    # 5
    # 8
    # 13
    

    1.13 描述符(descriptor)

    描述符就是将某种特殊类型的类的实例指派给另一个类的属性,这两个类必须都是新式类,而且这个特殊类至少实现了 __get__()、__set__()、__delete__() 中的一种。

    描述符的相关魔法方法
    魔法方法 含义
    __get__(self, instance, owner) 调用属性时触发,返回属性值
    __set__(self, instance, value) 为一个属性赋值时触发,不返回任何内容
    __delete__(self, instance) 采用 del 删除属性时触发,不返回任何内容
    • self :描述符类自身的实例 (Descriptor 的实例)
    • instance:描述符的拥有者所在类的实例 (Test 的实例)
    • owner:描述符的拥有者所在的类本身 (Test 本身)
    • value:设置的属性值
    class Descriptor:
        def __get__(self, instance, owner):
            print(self, instance, owner)
            
        def __set__(self, instance, value):
            print(self, instance, value)
            
        def __delete__(self, instance):
            print(self, instance)
            
    class Test:
        x = Descriptor()  # 类 Descriptor 是类 Test 的类属性 x
    

    描述符是用来代理另一个类的类属性,而不能定义到构造函数中去。那么它是在哪里以什么样的条件才能触发呢?
    事实上它只能在它所描述的类中,当其实例访问、修改或删除类属性时才会触发:

    t1 = Test()
    print(t1.x)     # 访问
    
    print(t1.__dict__)
    t1.x = 10       # 修改
    print(t1.__dict__)  # 凡是与被代理的类属性相关的操作,都是找描述符,因此代理类的实例属性字典为空。
    
    print(Test.__dict__)  # 查看 Test 的属性字典
    
    <__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> <class '__main__.Test'>
    None
    {}
    <__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> 10
    {}
    {'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F217B8>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
    

    可以看出实例 t1 在调用、修改属性 x 时,都会自动触发 __get__ 和 __set__ 方法 ,删除也是一样。查看 Test 的属性字典,发现其属性 x 的值是 Descriptor 的实例。

    描述符分两种:

    • 数据描述符: 至少实现了 __get__() 和 __set__()
    • 非数据描述符: 没有实现 __set__()

    Tips:

    • 描述符本身应该是新式类,其代理的类也应是新式类
    • 只能定义在类属性中,不能定义到构造函数中
    • 遵循优先级(由高到低):类属性 —— 数据描述符 —— 实例属性 —— 非数据描述符 —— 找不到的属性触发 __getattr__()
    • 凡是与被代理的类属性相关的操作,都是找描述符,因此代理类的实例属性字典为空。

    1.14 描述符优先级

    类属性 > 数据描述符

    class Descriptor:
        def __get__(self, instance, owner):
            print('get')
            
        def __set__(self, instance, value):
            print('set')
            
        def __delete__(self, instance):
            print('delete')
            
    class Test:
        x = Descriptor()
        
    t1 = Test()
    Test.x		# 调用数据属性
    print(Test.__dict__)     # 'x': <__main__.Descriptor object at 0x0000000004F21EF0>
    Test.x = 1    # 设置类属性
    print(Test.__dict__)    #  'x': 1
    
    get
    {'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F21EF0>, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
    {'__module__': '__main__', 'x': 1, '__dict__': <attribute '__dict__' of 'Test' objects>, '__weakref__': <attribute '__weakref__' of 'Test' objects>, '__doc__': None}
    

    上面例子中,描述符被定义为类属性,因此在调用时,触发了描述符中的 __get__() 方法。而赋值操作,直接将类属性的 x 的值由原来而的描述符赋值为 1,因此没有被触发

    问题:为什么实例赋值操作会触发 __set__() 方法?

    因为实例的属性字典中本身没有属性 x,它只能在类中查找。赋值操作,操作的是自己的属性字典,而不会改变类属性 x。

    类属性 > 数据描述符 > 实例属性

    t1 = Test()
    Test.x = 10000
    t1.x
    

    结果为空,什么都没触发。这是因为类属性 x 被修改,覆盖了数据描述符,因此实例属性也不能触发。

    **实例属性 > 非数据描述符 **

    class Descriptor:
        def __get__(self, instance, owner):
            print('get')
            
    class Test:
        x = Descriptor()
        
    t1 = Test()
    t1.x = 1
    print(t1.__dict__)		# {'x': 1}
    

    非数据描述符,没有 __set__() 方法。因此在修改实例属性时,不会触发 __set__() 方法,而是修改了自己的属性字典。

    非数据描述符 > __getattr__

    class Descriptor:
        def __get__(self, instance, owner):
            print('get')
            
    class Test:
        x = Descriptor()
        def __getattr__(self, item):
            print('触发 getattr')
        
    t1 = Test()
    t1.x = 1	# 调用实例属性
    print(t1.__dict__)
    t1.y		# 调用不存在的实例属性
    
    -------------------
    # {'x': 1}
    # 触发 getattr
    

    从上可以看出,先进行了调用了非数据描述符。再触发 __getattr__。但是因为实例属性大于非数据描述符,因此先看到的是实例的属性字典。

    1.15 描述符应用

    Python 是弱语言类型,在变量定义时不需要指定其类型,类型之间也可以互相轻易转换,但这也会导致程序在运行时类型错误导致整个程序崩溃。Python 内部没有相应的类型判断机制,因此我们有如下需求:

    自定制一个类型检查机制,在用户输入的类型与预期不符时,提示其类型错误。
    现有一个 Dog 类,想要实现用户输入的名字只能是字符串,年龄只能是整型,在输入之前对其进行判断:

    class Descriptor:
        def __init__(self, key, expected_type):
            self.key = key
            self.expected_type = expected_type
            
        def __get__(self, instance, owner):
            print('get')
            return instance.__dict__[self.key]
            
            
        def __set__(self, instance, value):
            print('set')
            print(value)
            if isinstance(value, self.expected_type):
                instance.__dict__[self.key] = value
            else:
                raise TypeError('你输入的%s不是%s' % (self.key, self.expected_type))
            
            
    class Dog:
        name = Descriptor('name', str)
    #     age = Descriptor('age', int)
        def __init__(self, name, age, color):
            self.name = name
            self.age = age
            self.color = color
            
    
    d1 = Dog('rose', 2, 'white')    # 触发 d1.__set__()
    print(d1.__dict__)
    
    d1.name = 'tom'
    print(d1.__dict__)
    
    ---------
    # set
    # rose
    # {'name': 'rose', 'age': 2, 'color': 'white'}
    # set
    # tom
    # {'name': 'tom', 'age': 2, 'color': 'white'}
    

    类 Descriptor 是 Dog 的修饰符(修饰类变量 name、age),当d1 = Dog('rose', 2, 'white'),触发 d1.__set__() 方法,也就会调用对象 d1 的属性字典,进行赋值操作。

    当用户输入的名字不是字符串时:

    d2 = Dog(56, 2, 'white')
    
    ------
    # set
    # 56
    # ---------------------------------------------------------------------------
    # TypeError                                 Traceback (most recent call last)
    # <ipython-input-36-a60d3743b7a0> in <module>()
    #      33 # print(d1.__dict__)
    #      34 
    # ---> 35 d2 = Dog(56, 2, 'white')
    
    # <ipython-input-36-a60d3743b7a0> in __init__(self, name, age, color)
    #      22 #     age = Descriptor('age', int)
    #      23     def __init__(self, name, age, color):
    # ---> 24         self.name = name
    #      25         self.age = age
    #      26         self.color = color
    
    # <ipython-input-36-a60d3743b7a0> in __set__(self, instance, value)
    #      15             instance.__dict__[self.key] = value
    #      16         else:
    # ---> 17             raise TypeError('你输入的%s不是%s' % (self.key, self.expected_type))
    #      18 
    #      19 
    
    # TypeError: 你输入的name不是<class 'str'>
    

    直接报错,提示用户输入的不是字符串,同理 age 也是一样。这样就简单地实现了定义变量之前的类型检查。

    1.16 上下文管理协议

    使用 with 语句操作文件对象,这就是上下文管理协议。要想一个对象兼容 with 语句,那么这个对象的类中必须实现 __enter__ 和 __exit__

    class Foo:
        def __init__(self, name):
            self.name = name
            
        def __enter__(self):
            print('next')
            return self
        
        def __exit__(self,exc_type, exc_val, exc_tb):
            print('触发exit')
    
    with Foo('a.txt') as f:     # 触发 __enter__()
        print(f.name)       # a.txt,打印完毕,执行 __exit__()
    print('------>')
    
    ---------
    # next
    # a.txt
    # 触发exit
    # ------>
    

    with Foo('a.txt') as f 触发 __enter__(),f 为对象本身。执行完毕后触发 __exit__()

    __exit__() 的三个参数

    在没有异常的时候,这三个参数皆为 None:

    class Foo:
        ...
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('触发exit')
            print(exc_type)
            print(exc_val)
            print(exc_tb)
    with Foo('a.txt') as f:
        print(f.name)
    
    ------
    # a.txt
    # 触发exit
    # None
    # None
    # None
    

    有异常的情况下,三个参数分别表示为:

    • ext_type:错误(异常)的类型
    • ext_val:异常变量
    • ext_tb:追溯信息 Traceback

    __exit__() 返回值为 None 或空时:

    class Foo:
        ...
        def __exit__(self, exc_type, exc_val, exc_tb):
            print('触发exit')
            print(exc_type)
            print(exc_val)
            print(exc_tb)
    with Foo('a.txt') as f:
        print(f.name)
        print(dkdkjsjf)     # 打印存在的东西,执行 __exit__(),完了退出整个程序
        print('--------->')     # 不会被执行
    print('123')    # 不会被执行
    
    aa.txt
    触发exit
    <class 'NameError'>
    name 'dkdkjsjf' is not defined
    <traceback object at 0x0000000004E62F08>
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-39-9fe07cc1dbe0> in <module>()
         13 with Foo('a.txt') as f:
         14     print(f.name)
    ---> 15     print(dkdkjsjf)
         16     print('--------->')
         17 print('123')
    
    NameError: name 'dkdkjsjf' is not defined
    

    with 语句运行触发 __enter__() 返回,并获得返回值赋值给 f。遇到异常触发 __exit__(),并将错误信息赋值给它的三个参数,整个程序结束,with 语句中出现异常的语句将不会被执行

    __exit__() 返回值为 True 时:

    class Foo:
        ...
        def __exit__(self, exc_type, exc_val, exc_tb):
        print('触发exit')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True
    with Foo('a.txt') as f:
        print(f.name)
        print(dkdkjsjf)
        print('--------->')
    print('123')
    
    a.txt
    触发exit
    <class 'NameError'>
    name 'dkdkjsjf' is not defined
    <traceback object at 0x0000000004E5AA08>
    123
    

    __exit__() 返回值为 True 时,with 中的异常被捕获,程序不会报错。__exit__() 执行完毕后,整个程序不会直接退出,会继续执行剩余代码,但是 with 语句中异常后的代码不会被执行。

    好处:

    • 把代码放在 with 中执行,with 结束后,自动清理工作,不需要手动干预。
    • 在需要管理一下资源如(文件、网络连接和锁的编程环境中),可以在 __exit__()中定制自动释放资源的机制。

    总结:

    1. with 语句触发 __enter__(),拿到返回值并赋值给 f。
    2. with 语句中无异常时,程序执行完毕触发 __exit__(),其返回值为三个 None。
    3. 有异常情况时,从异常处直接触发 __exit__
      • __exit__ 返回值为 true,那么直接捕获异常,程序不报错 。且继续执行剩余代码,但不执行 with 语句中异常后的代码。
      • __exit__ 返回值不为 True,产生异常,整个程序执行完毕。

    2. 类的装饰器

    Python 中一切皆对象,装饰器同样适用于类:

    无参数装饰器

    def foo(obj):
        print('foo 正在运行~')
        return obj
    
    @foo   # Bar=foo(Bar)
    class Bar:
        pass
    print(Bar())
    
    foo 正在运行~
    <__main__.Bar object at 0x0000000004ED1B70>
    

    有参数装饰器

    利用装饰器给类添加类属性:

    def deco(**kwargs):   # kwargs:{'name':'tom', 'age':1}
        def wrapper(obj):  # Dog、Cat
            for key, val in kwargs.items():
                setattr(obj, key, val)  # 设置类属性
            return obj
        return wrapper
    
    @deco(name='tom', age=1)        # @wrapper ===> Dog=wrapper(Dog)
    class Dog:
        pass
    print(Dog.__dict__)         # 查看属性字典
    print(Dog.name, Dog.age)
    
    @deco(name='john')
    class Cat:
        pass
    print(Cat.__dict__)
    print(Cat.name)
    
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None, 'name': 'tom', 'age': 1}
    tom 1
    {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Cat' objects>, '__weakref__': <attribute '__weakref__' of 'Cat' objects>, '__doc__': None, 'name': 'john'}
    john
    

    @deco(name='tom', age=1) 首先执行 deco(name='tom', age=1),返回 wrapper。再接着执行 @wrapper,相当于 Dog = wrapper(Dog)。最后利用 setattr(obj, key, value) 为类添加属性。

    描述符+装饰器实现类型检查

    class Descriptor:
        def __init__(self, key, expected_type):
            self.key = key     # 'name'
            self.expected_type = expected_type   # str
            
        def __get__(self, instance, owner):    # self:Descriptor对象, instance:People对象,owner:People
            return instance.__dict__[self,key]
        
        def __set__(self, instance, value):
            if isinstance(value, self.expected_type):
                instance.__dict__[self.key] = value
            else:
                raise TypeError('你传入的%s不是%s' % (self.key, self.expected_type))
                
    def deco(**kwargs):    # kwargs:{'name': 'str', 'age': 'int'}
        def wrapper(obj):   # obj:People
            for key, val in kwargs.items():
                setattr(obj, key, Descriptor(key, val))  # key:name、age   val:str、int 
            return obj
        return wrapper
    
    @deco(name=str, age=int)           # @wrapper ==> People = wrapper(People)
    class People:
    #     name = Descriptor('name', str)
        def __init__(self, name, age, color):
            self.name = name
            self.age = age
            self.color = color
    p1 = People('rose', 18, 'white')
    print(p1.__dict__)
    
    p2 = People('rose', '18', 'white')
    
    {'__module__': '__main__', '__init__': <function People.__init__ at 0x00000000051971E0>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None, 'name': <__main__.Descriptor object at 0x00000000051BFDD8>, 'age': <__main__.Descriptor object at 0x00000000051D1518>}
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-18-010cd074c06d> in <module>()
         30 print(People.__dict__)
         31 
    ---> 32 p2 = People('rose', '18', 'white')
    
    <ipython-input-18-010cd074c06d> in __init__(self, name, age, color)
         25     def __init__(self, name, age, color):
         26         self.name = name
    ---> 27         self.age = age
         28         self.color = color
         29 p1 = People('rose', 18, 'white')
    
    <ipython-input-18-010cd074c06d> in __set__(self, instance, value)
         11             instance.__dict__[self.key] = value
         12         else:
    ---> 13             raise TypeError('你传入的%s不是%s' % (self.key, self.expected_type))
         14 
         15 def deco(**kwargs):    # kwargs:{'name': 'str', 'age': 'int'}
    
    TypeError: 你传入的age不是<class 'int'>
    

    在利用 setattr() 设置属性的时候,对 value 进行类型检查。

    3. 自定制 property

    静态属性 property 可以让类或类对象,在调用类函数属性时,类似于调用类数据属性一样。下面我们利用描述符原理来模拟 property。

    把类设置为装饰器,装饰另一个类的函数属性:

    class Foo:
        def __init__(self, func):
            self.func = func
            
    class Bar:
        @Foo      # test = Foo(test)
        def test(self):
            pass
    b1 = Bar()
    print(b1.test)
    
    <__main__.Foo object at 0x00000000051DFEB8>
    
    

    调用 b1.test 返回的是 Foo 的实例对象。

    利用描述符模拟 property

    class Descriptor:        # 1
        """描述符"""
        def __init__(self, func):     # 4    # func: area
            self.func = func  # 5
            
        def __get__(self, instance, owner):   # 13
            """
            调用 Room.area
            self: Descriptor 对象
            instance: Room 对象(即 r1或Room中的 self)
            owner: Room 本身
            """
            res = self.func(instance)  #14 # 实现调用 Room.area,area需要一个位置参数 self,即 r1
            return res      # 17
        
           
    class Room:     # 2
        def __init__(self, name, width, height):   # 7
            self.name = name        # 8
            self.width = width     # 9
            self.height = height   # 10
        
        # area = Descriptor(area),Descriptor 对象赋值给 area,实际是给 Room 增加描述符
        # 设置 Room 的属性字典
        @Descriptor     # 3  
        def area(self):         # 15
            """计算面积"""
            return self.width * self.height   # 16
        
    r1 = Room('卧室', 3, 4)    # 6
    print(Room.__dict__)   # 11
    print(r1.area)      # 12    调用 Descriptor.__get__()
    
    {'__module__': '__main__', '__init__': <function Room.__init__ at 0x0000000005206598>, 'area': <__main__.Descriptor object at 0x000000000520C6A0>, 'test': <property object at 0x00000000051FFDB8>, '__dict__': <attribute '__dict__' of 'Room' objects>, '__weakref__': <attribute '__weakref__' of 'Room' objects>, '__doc__': None}
    12
    

    首先执行 @Descriptor,相当于 area = Descriptor(area),类似于给类 Room 设置 area属性。area 属性又被 Descriptor 代理(描述)。

    所有当执行 r1.area 的时候,触发调用 Descriptor.__get__() 方法,然后执行 area() 函数,并返回结果。

    到目前为止,模拟 property 已经完成了百分之八十。唯一不足的是:property 除了可以使用实例对象调用外,还可以使用类调用。只不过返回的是 property 这个对象:

    class Room:
        ...
        @property
        def test(self):
            return 1
    print(Room.test)
    
    <property object at 0x000000000520FAE8>
    

    那么我们也用类调用看看:

    print(Room.area)
    
    AttributeError: 'NoneType' object has no attribute 'width'
    

    发现类没有 width 这个属性,这是因为我们在使用 __get__() 方法时,其中 instance 参数接收的是类对象,而不是类本身。当使用类去调用时,instance = None

    因此在使用模拟类调用时,需要判断是否 instance 为 None:

    def __get__(self, instance, owner):
        if instance == None:
            return self
            ...
    print(Room.area)
    
    <__main__.Descriptor object at 0x0000000005212128>
    ​````
    发现返回的是 Descriptor 的对象,与 property 的返回结果一样。到此为止,我们使用描述符成功地模拟 property 实现的功能。
    
    **实现延迟计算功能**
    
    要想实现延迟计算功能,只需每计算一次便缓存一次到实例属性字典中即可:
    ​```python
    def __get__(self, instance, owner):
        if instance == None:
            return self
        res = self.func(instance)  
        setattr(instance, self.func.__name__, res)
        return res
        ...
    print(r1.area)  # 首先在自己的属性字典中找,自己属性字典中有 area 属性。因为已经缓存好上一次的结果,所有不需要每次都去计算
    
    

    总结

    • 描述符可以实现大部分 Python 类特性中的底层魔法,包括 @property、@staticmethod、@classmethod,甚至是 __slots__属性。
    • 描述符是很多高级库和框架的重要工具之一,通常是使用到装饰器或元类的大小框架中的一个组件。

    4. 描述符自定制

    4.1 描述符自定制类方法

    类方法 @classmethod 可以实现类调用函数属性。我们也可以描述符模拟出类方法实现的功能:

    class ClassMethod:
        def __init__(self, func):
            self.func = func
            
        def __get__(self, instance, owner):
            def deco(*args, **kwargs):
                return self.func(owner, *args, **kwargs)
            return deco
    
    class Dog:
        name = 'tom'
        @ClassMethod  # eat = ClassMethod(eat) 相当于为 Dog 类设置 eat 类属性,只不过它被 Classmethod 代理
        def eat(cls, age):
            print('那只叫%s的狗,今年%s岁,它正在吃东西' % (cls.name, age))
            
    print(Dog.__dict__)
    Dog.eat(2)     # Dog.eat:调用类属性,触发 __get__,返回 deco。再调用 deco(2)
    
    {'__module__': '__main__', 'name': 'tom', 'eat': <__main__.ClassMethod object at 0x00000000052D1278>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
    那只叫tom的狗,今年2岁,它正在吃东西
    

    类 ClassMethod 被定义为一个描述符,@ClassMethod 相当于 eat = ClassMethod(eat)。因此也相当于 Dog 类设置了类属性 eat,只不过它被 ClassMethod 代理。

    运行 Dog.eat(2),其中 Dog.eat 相当于调用 Dog 的类属性 eat,触发 __get__() 方法,返回 deco 。最后调用 deco(2)

    4.2 描述符自定制静态方法

    静态方法的作用是可以在类中定义一个函数,该函数的参数与类以及类对象无关。下面我们用描述符来模拟实现静态方法:

    class StaticMethod:
        def __init__(self, func):
            self.func = func
            
        def __get__(self, instance, owner):
            def deco(*args, **kwargs):
                return self.func(*args, **kwargs)
            return deco
        
    class Dog:
        @StaticMethod
        def eat(name, color):
            print('那只叫%s的狗,颜色是%s的' % (name, color))
    
    print(Dog.__dict__)
    Dog.eat('tom', 'black')
    
    d1 = Dog()
    d1.eat('lila', 'white')
    print(d1.__dict__)
    
    {'__module__': '__main__', 'eat': <__main__.StaticMethod object at 0x00000000052EF940>, '__dict__': <attribute '__dict__' of 'Dog' objects>, '__weakref__': <attribute '__weakref__' of 'Dog' objects>, '__doc__': None}
    那只叫tom的狗,颜色是black的
    那只叫lila的狗,颜色是white的
    {}
    

    类以及类对象属性字典中,皆没有 name 和 color 参数,利用描述符模拟静态方法成功。

    5. property 用法

    静态属性 property 本质是实现了 get、set、delete 三个方法。

    class A:
        @property
        def test(self):
            print('get运行')
            
        @test.setter
        def test(self, value):
            print('set运行')
            
        @test.deleter
        def test(self):
            print('delete运行')
            
    a1 = A()
    a1.test
    a1.test = 'a'
    del a1.test
    
    
    get运行
    set运行
    delete运行
    

    另一种表现形式:

    class A:
        def get_test(self):
            print('get运行')
            
        def set_test(self, value):
            print('set运行')
            
        def delete_test(self):
            print('delete运行')
            
        test = property(get_test, set_test, delete_test)
        
    a1 = A()
    a1.test
    a1.test = 'a'
    del a1.test
    
    get运行
    set运行
    delete运行
    

    应用

    class Goods:
        def __init__(self):
            self.original_price = 100
            self.discount = 0.8
            
        @property
        def price(self):
            """实际价格 = 原价 * 折扣"""
            new_price = self.original_price * self.discount
            return new_price
        
        @price.setter
        def price(self, value):
            self.original_price = value
            
        @price.deleter
        def price(self):
            del self.original_price
            
    g1 = Goods()
    print(g1.price)		# 获取商品价格
    g1.price = 200		# 修改原价
    print(g1.price)
    del g1.price
    
    80.0
    160.0
    

    6. 元类

    Python 中一切皆对象,对象是由类实例化产生的。那么类应该也有个类去产生它,利用 type() 函数我们可以去查看:

    class A:
        pass
    a1 = A()
    print(type(a1))
    print(type(A))
    
    <class '__main__.A'>
    <class 'type'>
    
    

    由上可知,a1 是类 A 的对象,而 A 是 type 类产生的对象。
    当我们使用 class 关键字的时候,Python 解释器在加载 class 关键字的时候会自动创建一个对象(但是这个对象非类实例产生的对象)。

    6.1 什么是元类

    元类是类的类,也就是类的模板。用于控制创建类,正如类是创建对象的模板一样。

    在 Python 中,type 是一个内建的元类,它可以用来控制生成类。Python 中任何由 class 关键字定义的类都 type 类实例化的对象。

    6.2 定义类的两种方法

    使用 class 关键字定义:

    class Foo:
        pass
    

    使用 type() 函数定义:

    type() 函数有三个参数:第一个为类名(str 格式),第二个为继承的父类(tuple),第三个为属性(dict)。

    x = 2
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def test(self):
        print('Hello %s' % self.name)
    
    Bar = type('Bar', (object,), {'x': 1, '__init__': __init__, 'test': test, 'test1': test1})  # 类属性、函数属性
    print(Bar)
    print(Bar.__dict__)
    
    b1 = Bar('rose', 18)
    b1.test()
    
    <class '__main__.Bar'>
    {'x': 1, '__init__': <function __init__ at 0x00000000055A6048>, 'test': <function test at 0x00000000055A60D0>, 'test1': <function test1 at 0x0000000005596BF8>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None}
    Hello rose
    

    6.3 __init__ 与 __new__

    构造方法包括创建对象和初始化对象,分为两步执行:先执行 __new__,再执行 __init__

    • __new__:在创建对象之前调用,它的任务是创建对象并返回该实例,因此它必须要有返回值,是一个静态方法。
    • __init__:在创建对象完成后被调用,其功能是设置对象属性的一些属性值。

    也就是 __new__创建的对象,传给 __init__ 作为第一个参数(即 self)。

    class Foo:
        def __init__(self):
            print('__init__方法')
            print(self)
            
        def __new__(cls):
            print('__new__方法')
            ret = object.__new__(cls)   # 创建实例对象
            print(ret)
            return ret
    f1 = Foo()
    
    __new__方法
    <__main__.Foo object at 0x00000000055AFF28>
    __init__方法
    <__main__.Foo object at 0x00000000055AFF28>
    

    总结

    • __new__ 至少要有一个参数 cls,代表要实例化的类,由解释器自动提供。必须要有返回值,可以返回父类出来的实例,或直接 object 出来的实例。
    • __init__ 的第一个参数 self 就是 __new__ 的返回值。

    6.4 自定义元类

    如果一个类没有指定元类,那么它的元类就是 type,也可以自己自定义一个元类。

    class MyType(type):
        def __init__(self, a, b, c):    # self:Bar   a:Bar  b:() 父类  c:属性字典
            print(self)   
            print('元类 init 执行~')
            
        def __call__(self, *args, **kwargs):
            print('call 方法执行~')
            obj = object.__new__(self)     # Bar 产生实例对象,即 b1
            print(obj)
            self.__init__(obj, *args, **kwargs)  # Bar.__init__(b1, name)
            return obj
            
    class Bar(metaclass=MyType): # Bar = MyType('Bar', (object,), {}) 触发 MyType 的 __init__() 
        def __init__(self, name):   # self:f1
            self.name = name
            
    b1 = Bar('rose')   # Bar 也是对象,对象加括号,触发 __call__()
    print(b1.__dict__)
    
    <class '__main__.Bar'>
    元类 init 执行~
    call 方法执行~
    <__main__.Bar object at 0x00000000055D3F28>
    {'name': 'rose'}
    

    加载完程序后,首先执行 metaclass=MyType,它相当于 Bar = MyType('Bar', (object,), {}),它会执行 MyType 的 __init__ 执行。

    再接着执行 b1=Bar('rose) ,因为 Bar 也是对象,对象加括号就会触发 __call__() 方法,再由 __new__() 方法产生实例对象,最后返回。

    总结

    • 元类是类的模板,其功能是产生类对象,那么就可以模拟通过 __new__() 产生一个类的对象,并返回。
    • 一切皆对象,那么类也是个对象,它是由 type 产生。
  • 相关阅读:
    MFC Windows 程序设计>WinMain 简单Windows程序 命令行编译
    AT3949 [AGC022D] Shopping 题解
    CF643D Bearish Fanpages 题解
    CF643C Levels and Regions 题解
    CF241E Flights 题解
    CF671C Ultimate Weirdness of an Array 题解
    CF1592F Alice and Recoloring 题解
    GYM 102452E 题解
    CF494C Helping People 题解
    P5556 圣剑护符
  • 原文地址:https://www.cnblogs.com/midworld/p/10301475.html
Copyright © 2011-2022 走看看