调用保存在 class 属性中的函数:内置函数与普通函数的不同行为

calling a function saved in a class attribute: different behavior with built-in function vs. normal function

使用以下脚本:

import time

class Foo(object):
    func = time.gmtime
    def go(self):
        return self.func(0.0)

print time.strftime('%Y', Foo().go())

我得到以下输出:

1970

但是,如果我稍微修改并换行 time.gmtime:

import time

def tmp(stamp):
    return time.gmtime(stamp)

class Foo(object):
    func = tmp
    def go(self):
        return self.func(0.0)

print time.strftime('%Y', Foo().go())

然后我得到以下错误:

Traceback (most recent call last):
  File "./test.py", line 13, in <module>
    print time.strftime('%Y', Foo().go())
  File "./test.py", line 11, in go
    return self.func(0.0)
TypeError: tmp() takes exactly 1 argument (2 given)

显然它试图调用 Foo.func 就好像它是一个实例方法并将 self 作为第一个参数传递。

两个问题:

如果您希望将函数存储为实例属性:

class Foo(object):
    def __init__(self):
        self.func = tmp

    def go(self):
        return self.func(0.0)

您需要在实例的上下文中分配函数,在初始化程序中这样做是一个可能的地方。

希望对您有所帮助。

注意区别...:

>>> import time
>>> type(time.gmtime)
<type 'builtin_function_or_method'>
>>> def f(*a): return time.gmtime(*a)
... 
>>> type(f)
<type 'function'>

A <type 'function'>(这是一个描述符)不是<type 'builtin_function_or_method'>(不是)相同的东西...

你问"How can I safely save an ordinary function in a class/instance attribute and call it later from an instance method?".

如果 "an ordinary function" 是指 <type 'function'>,则将其包装为 staticmethod 以避免 self 在调用时作为第一个参数插入。而且,也可以将 staticmethod 包裹在内置的周围。所以我想这是你最好的选择!

  1. Both time.gmtime and tmp are functions that take a single argument, so why do the two scripts behave differently?

让我们了解当您

时Python会做什么
self.func

来自 the documentation,

When an instance attribute is referenced that isn’t a data attribute, its class is searched. If the name denotes a valid class attribute that is a function object, a method object is created by packing (pointers to) the instance object and the function object just found together in an abstract object: this is the method object. When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

因此,如果 funcself 中的有效 函数对象 ,则将创建一个绑定方法对象,它期望第一个参数为是调用此函数的对象。

现在让我们比较一下,

print type(time.gmtime)

class Foo(object):
    func = time.gmtime
    def go(self):
        print type(self.func)
        return self.func(0.0)

当用Foo().go()调用go时,它会打印

<type 'builtin_function_or_method'>
<type 'builtin_function_or_method'>

没错。由于time.gmtime是内置函数(与函数对象不同),所以这里没有创建绑定方法对象。现在让我们试试你的第二个例子,

def tmp(stamp):
    return time.gmtime(stamp)

print type(tmp), tmp

class Foo(object):
    func = tmp
    def go(self):
        print type(self.func), self.func
        return self.func(0.0)

会打印

<type 'function'> <function tmp at 0x7f34d9983578>
<type 'instancemethod'> <bound method Foo.tmp of <__main__.Foo object at ...>>

由于 tmp 是一个函数对象,根据上面显示的文档,创建了一个绑定方法对象,它期望 Foo 的对象作为第一个参数。因此,tmp 实际上作为 instancemethod 绑定到 Foo。当你像 Foo().go() 那样调用 go 时,它会像这样

在内部调用 tmp
Foo.func(self, 0.0)

有效

tmp(self, 0.0)

这就是您收到以下错误的原因

TypeError: tmp() takes exactly 1 argument (2 given)
  1. How can I safely save an ordinary function in a class/instance attribute and call it later from an instance method?

方案一:

再次引用Python documentation

It is also important to note that user-defined functions which are attributes of a class instance are not converted to bound methods; this only happens when the function is an attribute of the class.

这意味着,当您将用户定义的函数分配给 class 变量时,将发生绑定方法构造。但是,如果您将它分配给一个实例,那么它就不会发生。

所以,你可以利用它来发挥你的优势,就像这样

import time

def tmp(stamp):
    return time.gmtime(stamp)

class Foo(object):
    def __init__(self):
        self.func = tmp       # Instance variable, not a class variable

    def go(self):
        return self.func(0.0)

print time.strftime('%Y', Foo().go())

此处,self.func 将转换为 tmp(0.0),因为没有绑定方法构造发生。

方案二:

像这样使用staticmethod函数

class Foo(object):
    func = staticmethod(tmp)

    def go(self):
        # `return Foo.func(0.0)` This will also work
        return self.func(0.0)

现在,self.func 仍会引用 tmp。这类似于像这样定义 class

class Foo(object):

    @staticmethod
    def func(stamp):
        return time.gmtime(stamp)

    def go(self):
        # `return Foo.func(0.0)` This will also work
        return self.func(0.0)