python描述符【三】:函数与绑定方法

本文深入探讨Python中函数与方法的本质区别,详细介绍了实例方法、静态方法和类方法的实现原理及应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

Python函数是典型的非数据型描述符,当定义一个函数时,会为函数对象自动生成__get__方法。另外在Python中,函数与方法的唯一区别在于:方法的第一个参数是保留的,不是实例就是类,一般用self表示实例,cls表示类,而函数是没有保留的参数滴。因此本部分先从描述符的角度记录函数,然后记录函数与方法的关系,最后记录类方法、实例方法、静态方法。
 

函数

使用def或者lambda关键字即可定义函数,前者是命名函数,后者是匿名函数,本部分是指命名函数。从下面可以看出python中的函数默认是包含__get__特殊方法的,函数的__get__特殊方法返回函数对象自身

   def print_hello(*args):
       print("Hello Python's function from Desriptor perspective")
       print("i have arguments that include {}".format(','.join(list(args))))

   print(print_hello) # <function print_hello at 0x7f2d17b956a8>
   print_hello('a', 'b', 'c')
   
   print(print_hello.__get__) # <method-wrapper '__get__' of function object at 0x7f2d17b956a8>
   print(print_hello.__get__())  # <bound method print_hello of <function print_hello at 0x7fafc5e41f28>>

 

方法

方法中第一个参数是保留给对象的,根据第一个参数可将方法分为三类,实例方法、类方法与静态方法。在定义函数时加入类方法装饰器,或者静态方法装饰器,就可以得到类方法或者静态方法。下列的引用代码表示了函数与绑定方法的关系:

class MethodType:
    "Emulate PyMethod_Type in Objects/classobject.c"

    def __init__(self, func, obj):
        self.__func__ = func  # 储存实际的函数
        self.__self__ = obj  # 存储实例对象

    def __call__(self, *args, **kwargs):
        func = self.__func__
        obj = self.__self__
        return func(obj, *args, **kwargs)  # 将实例对象前置在所有参数前面

class Function:
    ...
    def __get__(self, obj, objtype=None):
        "Simulate func_descr_get() in Objects/funcobject.c"
        if obj is None:  # 如果是类调用直接返回函数对象
            return self
        return MethodType(self, obj)  # 实例不为空时,返回绑定方法

 

实例方法

实例方法是最普遍使用的,本质就是第一个参数是实例对象的函数。

class TestFunc(object):
    """ 测试函数 """
    def echo(self, *args):
        print(" {} i am instance method and have arguments that include {}".format(self, ','.join(list(args))))

    @classmethod
    def cls_echo(cls, *args):
        print("{} i am classmethod and have arguments that include {}".format(','.join(list(args))))

    @staticmethod
    def sta_echo(*args):
        print("<None> i am staticmethod and have arguments that include {!r}".format(','.join(list(args))))

f = TestFunc()
print(TestFunc.echo)
print(f.echo)

print(f.echo.__func__)
print(f.echo.__self__)

print(f.echo('a', 'b', 'c'))
f.echo.__call__('a', 'b', 'c')

在这里插入图片描述
通过类调用时,直接返回函数;通过实例调用时返回绑定方法。因为函数是非数据型描述符,对象.属性自动触发__getattribute__方法,该特殊方法识别函数中的__get__特殊方法后,自动调用__get__(self, obj, owner):

绑定方法中的__func__与__self__属性储存绑定的函数调用对象, 绑定方法调用时,会将__self__作为第一个参数传递给__func__,然后才将其他参数传入__func__。而使用类调用的,由于会返回函数,因此需要手动传入实例作为第一个参数,如下所示:
在这里插入图片描述对于实例方法,通过对象调用时,__get__方法会将obj.f(*args)转化为f(obj, *args)。通过类调用时,将cls.f(*args)转化为f(*arg)
 

静态方法

在类中定义函数时,当使用staticmethod类装饰器装饰函数时,得到的就是静态方法,静态方法一般是概念上与类不相关,但可以定义在类内面的函数,其第一个参数并未被绑定对象,就像函数一样,通过重写__get__特殊方法,就可以忽略调用对象,返回函数本身

class StaticMethod:
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"
    def __init__(self, f):
        self.f = f
        
    def __get__(self, obj, objtype=None):
        return self.f  # 不论是类调用、还是实例调用返回函数本身,因此定义静态方法时,不需要self、或者cls保留的位置参数

在这里插入图片描述
 

类方法

在类中定义函数时,使用类装饰器就可以得到类函数,类函数第一个默认参数就是类对象,类装饰器通过覆盖__get__方法,将类调用或者实例调用中的参数列表前补充一个类对象:

class ClassMethod:
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"

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

    def __get__(self, obj, cls=None):
        if cls is None:
            cls = type(obj)
        if hasattr(type(self.f), '__get__'):
            return self.f.__get__(cls)
        return MethodType(self.f, cls)  # 将类对象绑定到函数对象上

在这里插入图片描述可以看到对于类方法,实例与类都是可以调用的,并且都会自动补充类作为第一个参数。
 

小结

  1. 不论是实例方法、静态方法、类方法,都可以通过实例或者类去调用,只是对应的__get__特殊方法的定义不同,当然函数也可以不通过对象而直接调用,这样不会自动调用__get__方法。
  2. 对于实例方法,__get__会将实例调用转化成满足实例方法定义的样子,而通过类调用时,就要手动传递实例对象作为第一个参数了,因为类调用返回的是函数,而不是绑定方法。
  3. 对于静态方法,实例与类调用的效果都是一样的,都是返回函数自身,因此在定义静态方法时,不需要在第一个位置保留对象参数。
  4. 对于类方法,不论通过实例调用还是类调用都会返回绑定方法,并自动将类作为函数的第一个参数。
     

参考资料

  1. https://docs.python.org/3/howto/descriptor.html#pure-python-equivalents

  2. 《流畅的python》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值