前言
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) # 将类对象绑定到函数对象上
可以看到对于类方法,实例与类都是可以调用的,并且都会自动补充类作为第一个参数。
小结
- 不论是实例方法、静态方法、类方法,都可以通过实例或者类去调用,只是对应的__get__特殊方法的定义不同,当然函数也可以不通过对象而直接调用,这样不会自动调用__get__方法。
- 对于实例方法,__get__会将实例调用转化成满足实例方法定义的样子,而通过类调用时,就要手动传递实例对象作为第一个参数了,因为类调用返回的是函数,而不是绑定方法。
- 对于静态方法,实例与类调用的效果都是一样的,都是返回函数自身,因此在定义静态方法时,不需要在第一个位置保留对象参数。
- 对于类方法,不论通过实例调用还是类调用都会返回绑定方法,并自动将类作为函数的第一个参数。
参考资料
-
https://docs.python.org/3/howto/descriptor.html#pure-python-equivalents
-
《流畅的python》