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.f
和a2.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.f
和a2.f
相当于Method类的两个实例,不同实例肯定id不一样