Descriptor描述器
python中的descriptor表示一类对象,如果定义了 __getters__()
, __setters__()
, __delete__()
的任意一个对象则可称之为descriptor, 基本上就是实现了一个对象的储存系统
三种实现descriptor protocal的方式
1. 自定义Descriptor class
类似django Model -> Field的写法,Model的每个字段为一个Descriptor的实例化,Descriptor则为Field
class Descriptor(object):
def __init__(self, value=""):
self.value = value
# 注意这里的value只是实现了一个储存器
# 在获取和修改时对self(当前对象)的属性进行操作
def __get__(self, obj, objtype):
"""在此例中
self为实例化描述符的本身即Descriptor()
obj为描述符所属的对象, MyClass()——实例化的MyClass对象
objtype则时MyClass——对象的class
"""
return "{}for{}".format(self.value, self.value)
def __set__(self, obj, value):
if isinstance(value, str):
self.value = value
else:
raise TypeError("Name should be string")
class MyClass(object):
name = Descriptor()
notes = Descriptor()
>>> obj = MyClass()
>>> print(obj.name)
for
>>> obj.notes = "test"
>>> print(obj.notes)
testfortest
2. 使用property函数
class MyClass(object):
def __init__(self, value):
self._value = value
def get_value(self):
return self._value
def set_value(self, value):
self._value = value
def del_value(self):
del self._value
value = property(get_value, set_value, del_value, "doc_string")
3. 使用@property装饰器
class MyClass(object):
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
@value.setter
def value(self, value):
self._value = value
@value.deleter
def value(self):
del self._value
function和bound method
python3.0以后没有unbound method这一说,仅有function和bound method, python2与之对应的为以下:
python3:
vs python2:
不同类别的函数的详细情况:
- @classmethod 【bound method】
利用method-wrapper将类的本身cls作为函数第一个参数传入(method-wrapper在文章下一节会详细介绍) - instance method 【bound method】
利用method-wrapper将实例的本身self作为函数的第一个参数传入 - @staticmethod 【function】
- @property 【property —— 一种特殊的bound method?】
class MyClass:
@classmethod
def method1(cls):
return None
def method2(self):
return None
@staticmethod
def method3():
return None
@property
def p(self):
return 1
# 类方法
>>> MyClass.method1, MyClass().method1
(>,
>)
# 实例方法
>>> MyClass.method2, MyClass().method2
(,
>)
# 实例方法另外一种使用方法,使用descriptor __get__方法将实例对象传入
# 把function 转化为bound method
>>> MyClass.method2.__get__(MyClass())
>
# 静态方法
>> MyClass.method3, MyClass().method3
(,
)
slot wrapper 和 method-wrapper
与function和bound method类似, slot wrapper和method-wrapper分别为CPython中对于function/bound method的实现,底层实现为C语言。
两者均为callable,其中slot wrapper有descriptor的实现(i.e.实现了__get__
)。
python3官方包里的types.MethodType(func, obj)
也实现了descriptor, 可以利用它来动态替换实例的方法(不建议这样使用):
"""
my_class_obj = MyClass() 时
types.MethodType(MyClass.method2, my_class_obj) 与
my_class_obj.method2 是等价的
"""
# instance-level method patch example
class MyClass:
def __init__(self, patch=False):
def method2_patch(self):
return 1
if patch:
self.method2 = types.MethodType(method2_patch, self)
def method2(self):
return None
Tips: 所有method-wrapper/bound method都有__self__
属性,表示它被绑定的cls
/self
是什么,可以用于调试,在method-wrapper/bound method实际被调用时,会从__self__
属性获取对象作为第一个需要传入的参数cls
/self
可以使用object.__getattribute__
获取一个slot wrapper,再使用slot wrapper的__get__
方法将cls/self传入以获取cls/self的method-wrapper
General Example:
# 获取slot wrapper
sw = object.__getattribute__
# 然后可以使用descriptor protocal中的__get__(self, obj, objtype=None)
# 将类cls/实例self传入以获取method-wrapper <-> bound method
sw.__get__(object())
sw.__get__(object)
Specific Example for MyClass:
# 一、实例函数由slot wrapper -> method-wrappert -> descriptor protocal
# 直到获取到bound method(实例函数)的方式:
>>> sw = object.__getattribute__
>>> sw
>>> sw.__get__(MyClass)
# MyClass默认由type创建
# 所以是type object, 实际上是一个type object,同时应该也是 class MyClass
# 只是这里__repr__展示的是创建它的objtype的object
# 注: objtype为__get__的最后一个参数
>>> sw.__get__(MyClass)("method2")
# 这里获取到的是MyClass的实例函数(没有被bound的, 属于function),与以下等价
>>> MyClass.method2
# 接着对funtion进行bound处理获取到bound method
>>> my_class_obj = MyClass()
>>> sw.__get__(MyClass)("method2").__get__(my_class_obj)
>
>>> MyClass.method2.__get__(my_class_obj)
>
# 二、类函数:
>>> object.__getattribute__.__get__(MyClass)("method1").__get__(MyClass())
>
>>> MyClass.method1
>
小结
以下的调用方式中 同一个行里面的本质是差不多一样的
# 1. 类方法
MyClass.method1, MyClass().method1
# 2. 实例方法
MyClass().method2, MyClass.method2.__get__(MyClass()), types.MethodType(MyClass.method2, MyClass())
MyClass.method2, object.__getattribute__.__get__(MyClass)("method2")
# 3. 静态方法
MyClass.method3, MyClass().method3
MyClass.__dict__["method3"], object.__getattribute__.__get__(MyClass)("method3")
Keywords
python, descriptor, function, bound method, slot wrapper, method-wrapper, staticmethod, classmethod, property, dynamic method patching, types.MethodType
参考
https://www.geeksforgeeks.org/descriptor-in-python/
Bound/unbound method
StackOverflow Python method-wrapper type?
TODO: Descriptor How To Guide
https://docs.python.org/3/howto/descriptor.html