Skip to main content
 首页 » 编程设计

对构造函数的 Python 函数式编程引用

2025年05月04日15lonelyxmas

我想要一个可以指向任一个的函数指针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() 调用有效,但最后一个无效。

请您参考如下方法:

这不起作用的原因是 __init__方法不是构造函数,它是初始化程序。*

注意它的第一个参数是 self——self 必须在它的 __init__ 方法被调用之前已经被构建,否则,在哪里它来自。

换句话说,它是一个普通的实例方法,就像 instance.m 一样,但是你试图将它作为一个未绑定(bind)的方法来调用——就像你试图调用 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 在评论中指出的那样:

In general, if you want a ptr that does whatever_expression(foo) when you call ptr(foo), you should set ptr = whatever_expression

这是一个很好的、简单的经验法则,Python 经过精心设计以尽可能使该经验法则起作用。


最后,作为旁注,您可以将 ptr 分配给任何可调用的对象,而不仅仅是您描述的情况:

  • 一个函数,
  • 绑定(bind)方法(您的instance.m),
  • 一个构造器(也就是一个类),
  • 一个未绑定(bind)的方法(例如,C.m——您可以调用它,但您必须将 instance 作为第一个参数传递),
  • 绑定(bind)类方法(例如,C.cm 和 instance.cm,如果您将 cm 定义为 @classmethod ),
  • 一个未绑定(bind)的类方法(更难构造,而且用处不大),
  • 静态方法(例如,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__ 方法。