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触发exitname '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触发exitname 'dkdkjsjf' is not defined 123
当 __exit__()
返回值为 True 时,with 中的异常被捕获,程序不会报错。__exit__()
执行完毕后,整个程序不会直接退出,会继续执行剩余代码,但是 with 语句中异常后的代码不会被执行。
好处:
- 把代码放在 with 中执行,with 结束后,自动清理工作,不需要手动干预。
- 在需要管理一下资源如(文件、网络连接和锁的编程环境中),可以在
__exit__()
中定制自动释放资源的机制。
总结:
- with 语句触发
__enter__()
,拿到返回值并赋值给 f。 - with 语句中无异常时,程序执行完毕触发
__exit__()
,其返回值为三个 None。 - 有异常情况时,从异常处直接触发
__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 产生。