16.4 Python descriptor-function and method

Python descriptor - function and method

转载请标明出处(http://blog.csdn.net/lis_12/article/details/53495627).

properties,methods, static methods,class methods, and super()都是基于描述符实现的。本篇文章了解下就好,不必深究,如果没学过描述符的话,建议研究下描述符

准备知识

  1. 访问属性优先级: 类属性的数据描述符 > 实例属性 > 类属性的非数据描述符,非描述符的类属性 > __getattr__()
  2. 如果一个对象定义了__get__()方法,在属性访问时会覆盖默认行为,调用__get__();(这个对象要为类属性)

function and method

​ Python面向对象的特征是建立在函数上的,非数据描述符将二者完美的结合在了一起。

类的字典将类中的方法当做函数存储。在定义类的时候,方法通常用关键字 deflambda来声明,这和创建函数的方式是一样的。唯一的不同之处是方法的第一个参数用来表示实例,Python约定,这个参数通常是 self, 也可以是 this 或者其它任何名字。

个人认为 方法就是一种加了命名空间的特殊函数,命名空间就是实例。

​ 为了支持方法调用,函数定义了 __get__()方法,即当函数作为属性被访问时会调用function.__get__()。所有的函数都是非数据描述符,它们返回绑定(bound)还是非绑定(unbound)方法取决于是被实例调用还是被类调用。

绑定方法: 函数中的第一个参数已经被设置成实例;

未绑定方法: 所有参数原封不动地传给原来的函数,包括第一个参数self;

Python模拟函数的实现

class function(object):
    . . .
    def __get__(self, obj, objtype = None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        return types.methodType(self, obj, objtype)

因为function为非数据描述符,当function对象作为属性被访问时会调用function.__get__()

class Foo(object):
    def __init__(self):
        self.x = 1
    def fun(self):
        print 'fun'
def f1():
    pass
f = Foo()
  1. 方法在类字典中的存储方式为函数

    print f1                                     #<function fun at>
    print type(f1)                               #<type 'function'>
    print Foo.__dict__['fun']                    #<function fun at>,与f1的结果一样,虽然是类中的方法,但是按照函数存储的
    print type(Foo.__dict__['fun']) == type(f1)  #True,相等啊,记住方法是按照函数存储的...

    此时Foo.__dict__['fun']就是一个函数…只不过第一参数要传Foo的实例,不然可能会出现异常。

  2. 按函数的形式调用方法

    print Foo.__dict__['fun'](f)                 #fun,将实例以参数的形式传给fun函数,等价于f.fun()
  3. 绑定方法和非绑定方法

    print f.fun          #bound method,此时已经将第一个参数设置成了实例f
    print Foo.fun        #unbound method,参数原封不动
    f.fun()              #调用fun方法
    
    #Foo.fun()           #error,缺少实例参数,即self
    
    Foo.fun(f)           #加上实例参数,等价于f.fun(),有没有感觉很和函数一样...,只不过多了个命名空间
  4. 函数为非数据描述符

    print '__get__' in dir(f1)                  #True
    print '__get__' in dir(Foo.__dict__['fun']) #True
  5. 类中的函数作为属性被访问时,描述符方法__get__()会将函数转化为方法,即当调用f.fun时,编译器会将f.fun转化为Foo.__dict__['fun'].__get__(f,type(f)),也就是说f.fun为Foo.__dict__['fun'].__get__(f,type(f))的返回值。

    bound = Foo.__dict__['fun'].__get__(f,type(f))
    bound1 = Foo.__dict__['fun'].__get__(f)
    unbound = Foo.__dict__['fun'].__get__(None,Foo)
    print bound == f.fun         #True
    print bound1 == f.fun        #True
    print unbound == Foo.fun     #True

由以上代码可知,

  1. 方法在类字典中的存储方式为函数;

  2. 绑定方法和非绑定方法是两个不同的类型。

    绑定方法,由函数转化为绑定方法时,函数的第一个参数设置成实例,其余的参数为绑定方法的参数;

    非绑定方法,由函数转化为非绑定方法时 函数中的参数原封不动的传给方法。

  3. 函数为非数据描述符,当类中的函数作为属性被访问时(即访问类中的方法),会调用function.__get__()function.__get__()的返回值为方法;

函数和方法测试code

class Foo(object):
    def __init__(self,x = 1):
        self.x = x
    def fun(self,x):
        print 'fun:self.x = %s;x = %s'%(self.x,x)
def fun():
    pass

f = Foo()
f.fun(2)              #fun:self.x = 1;x = 2

#传参二重奏,先将实例传进去,然后再将其他参数传进去
Foo.__dict__['fun'].__get__(f,type(f))(2) #fun:self.x = 1;x = 2,等价于f.fun(2)

#验证
F = type(f).__dict__['fun'].__get__(f,type(f))
print type(F)         #<type 'instancemethod'>
print F == f.fun      #True
F(3)                  #fun:self.x = 1;x = 3

以下为个人理解.

传参二重奏:第一阶段先给self赋值,第二阶段给除self以外的参数赋值…

  1. fun中的self参数为fun所在的命名空间,实例f就是一个命名空间;
  2. F = type(f).__dict__['fun'].__get__(f,type(f))先给fun中的self赋值,相当于传参只传了一部分,即只给self赋值了(self = f),其他参数未赋值,此时F就为一个特殊的普通函数,可以像普通函数一样调用…
  3. 继续给其他参数赋值,即给除了self之外的参数赋值,如调用函数F,F(3)

静态方法

那些不需要 selfcls 变量的方法适合为静态方法。

staticmethod() Python的模拟实现:

class staticmethod(object):
 "Emulate Pystaticmethod_Type() in Objects/funcobject.c"

 def __init__(self, f):
      self.f = f

 def __get__(self, obj, objtype=None):
      return self.f

因为staticmethod为非数据描述符,当staticmethod对象作为属性被访问时会调用staticmethod.__get__()

class E(object):
    def f(x):
        print x
    f = staticmethod(f)
  1. 静态方法的存储方式

    print E.__dict__['f']  #<staticmethod object at>

    静态方法与实例方法的存储方式不一样,不是按照函数形式存储的。

  2. 从类和实例中调用静态方法

    e = E()
    E.f(3)             #3
    e.f(3)             #3
    
    #E.__dict__['f'](3) #TypeError: 'staticmethod' object is not callable,不能利用这种方式调用
    
  3. 静态方法是非数据描述符

    print '__get__' in dir(E.__dict__['f']) #True
  4. 静态方法作为属性被访问时会调用staticmethod.__get__()

    '''第一个参数为实例还是None对静态方法没影响,只要类对了就ok了'''
    a = E.__dict__['f'].__get__(None,E)
    b = E.__dict__['f'].__get__(e,E)
    
    print a == E.f   #True
    print b == E.f   #True

从以上代码可知,

  1. 静态方法在类字典中的存储方式与实例方法不同;
  2. 静态方法为非数据描述符,当静态方法作为属性被访问时会调用staticmethod.__get__()
  3. 静态方法与实例无关,与类有关…

类方法

与静态方法不同,类方法的第一个参数用来表示类,一般为cls。

classmethod() Python模拟实现:

class classmethod(object):
     "Emulate Pyclassmethod_Type() in Objects/funcobject.c"
     def __init__(self, f):
          self.f = f

     def __get__(self, obj, klass=None):
          if klass is None:
               klass = type(obj)   #注意这里...
          def newfunc(*args):
               return self.f(klass, *args)
          return newfunc

因为classmethod为非数据描述符,当classmethod对象作为属性被访问时会调用classmethod.__get__()

class E(object):
    def f(cls,x):
        print x
    f = classmethod(f)
e = E()
  1. 类方法的存储方式

    print E.__dict__['f']   #<classmethod object at 0x00000000033FB168>

    类方法的存储方式与静态方法,实例方法都不同。

  2. 从类和实例调用类方法

    e.f(3)  #3
    E.f(3)  #3
    
    #E.__dict__['f']()    #TypeError: 'classmethod' object is not callable,不能用这种方式调用
    

    与实例方法的self一样,cls参数已经自动传入类方法了,无需手动传入。

  3. 类方法是非数据描述符

    print '__get__' in dir(E.__dict__['f'])  #True
  4. 类方法作为属性被访问时会调用classmethod.__get__()

    a = E.__dict__['f'].__get__(None,E)  #<bound method type.f of <class '__main__.E'>>
    b = E.__dict__['f'].__get__(e,E)     #<bound method type.f of <class '__main__.E'>>
    c = E.__dict__['f'].__get__(E,E)     #<bound method type.f of <class '__main__.E'>>
    d = E.__dict__['f'].__get__(E)       #<bound method type.f of <type 'type'>>
    print a == E.f                       #True
    print b == E.f                       #True
    print c == E.f                       #True
    print d == E.f                       #False

由上述代码可知,

  1. 类方法的存储方式与静态方法,实例方法都不同;
  2. 类方法也为非数据描述符,当类方法作为属性被访问时会调用classmethod.__get__()
  3. 类方法与实例无关,与类有关…

类方法相比于静态方法的优势

当一个函数不需要相关的数据做参数而只需要一个类的引用的时候,这个特征就显得很有用了。类方法的一个用途是用来创建不同的类构造器。在Python 2.3中, dict.fromkeys() 可以依据一个key列表来创建一个新的字典。等价的Python实现就是:

class Dict:
    . . .
    def fromkeys(klass, iterable, value=None):
        "Emulate dict_fromkeys() in Objects/dictobject.c"
        d = klass()
        for key in iterable:
            d[key] = value
        return d
    fromkeys = classmethod(fromkeys)

#现在,一个新的字典就可以这么创建:

>>> Dict.fromkeys('abracadabra')
{'a': None, 'r': None, 'b': None, 'c': None, 'd': None}

类方法,静态方法,实例方法

class Foo(object):
    def inst_f(self,a):
        print 'inst_fun()',a

    @classmethod
    def class_f(cls,a):
        print 'class fun()',a

    @staticmethod
    def static_f(a):
        print 'static fun()',a
f = Foo()
  1. 存储方式

    print type(Foo.__dict__['inst_f'])   #<type 'function'>
    print type(Foo.__dict__['class_f'])  #<type 'classmethod'>
    print type(Foo.__dict__['static_f']) #<type 'staticmethod'>
  2. 以函数的形式调用(从类调用方法)

    Foo.inst_f(f,1)                      #inst_fun() 1,等价于f.inst_f()
    Foo.class_f(2)                       #class fun() 2,等价于f.class_f(2)
    Foo.static_f(3)                      #static fun() 3,等价于static_f(3)
  3. 从实例调用方法

    f.inst_f(1)                          #inst_fun() 1,等价于f.inst_f()
    f.class_f(2)                         #class fun() 2,等价于f.class_f(2),self自动传入
    f.static_f(3)                        #static fun() 3,等价于static_f(3),cls自动传入
  4. 从类和实例调用区别

    print f.inst_f,Foo.inst_f           #bound method,unbound method
    print f.inst_f == Foo.inst_f        #False
    
    print f.class_f,Foo.class_f         #bound method,bound method,两种形式是一样的
    print f.class_f == Foo.class_f      #True
    
    print f.static_f,Foo.static_f       #function static_f,function static_f,两种是一样的
    print f.static_f == Foo.static_f    #True   

    从以上代码可知,

    1) 调用实例方法时,从类和实例调用是不一样的,即实例方法与实例有关;

    2) 调用类方法和静态方法时,从类和实例调用是一样的,即类方法和静态方法与实例无关。

  5. 利用__get__()调用方法

    Foo.__dict__['inst_f'].__get__(f,type(f))(1)      #inst_fun() 1
    Foo.__dict__['static_f'].__get__(None,type(f))(2) #static fun() 2
    Foo.__dict__['class_f'].__get__(None,type(f))(3)  #class fun() 3

参考文档

  1. https://docs.python.org/2/howto/descriptor.html#definition-and-introduction
  2. http://blog.csdn.net/lis_12/article/details/53453665
  3. http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html
  4. http://stackoverflow.com/questions/114214/class-method-differences-in-python-bound-unbound-and-static
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值