zoukankan      html  css  js  c++  java
  • 面向对象---绑定方法的实现

    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不一样

  • 相关阅读:
    次小生成树(SST)
    传纸条(scrip)
    动态规划练习5
    动态规划练习4
    整数的lqp拆分
    [HNOI2002]跳蚤
    BZOJ1803: Spoj1487 Query on a tree III
    51nod-1526: 分配笔名
    51nod-1615: 跳跃的杰克
    BZOJ2588: Spoj 10628. Count on a tree
  • 原文地址:https://www.cnblogs.com/alantammm/p/15470420.html
Copyright © 2011-2022 走看看