面向对象---绑定方法的实现

class A:
    def f(self):
        pass

a1 = A()
a2 = A()

id(a1.f) == id(a2.f)  # True
a1.f is a2.f 	# False

问题1:为什么id相等,却不是同一个对象?

问题2:类的属性在内存中就放一份,为什么不同实例去调用 地址却不一样,难道放了多份?

问题1解答:

a1.fa2.f都是匿名对象,在id(a1.f) 执行完之后,a1.f是没有引用的,应该被当做垃圾回收,但是由于py的缓存机制,在id(a2.f)引用同类型的值时候,会把之前那块缓存的地址拿出来,在cmd测试如下:

>>> class A:
...     def f(self):
...             pass
...
>>> a1=A()
>>> a2=A()
>>> id(a1.f)   
28787416
>>> id(a2.f)
28787416
>>> id(a1.f)
28787416
>>> id(a2.f)
28787416

所以, id(a1.f) == id(a2.f) # True 这里==两遍判断的都不是属于同一时间的两个id,这里是先得出了a1.f的id 然后引用归0,这块内存进入缓存链表,然后获取a2的id的时候,重新拿出缓存链表的这块内存,所以两者的id相等

问题2解答

a1.f is a2.f 返回的是False,这里比较的就是同一时间下两者的内存id,因为在用is判断的时候,是拿的这两个参数,都存在引用,所以得出结论,不同实例的绑定方法在内存中并不相同

用类名.方法 和 实例.方法 得到的一个是普通函数,一个是绑定方法

>>> A.f
<function A.f at 0x0201C660>
>>> a1.f
<bound method A.f of <__main__.A object at 0x02018950>>

在底层中是如何实现的?

描述符

实例a1.f 如果a1的实例属性没有f,在调用a1.f的时候是访问的 非数据描述符的__get__

也就是为什么在手动赋值 a1.f = '123'后, a1.f 就不会去访问绑定方法了,但是a2.f还是可以访问

绑定方法,因为a2的实例属性没有f

具体的 实例.属性的访问顺序 :如果实例字典中有与描述符同名的属性,如果描述符是数据描述符,优先使用数据描述符,如果是非数据描述符,优先使用字典中的属性。

1、函数就是一个非数据描述符

class A:
	def f(self):
        pass
    
# 相当于 f = Function()

测试一下:
>>> type(A.f)
<class 'function'>
>>> type(A.f).__dict__.keys()
dict_keys(['__repr__', '__call__', '__get__'......)
           #这里有__get__  没有__set__  __delete__ 所以是一个非数据描述符

2、函数这个描述符 是如何把 实例.函数 转换成绑定方法的呢

查看c源码"Simulate func_descr_get() in Objects/funcobject.c"

相同逻辑用python表达出来就是:

class Function:
    ...
    def __get__(self, obj, objtype=None):
        #类中的函数属于类属性,在调用这个类属性时,如果不是实例调用,则直接返回这个函数
        # 这就是为什么 A.f  是普通函数
        if obj is None:
            return self
        #如果obj不是None,也就是实例调用,则返回 Method(函数,实例对象)
        return Method(self,obj)

其中Method 类 部分逻辑如下:

class MethodType:
    
    def __init__(self, func, obj):
        self.__func__ = func  # 类中定义的函数  也就是f
        self.__self__ = obj	  # 调用这个函数的 类实例对象  也就是a
    def __call__(self, *args, **kwargs):
        func = self.__func__  
        obj = self.__self__
        # 调用类中定义的函数,同时自动把实例a作为第一个参数传递进去
        return func(obj, *args, **kwargs) 
    

所以 实例.方法 得到的是一个绑定方法, 在a.f加上括号 a.f() 时候,就调用Method的__call__,自动把实例a作为第一个参数传递进去了,然后如果f是带参数的,就只用传递对应参数了

如上,这也就是为什么 【绑定方法】 会自动把调用的实例传递到第一个参数self的底层逻辑

测试验证一下:

>>> dir(a1.f) # 部分如下
['__call__', '__class__', '__format__', '__func__', '__ge__', '__get__',  '__self__'......]

#查看绑定方法的所属类,所以绑定方法就是类Method的一个实例
>>> a1.f.__class__
<class 'method'>
#验证上述Method类中的逻辑,这里的__self__显然就是 实例对象a
>>> a1.f.__self__
<__main__.A object at 0x02018950>
# 这里的__func__就是类中唯一存放的那个函数f,验证A.f  是一块函数
>>> a1.f.__func__
<function A.f at 0x0201C660>
>>> A.f
<function A.f at 0x0201C660>

回到问题2,a1.fa2.f相当于Method类的两个实例,不同实例肯定id不一样

posted @ 2021-10-27 14:09  Alantammm  阅读(50)  评论(0编辑  收藏  举报