装饰器:装饰器就是一个函数,它可以接受一个函数作为输入并返回一个新的函数作为输出。
描述符:就是以特殊方法__get__()、__set__()、__delete__()的形式实现了三个核心的属性访问操作(对应于get、set和delete)的类。这些方法通过接受类实例作为输入来工作。之后,底层的实例字典会根据需要适当的进行调整。
class Integer:
def __init__(self, name):
self.name = name
print('Integer __init__')
def __get__(self, instance, cls):
print('Integer __get__')
if instance is None:
return self
else:
return instance.__dict__[self.name]
def __set__(self, instance, value):
print('Integer __set__')
if not isinstance(value, int):
raise TypeError('Expceted an int')
instance.__dict__[self.name] = value
def __delete__(self, instance):
print('Integer __delete__')
del instance.__dict__[self.name]
class Point:
print('Point')
x = Integer('x')
y = Integer('y')
def __init__(self, x, y):
print('28')
self.x = x
self.y = y
print('Point __init__')
print(self.__dict__)
p = Point(2, 3)
print('34')
print(p.x)
p.y = 5
# p.x = 2.3
Point
Integer __init__
Integer __init__
28
Integer __set__
Integer __set__
Point __init__
{'x': 2, 'y': 3}
34
Integer __get__
2
Integer __set__
p.x = 2.3
Traceback (most recent call last):
Point
File "C:/Users/***/PycharmProjects/study_test/cookbook_8.9.2.py", line 39, in <module>
p.x = 2.3
File "C:/Users/***/PycharmProjects/study_test/cookbook_8.9.2.py", line 16, in __set__
raise TypeError('Expceted an int')
TypeError: Expceted an int
每个描述符方法都会接受被操纵的实例作为输入。要执行所请求的操作,底层的实例字典(即__dict__属性)会根据需要适当地进行调整。描述符的self.name属性会保存字典的键,通过这些键可以找到存储在实例字典中的实际数据。
关于描述符,常容易困惑的地方就是它们只能再类的层次上定义,不能根据实例来产生。因此,下面的代码是无法正常工作的:
class Point:
def __init__(self, x, y):
self.x = Integer('x')
self.y = Integer('y')
self.x = x
self.y = y
此外,在实现__get__()方法时比想象中的还要复杂一些:
__get__()看起来复杂的原因在于实例变量(instance)和类变量(cls)之间是有区别的。如果是以类变量的形式返回描述符,参数instance应该设为None。在这种情况下,标准的做法就是简单的返回描述符实例本身(尽管此时做任何类型的自定义处理也是允许的)。实例如下:
print(Point.x)
<__main__.Integer object at 0x0000026BB2EAE7B8>
下面是一些更加高级的基于描述符的代码:
class Type:
def __init__(self, name, expected_type):
self.name = name
self.expected_type = expected_type
print('Type __init__')
def __get__(self, instance, cls):
print('Type __get__')
if instance is None:
return self
else:
return instance._dict__[self.name]
def __set__(self, instance, value):
print('Type __set__')
if not isinstance(value, self.expected_type):
raise TypeError('Expected ' + str(self.expected_type))
instance.__dict__[self.name] = value
def __delete__(self, instance):
print('Type __delete__')
del instance.__dict__[self.name]
def typeassert(**kwargs):
def decorate(cls):
for name, expected_type in kwargs.items():
print('typeassert decorate setattr')
print(name, expected_type)
print(cls)
setattr(cls, name, Type(name, expected_type))
return cls
return decorate
@typeassert(name=str, shares=int, price=float)
class Stock:
def __init__(self, name, shares, price):
print('Stock __init__ before')
self.name = name
self.shares = shares
self.price = price
print('Stock __init__ later')
s = Stock('s', 2, 3.5)
print(s)
print(Stock)
输出:
typeassert decorate setattr
name <class 'str'>
<class '__main__.Stock'>
Type __init__
typeassert decorate setattr
shares <class 'int'>
<class '__main__.Stock'>
Type __init__
typeassert decorate setattr
price <class 'float'>
<class '__main__.Stock'>
Type __init__
Stock __init__ before
Type __set__
Type __set__
Type __set__
Stock __init__ later
<__main__.Stock object at 0x0000011DAD4782E8>
<class '__main__.Stock'>
Python setattr() 函数对应函数 getattr(),用于设置属性值,该属性不一定是存在的。
setattr(object, name, value)