Python 函数式编程中的构造函数参考
我想要一个函数指针 ptr
,它可以指向以下三种东西中的任意一种:
一个普通的函数,
一个对象实例的方法,或者
这个对象的构造函数。
在最后一种情况下,当执行 ptr()
时,它应该能够创建这个类的一个实例。
def function(argument) :
print("Function called with argument: "+str(argument))
class C(object) :
def __init__(self,argument) :
print("C's __init__ method called with argument: "+str(argument))
def m(self,argument) :
print("C's method 'm' called with argument: "+str(argument))
## works
ptr = function
ptr('A')
## works
instance = C('asdf')
ptr = instance.m
ptr('A')
## fails
constructorPtr = C.__init__
constructorPtr('A')
这段代码的输出结果是:
Function called with argument: A
C's __init__ method called with argument: asdf
C's method 'm' called with argument: A
Traceback (most recent call last): File "tmp.py", line 24, in <module>
constructorPtr('A')
TypeError: unbound method __init__() must be called with C instance as first argument (got str instance instead)
显示前两个 ptr()
调用成功了,但最后一个没有成功。
2 个回答
我在网上找这个问题的答案时遇到了很多困难,但我终于搞明白了,所以这里是解决方案。
你可以把 constructorPtr
指向 C
,而不是指向 C.__init__
,这样做就可以了。
constructorPtr = C
constructorPtr('A')
这样输出的结果是:
C's __init__ method called with argument: A
这个问题之所以出现,是因为 __init__
方法并不是构造函数,而是一个初始化方法。
注意它的第一个参数是 self
——这意味着 self
必须在调用 __init__
方法之前就已经被构造出来了,否则它从哪里来呢?
换句话说,它是一个普通的实例方法,就像 instance.m
一样,但你却试图把它当作一个未绑定的方法来调用——就像你试图调用 C.m
而不是 instance.m
一样。
Python 确实有一个专门的构造函数方法,叫做 __new__
(虽然 Python 为了避免和一些单阶段构造的语言混淆,称它为“创建者”)。这个方法是一个静态方法,它的第一个参数是要构造的类,后面的参数是构造函数的参数。你从 object
继承的默认实现只是创建了该类的一个实例,并将参数传递给它的初始化方法。
constructor = C.__new__
constructor(C, 'A')
或者,如果你更喜欢这样:
from functools import partial
constructor = partial(C.__new__, C)
constructor('A')
不过,直接调用 __new__
是非常罕见的,除非是在子类的 __new__
中。类本身是可以调用的,实际上它们充当自己的构造函数——这意味着它们用适当的参数调用 __new__
方法,但这里有一些细微之处(在每个不同的情况下,C()
可能是你想要的,而不是 C.__new__(C)
)。
所以:
constructor = C
constructor('A')
正如用户 user2357112 在评论中指出的:
一般来说,如果你想要一个
ptr
,在你调用ptr(foo)
时执行whatever_expression(foo)
,你应该设置ptr = whatever_expression
这是一个很好的简单规则,Python 也经过精心设计,以便在可能的情况下使这个规则有效。
最后,作为一个附带说明,你可以将 ptr
赋值为任何可调用的东西,而不仅仅是你描述的那些情况:
- 一个函数,
- 一个绑定的方法(你的
instance.m
), - 一个构造函数(也就是一个类),
- 一个未绑定的方法(例如
C.m
——你可以正常调用它,但你需要将instance
作为第一个参数传递), - 一个绑定的类方法(例如
C.cm
和instance.cm
,如果你将cm
定义为@classmethod
), - 一个未绑定的类方法(更难构造,且用处较少),
- 一个静态方法(例如
C.sm
和instance.sm
,如果你将sm
定义为@staticmethod
), - 各种实现特定的“内置”类型,它们模拟函数、方法和类。
- 任何类型的实例,只要它有一个
__call__
方法,
实际上,所有这些都是最后一种情况的特殊情况——type
类型有一个 __call__
方法,types.FunctionType
和 types.MethodType
也有,等等。
* 如果你熟悉其他语言,比如 Smalltalk 或 Objective-C,你可能会觉得 Python 看起来并没有两阶段构造。在 ObjC 中,你很少实现 alloc
,但你经常调用它:[[MyClass alloc] initWithArgument:a]
。在 Python 中,你可以假装 MyClass(a)
意思是一样的(虽然实际上更像是 [MyClass allocWithArgument:a]
,其中 allocWithArgument:
会自动为你调用 initWithArgument:
)。
** 实际上,这并不完全正确;默认实现只是返回一个 C
的实例,如果 isinstance(returnvalue, C)
,Python 会自动调用 __init__
方法。