• 面向对象进阶


    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 产生。
  • 相关阅读:
    镜像(Image)、容器(Container)
    奇安信处理多起服务器挖矿应急事件
    英特尔® Threat Detection Technology——这玩意检测无文件攻击、挖矿、勒索就是神器啊
    Necro挖矿使用了使用Tor+动态域名DGA
    “挖矿”恶意代码肆虐,安天智甲有效防护——属于异常检测
    深信服挖矿检测——EDR没有检测能力?
    活跃的H2Miner组织挖矿分析,含样本本地复现【EDR 经典case】
    CNCERT:2021年恶意挖矿威胁趋势分析报告
    vscode ${workspaceFolder} can not be resolved
    Streamlit 简介
  • 原文地址:https://www.cnblogs.com/midworld/p/10301475.html
Copyright © 2020-2023  润新知