__get__实现python方法与实例的绑定

683 阅读2分钟

问题来源

在使用peft进行LoRA调优的过程中,看见一行代码,让人摸不着头脑

model.state_dict = (
    lambda self, *_, **__: get_peft_model_state_dict(self, old_state_dict())
).__get__(model, type(model))

结论

这行将lambda方法绑定给实例model,方法名为state_dict,它的类型为method

通常比较直观的给实例绑定函数是通过MethodType__get__这个方法无法定位到源码,没接触过很难想到它是什么意思。

下面给出了两种给实例绑定方法的代码

from types import MethodType


class A:
    pass


def func(self, *_, **__):
    return 10


a = A()

a.f1 = MethodType(func, a)
a.f2 = (
    lambda self, *_, **__: 20
).__get__(a, type(a))

res = a.f1()
print(res)
res = a.f2()
print(res)
10
20

如何理解__get__()

首先说明与上述示例无关的用法

官方文档中提到

object.__get__(*self*, *instance*, *owner=None*)

Called to get the attribute of the owner class (class attribute access) or of an instance of that class (instance attribute access). The optional *owner* argument is the owner class, while *instance* is the instance that the attribute was accessed through, or `None` when the attribute is accessed through the *owner*.

This method should return the computed attribute value or raise an AttributeError exception.
调用以获取所有者类(类属性访问)或该类实例(实例属性访问)的属性。可选参数owner:类名,instance参数:可以通过owner获取属性时传None,需要通过实例获取属性时传实例(即看你需要获取的是实例的属性还是类的属性)。

这个方法的返回值是计算好的属性值,或抛出属性错误的异常。

可以看到这是一般object__get__实现的功能。但是调用者是Function类呢(虽然python一切都是类,但是继承过程中可能会重写)?

接下来是与示例有关的用法

官方文档中提到

To support automatic creation of methods, functions include the `__get__()` method for binding methods during attribute access. This means that functions are non-data descriptors that return bound methods during dotted lookup from an instance. Here’s how it works:
class Function:
    ...

    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:
            return self
        return MethodType(self, obj)

可以看到,在实现上,func.__get__(a, type(a))等价于 MethodType(func, a)

其实就是Function类重写了__get__这个descriptors,让它返回是调用者(Function实例)的实例方法。

附: func本身是类型是<function>func.__get__(a, type(a))之后,类型是<bound method>,意思应该是实例方法。