共计 7833 个字符,预计需要花费 20 分钟才能阅读完成。
引入形容器
以 stackoverflow 上对于形容器(descriptor)的疑难开篇。
class Celsius:
def __get__(self, instance, owner):
return 5 * (instance.fahrenheit - 32) / 9
def __set__(self, instance, value):
instance.fahrenheit = 32 + 9 * value / 5
class Temperature:
celsius = Celsius()
def __init__(self, initial_f):
self.fahrenheit = initial_f
t = Temperature(212)
print(t.celsius) # 输入 100.0
t.celsius = 0
print(t.fahrenheit) # 输入 32.0
以上代码实现了温度的摄氏温度和华氏温度之间的主动转换。其中 Temperature 类含有实例变量 fahrenheit 和类变量 celsius,celsius 由形容器 Celsius 进行代理。由这段代码引出的三点疑难:
- 疑难一:什么是形容器?
- 疑难二:
__get__
,__set__
,__delete__
三种办法的参数 - 疑难三:形容器有哪些利用场景
- 疑难四:property 和形容器的区别是什么?
<!–more–>
疑难一:什么是形容器?
形容器是一个 实现了 __get__
、__set__
和 __delete__
中 1 个或多个办法的类对象。当一个类变量指向这样的一个装璜器的时候,拜访这个类变量会调用 __get__
办法,对这个类变量赋值会调用__set__
办法,这品种变量就叫做形容器。
形容器 事实上是一种代理机制:当一个 类变量 被定义为形容器,对这个类变量的操作,将由此形容器来代理。
疑难二:形容器三种办法的参数
class descriptor:
def __get__(self, instance, owner):
print(instance)
print(owner)
return 'desc'
def __set__(self, instance, value):
print(instance)
print(value)
def __delete__(self, instance):
print(instance)
class A:
a = descriptor()
del A().a # 输入 <__main__.A object at 0x7f3fc867cbe0>
A().a # 返回 desc,输入 <__main__.A object at 0x7f3fc86741d0>,<class '__main__.A'>
A.a # 返回 desc,输入 None,<class '__main__.A'>
A().a = 5 # 输入 <__main__.A object at 0x7f3fc86744a8>,5
A.a = 5 # 间接批改类 A 的类变量,也就是 a 不再由 descriptor 形容器进行代理。
由以上输入后果能够得出结论:
参数解释
__get__(self, instance, owner)
instance 示意以后实例 owner 示意类自身, 应用类拜访的时候,instance 为 None__set__(self, instance, value)
instance 示意以后实例, value 右值,只有实例才会调用__set__
__delete__(self, instance)
instance 示意以后实例
三种办法的实质
- 拜访:
instance.descriptor
理论是调用了descriptor.__get__(self, instance, owner)
办法,并且须要返回一个 value - 赋值:
instance.descriptor = value
理论是调用了descriptor.__set__(self, instance, value)
办法,返回值为 None。 - 删除:
del instance.descriptor
理论是调用了descriptor.__delete__(self, obj_instance)
办法,返回值为 None
疑难三:形容器有哪些利用场景
咱们想创立一种新模式的实例属性,除了批改、拜访之外还有一些额定的性能,例如 类型查看、数值校验等,就须要用到形容器《Python Cookbook》
即形容器次要用来接管对实例变量的操作。
实现 classmethod 装璜器
from functools import partial
from functools import wraps
class Classmethod():
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
return wraps(self.fn)(partial(self.fn, owner))
将办法 fn 的第一个参数固定成实例的类。可参考 python 官网文档的另一种写法:descriptor
class ClassMethod(object):
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner=None):
if owner is None:
owner = type(obj)
def newfunc(*args):
return self.f(owner, *args)
return newfunc
实现 staticmethod 装璜器
class Staticmethod:
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, cls):
return self.fn
实现 property 装璜器
class Property:
def __init__(self, fget, fset=None, fdel=None, doc=''):
self.fget = fget
self.fset = fset
self.fdel = fdel
self.doc = doc
def __get__(self, instance ,owner):
if instance is not None:
return self.fget(instance)
return self
def __set__(self, instance, value):
if not callable(self.fset):
raise AttibuteError('cannot set')
self.fset(instance, value)
def __delete__(self, instance):
if not callable(self.fdel):
raise AttributeError('cannot delete')
self.fdel(instance)
def setter(self, fset):
self.fset = fset
return self
def deleter(self, fdel):
self.fdel = fdel
return self
应用自定义的 Property 来形容 farenheit 和 celsius 类变量:
class Temperature:
def __init__(self, cTemp):
self.cTemp = cTemp # 有一个实例变量 cTemp:celsius temperature
def fget(self):
return self.celsius * 9 /5 +32
def fset(self, value):
self.celsius = (float(value) -32) * 5 /9
def fdel(self):
print('Farenhei cannot delete')
farenheit = Property(fget, fset, fdel, doc='Farenheit temperature')
def cget(self):
return self.cTemp
def cset(self, value):
self.cTemp = float(value)
def cdel(self):
print('Celsius cannot delete')
celsius = Property(cget, cset, cdel, doc='Celsius temperature')
应用后果:
t = Temperature(0)
t.celsius # 返回 0.0
del t.celsius # 输入 Celsius cannot delete
t.celsius = 5
t.farenheit # 返回 41.0
t.farenheit = 212
t.celsius # 返回 100.0
del t.farenheit # 输入 Farenhei cannot delete
应用装璜器的形式来装璜 Temperature 的两个属性 farenheit 和 celsius:
class Temperature:
def __init__(self, cTemp):
self.cTemp = cTemp
@Property # celsius = Property(celsius)
def celsius(self):
return self.cTemp
@celsius.setter
def celsius(self, value):
self.cTemp = value
@celsius.deleter
def celsius(self):
print('Celsius cannot delete')
@Property # farenheit = Property(farenheit)
def farenheit(self):
return self.celsius * 9 /5 +32
@farenheit.setter
def farenheit(self, value):
self.celsius = (float(value) -32) * 5 /9
@farenheit.deleter
def farenheit(self):
print('Farenheit cannot delete')
应用后果同间接用形容器形容类变量
实现属性的类型查看
首先实现一个类型查看的形容器 Typed
class Typed:
def __init__(self, name, expected_type):
# 每个属性都有一个名称和对应的类型
self.name = name
self.expected_type = expected_type
def __get__(self, instance, cls):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance ,value):
if not isinstance(value, self.expected_type):
raise TypeError('Attribute {} expected {}'.format(self.name, self.expected_type))
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
而后实现一个 Person 类,Person 类的属性 name 和 age 都由 Typed 来形容
class Person:
name = Typed('name', str)
age = Typed('age', int)
def __init__(self, name: str, age: int):
self.name = name
self.age = age
类型查看过程:
>>> Person.__dict__
mappingproxy({'__dict__': <attribute '__dict__' of 'Person' objects>,
'__doc__': None,
'__init__': <function __main__.Person.__init__>,
'__module__': '__main__',
'__weakref__': <attribute '__weakref__' of 'Person' objects>,
'age': <__main__.Typed at 0x7fe2f440bd68>,
'name': <__main__.Typed at 0x7fe2f440bc88>})
>>> p = Person('suncle', 18)
>>> p.__dict__
{'age': 18, 'name': 'suncle'}
>>> p = Person(18, 'suncle')
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-88-ca4808b23f89> in <module>()
----> 1 p = Person(18, 'suncle')
<ipython-input-84-f876ec954895> in __init__(self, name, age)
4
5 def __init__(self, name: str, age: int):
----> 6 self.name = name
7 self.age = age
<ipython-input-83-ac59ba73c709> in __set__(self, instance, value)
11 def __set__(self, instance ,value):
12 if not isinstance(value, self.expected_type):
---> 13 raise TypeError('Attribute {} expected {}'.format(self.name, self.expected_type))
14 instance.__dict__[self.name] = value
15
TypeError: Attribute name expected <class 'str'>
然而上述类型查看的办法存在一些问题,Person 类可能有很多属性,那么每一个属性都须要应用 Typed 形容器形容一次。咱们能够写一个带参数的类装璜器来解决这个问题:
def typeassert(**kwargs):
def wrap(cls):
for name, expected_type in kwargs.items():
setattr(cls, name, Typed(name, expected_type)) # 经典写法
return cls
return wrap
而后应用 typeassert 类装璜器从新定义 Person 类:
@typeassert(name=str, age=int)
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
能够看到 typeassert 类装璜器的参数是传入的属性名称和类型的键值对。
如果咱们想让 typeassert 类装璜器主动的辨认类的初始化参数类型,并且减少相应的类变量的时候,咱们就能够借助 inspect 库和 python 的类型注解实现了:
import inspect
def typeassert(cls):
params = inspect.signature(cls).parameters
for name, param in params.items():
if param.annotation != inspect._empty:
setattr(cls, name, Typed(name, param.annotation))
return cls
@typeassert
class Person:
def __init__(self, name: str, age: int): # 没有类型注解的参数不会被托管
self.name = name
self.age = age
疑难四:property 和形容器的区别
咱们能够利用 Python 的外部机制获取和设置属性值。总共有三种办法:
- Getters 和 Setter。咱们能够应用办法来封装每个实例变量,获取和设置该实例变量的值。为了确保实例变量不被内部拜访,能够把这些实例变量定义为公有的。所以,拜访对象的属性须要通过显式函数:anObject.setPrice(someValue); anObject.getValue()。
- property。咱们能够应用内置的 property 函数将 getter,setter(和 deleter)函数与属性名绑定。因而,对属性的援用看起来就像间接拜访那么简略,然而实质上是调用对象的相应函数。例如,anObject.price = someValue; anObject.value。
- 形容器。咱们能够将 getter,setter(和 deleter)函数绑定到一个独自的类中。而后,咱们将该类的对象调配给属性名称。这时候对每个属性的援用也像间接拜访一样,然而实质上是调用这个形容器对象相应的办法,例如,anObject.price = someValue; anObject.value。
Getter 和 Setter 这种设计模式不够 Pythonic,尽管在 C ++ 和 JAVA 中很常见,然而 Python 谋求的是简介,谋求的是可能间接拜访。
附 1、data-descriptor and no-data descriptor
翻译为中文其实就是材料形容器和非材料形容器
- data-descriptor:同时实现了
__get__
和__set__
办法的形容器 - no-data descriptor:只实现了
__get__
办法的形容器
两者的区别在于:
- no-data descriptor 的优先级低于
instance.__dict__
class Int:
def __get__(self, instance, cls):
return 3
class A:
val = Int()
def __init__(self):
self.__dict__['val'] = 5
A().val # 返回 5
- data descriptor 的优先级高于
instance.__dict__
class Int:
def __get__(self, instance, cls):
return 3
def __set__(self, instance, value):
pass
class A:
val = Int()
def __init__(self):
self.__dict__['val'] = 5
A().val # 返回 3
附 2、形容器机制剖析材料:
- 官网文档 -descriptor
- understanding-get-and-set-and-python-descriptors
- anyisalin – Python – 形容器
- Python 形容器疏导(翻译)
- Properties and Descriptors
记得帮我点赞哦!
精心整顿了计算机各个方向的从入门、进阶、实战的视频课程和电子书,依照目录正当分类,总能找到你须要的学习材料,还在等什么?快去关注下载吧!!!
朝思暮想,必有回响,小伙伴们帮我点个赞吧,非常感谢。
我是职场亮哥,YY 高级软件工程师、四年工作教训,回绝咸鱼争当龙头的斜杠程序员。
听我说,提高多,程序人生一把梭
如果有幸能帮到你,请帮我点个【赞】,给个关注,如果能顺带评论给个激励,将不胜感激。
职场亮哥文章列表:更多文章
自己所有文章、答复都与版权保护平台有单干,著作权归职场亮哥所有,未经受权,转载必究!