Python 函数式编程中的构造函数参考

3 投票
2 回答
2272 浏览
提问于 2025-04-19 21:17

我想要一个函数指针 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 个回答

1

我在网上找这个问题的答案时遇到了很多困难,但我终于搞明白了,所以这里是解决方案。

你可以把 constructorPtr 指向 C,而不是指向 C.__init__,这样做就可以了。

constructorPtr = C
constructorPtr('A')

这样输出的结果是:

C's __init__ method called with argument: A
10

这个问题之所以出现,是因为 __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.cminstance.cm,如果你将 cm 定义为 @classmethod),
  • 一个未绑定的类方法(更难构造,且用处较少),
  • 一个静态方法(例如 C.sminstance.sm,如果你将 sm 定义为 @staticmethod),
  • 各种实现特定的“内置”类型,它们模拟函数、方法和类。
  • 任何类型的实例,只要它有一个 __call__ 方法,

实际上,所有这些都是最后一种情况的特殊情况——type 类型有一个 __call__ 方法,types.FunctionTypetypes.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__ 方法。

撰写回答