博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
面向对象进阶
阅读量:4939 次
发布时间:2019-06-11

本文共 26686 字,大约阅读时间需要 88 分钟。

1. 类的其他内置函数

1.1 isinstance 和 issubclass

1. isinstance(obj, cls)

判断第一个参数是否是第二个参数的实例对象,返回布尔值。第一个参数为对象,第二个参数为类。

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

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

2. issubclass(cls, class_or_tuple))

判断第一个参数是否是第二个参数的子类,返回布尔值。

class Animal:    passclass Dog(Animal):    passprint(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    # 触发 getattributea1.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__)     # {}# seta1['name'] = 'rose'     # __setitem__print(a1.__dict__)     # {'name': 'rose'}# getprint(a1['name'])   # __getitem__   rose# deldel 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:    passa1 = 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      返回 reprprint(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-28print(format(d1, 'ymd'))    # 2018-11-28print(format(d1, 'mdy'))    # 11-28-2018print(format(d1))   # 当为空时,输出默认格式 ymd

format 实际调用的是 __format__()

1.6 __slots__

__slots__ 实质是一个类变量,它可以是字符串、列表、元组也可以是个可迭代对象。

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

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

Tips:

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

1.7 __doc__

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

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

1.8 __module__ 和 __class__

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

# a.pyclass Cat:    name = 'tom'    pass
# b.pyfrom a import Catc1 = Cat()print(c1.__module__)    # aprint(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__())      # 3for 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> 
None{}<__main__.Descriptor object at 0x0000000004F217B8> <__main__.Test object at 0x0000000004F217F0> 10{}{'__module__': '__main__', 'x': <__main__.Descriptor object at 0x0000000004F217B8>, '__dict__':
, '__weakref__':
, '__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__': 
, '__weakref__':
, '__doc__': None}{'__module__': '__main__', 'x': 1, '__dict__':
, '__weakref__':
, '__doc__': None}

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

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

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

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

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

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

实例属性 > 非数据描述符

class Descriptor:    def __get__(self, instance, owner):        print('get')        class Test:    x = Descriptor()    t1 = Test()t1.x = 1print(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)# 
in
()# 33 # print(d1.__dict__)# 34 # ---> 35 d2 = Dog(56, 2, 'white')#
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#
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不是

直接报错,提示用户输入的不是字符串,同理 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
name 'dkdkjsjf' is not defined
---------------------------------------------------------------------------NameError Traceback (most recent call last)
in
() 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 Truewith Foo('a.txt') as f:    print(f.name)    print(dkdkjsjf)    print('--------->')print('123')
a.txt触发exit
name 'dkdkjsjf' is not defined
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:    passprint(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:    passprint(Dog.__dict__)         # 查看属性字典print(Dog.name, Dog.age)@deco(name='john')class Cat:    passprint(Cat.__dict__)print(Cat.name)
{'__module__': '__main__', '__dict__': 
, '__weakref__':
, '__doc__': None, 'name': 'tom', 'age': 1}tom 1{'__module__': '__main__', '__dict__':
, '__weakref__':
, '__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 = colorp1 = People('rose', 18, 'white')print(p1.__dict__)p2 = People('rose', '18', 'white')
{'__module__': '__main__', '__init__': 
, '__dict__':
, '__weakref__':
, '__doc__': None, 'name': <__main__.Descriptor object at 0x00000000051BFDD8>, 'age': <__main__.Descriptor object at 0x00000000051D1518>}---------------------------------------------------------------------------TypeError Traceback (most recent call last)
in
() 30 print(People.__dict__) 31 ---> 32 p2 = People('rose', '18', 'white')
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')
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不是

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

3. 自定制 property

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

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

class Foo:    def __init__(self, func):        self.func = func        class Bar:    @Foo      # test = Foo(test)    def test(self):        passb1 = 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)    # 6print(Room.__dict__)   # 11print(r1.area)      # 12    调用 Descriptor.__get__()
{'__module__': '__main__', '__init__': 
, 'area': <__main__.Descriptor object at 0x000000000520C6A0>, 'test':
, '__dict__':
, '__weakref__':
, '__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 1print(Room.test)

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

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 实现的功能。**实现延迟计算功能**要想实现延迟计算功能,只需每计算一次便缓存一次到实例属性字典中即可:​```pythondef __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 decoclass 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__': 
, '__weakref__':
, '__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__': 
, '__weakref__':
, '__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.testa1.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.testa1.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.0160.0

6. 元类

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

class A:    passa1 = A()print(type(a1))print(type(A))

由上可知,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 = 2def __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()
{'x': 1, '__init__':
, 'test':
, 'test1':
, '__module__': '__main__', '__dict__':
, '__weakref__':
, '__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 retf1 = 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__)
元类 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 产生。

转载于:https://www.cnblogs.com/midworld/p/10301475.html

你可能感兴趣的文章
window的对象有哪些(笔记)
查看>>
Boolean Expressions
查看>>
They Are Everywhere
查看>>
数据结构--汉诺塔递归Java实现
查看>>
day14 多态与抽象
查看>>
Eclipse CDT 出现 launch failed Binary not found
查看>>
apache jmeter
查看>>
Linux 基本命令
查看>>
RedHat7.0 网络源的配置
查看>>
(Mark)JS中关于闭包
查看>>
流程结构图
查看>>
ios端web app在键盘升起后缩小view防止界面仍可上下滑动
查看>>
从service弹出系统级自定义提示框,可在任意页面弹出
查看>>
Bootstrap简单介绍
查看>>
iOS Touch ID 身份认证
查看>>
springboot 注解笔记
查看>>
图解HTTP---------------------------------------4
查看>>
hibernate实体类配置文件问题(字段使用默认值)
查看>>
rsync+inotify脚本
查看>>
LeetCode 860.柠檬水找零(C++)
查看>>