2020-12-10 Python Descriptor & Bound method

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

你可能感兴趣的:(2020-12-10 Python Descriptor & Bound method)