首页 > 技术文章 > 昨天看了一篇super的文章,让我对函数(function)与描述符(descriptor)有了更多的认知。记录一下。

sidianok 2019-12-02 16:35 原文

昨天看了一篇关于super的文章,对我的基础学习还是非常有帮助的,记录一些自己的想法与笔记,加深印象。

其实当我们定义一个函数的时候,如果把函数当做一个对象,其实这个对象里面也有很多方法。

In [428]: def fun(): 
     ...:     pass 
     ...:                                                                                                     

In [429]: dir(fun)                                                                                            
Out[429]: 
['__annotations__',
 '__call__',
 '__class__',
 '__closure__',
 '__code__',
 '__defaults__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__get__',
 '__getattribute__',
 '__globals__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__kwdefaults__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__']

 里面有一些简单的我知道一些比如__name__,__sizeof__,__closure__,等

但里面有一个__get__,与__call__,__call__是直接调用的,但__get__是当做描述符(descriptor)用的,其实我们在类里面定义的方法调用的都是__get__,而且里面应该有着具体的逻辑,

要不然你用类去调用该方法与你用实例去调用该方法会不一样。

In [430]: fun.__class__                                                                                       
Out[430]: function

In [431]: fun.__class__.__class__                                                                             
Out[431]: type

 从上面的代码首先了解到自定义的函数是由function实例出来的,function尽然还是由type创造出来的,看来type真的很厉害,创造的所有的类里面包含了function,想想也对。

但根据《流畅的Python》书中写到,object类和type类之间的关系很独特,object是type的的实例,而type是object的子类。

这个感觉有点像鸡与蛋的故事一样,object需要type实例创建,但type类却继承object,好比type创造了一个自己的爸爸类。

 

接下来,我将定义一个简单的描述符,来查看一下,我们调用这个描述符到底做了什么。

In [436]: class Son: 
     ...:     def __get__(self, instacne, cls_): 
     ...:         print('__get__', instacne, cls_) 
     ...:     def __call__(self): 
     ...:         print('__call__') 
     ...:                                                                                                     

In [437]: class Father: 
     ...:     des = Son() 
     ...:                                                                                                     

In [438]: father = Father()                                                                                   

In [439]: father.des                                                                                          
__get__ <__main__.Father object at 0x114923790> <class '__main__.Father'>

In [440]: father.des()                                                                                        
__get__ <__main__.Father object at 0x114923790> <class '__main__.Father'>
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-440-60c997b7a682> in <module>
----> 1 father.des()

TypeError: 'NoneType' object is not callable

In [441]: Father.des                                                                                          
__get__ None <class '__main__.Father'>

In [442]: Father.des()                                                                                        
__get__ None <class '__main__.Father'>
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-442-1e7b73a77521> in <module>
----> 1 Father.des()

TypeError: 'NoneType' object is not callable

 对于描述符深刻映象来至与这个实际操作,当一个具有__get__属性的对象,在它被当做某个对象的属性时,调用这个属性,会执行该对象的__get__方法。

当不用的实例调用该对象,会发生不同的效果,如果一个把这个对象当做类属性的话,它的instance与cls返回的是Father.des __get__ None <class '__main__.Father'>

但当他用这个类的实例去调用该它时,它的instance与cls返回的是__get__ <__main__.Father object at 0x114923790> <class '__main__.Father'>

所以一个描述符可以用过__get__里面获取的instance来判断调用它的对象是实例还是类。

 

In [443]: class Foo: 
     ...:     def func(self): 
     ...:         pass 
     ...:                                                                                                     

In [444]: Foo.func                                                                                            
Out[444]: <function __main__.Foo.func(self)>

In [445]: foo = Foo()                                                                                         

In [446]: foo.func                                                                                            
Out[446]: <bound method Foo.func of <__main__.Foo object at 0x114893110>>

 上面简单的定义了一个类,类里面定义了一个函数,但定义在类里面的函数就是方法了,但很明显这个方法也好,函数也好,肯定由__get__属性。

所以这个函数func已经变成了类Foo的属性,无论是Foo去调用还是foo去调用

In [449]: Foo.func.__get__(None,A)                                                                            
Out[449]: <function __main__.Foo.func(self)>

In [450]: Foo.func.__get__(foo,A)                                                                             
Out[450]: <bound method Foo.func of <__main__.Foo object at 0x114893110>>

 前面那个例子可以看出来,假如当做方法当做类属性的时候,instance是none,如果当时实例属性的时候,instance是实例本身

所以Foo.fun就好比Foo.func.__get__(None,A)

foo.fun就好比Foo.func.__get__(foo,A)

__get__函数里面通过instance的判断,返回不同的方法给对象,所以才会由前面不同的对象调用方法属性的时候,会显示绑定与未绑定的情况。

 

所以我们无论通过实例也好,类也好,在访问类的内部定义的方法,统统调用的是__get__方法,通过访问该方法的对象(类或实例)的不同,来返回不同的结果。

能力有限,时间有限,不能写出里面具体的实现过程,只能了解到这里先。

推荐阅读