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

时间:2015-01-18 05:13:59

标签: python python-2.7 python-internals

使用以下脚本:

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作为第一个参数传递。

两个问题:

  • time.gmtimetmp都是采用单个参数的函数,那么为什么这两个脚本的行为不同?
  • 如何安全地将普通函数保存在类/实例属性中,稍后从实例方法调用它?

3 个答案:

答案 0 :(得分:4)

  
      
  1. time.gmtimetmp都是采用单个参数的函数,那么为什么这两个脚本的行为不同?
  2.   

让我们了解Python做什么

self.func

来自the documentation

  

当引用的实例属性不是数据属性时,将搜索其类。如果名称表示有效的类属性是一个函数对象,则通过打包(指向)实例对象和刚在抽象对象中找到的函数对象来创建方法对象:这是方法对象。当使用参数列表调用方法对象时,将从实例对象和参数列表构造新的参数列表,并使用此新参数列表调用函数对象。

因此,如果funcself中的有效函数对象,那么将创建一个绑定的方法对象,该对象期望第一个参数成为其上的对象调用此函数。

现在让我们进行比较,

print type(time.gmtime)

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

使用go调用Foo().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的Object成为第一个参数。因此,tmp实际上与Foo绑定为instancemethod。当您go调用Foo().go()时,会在内部调用此tmp

Foo.func(self, 0.0)

实际上是

tmp(self, 0.0)

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

TypeError: tmp() takes exactly 1 argument (2 given)
  
      
  1. 如何安全地将普通函数保存在类/实例属性中,稍后从实例方法调用它?
  2.   

解决方案1:

再次引用Python documentation

  

同样重要的是要注意,作为类实例属性的用户定义函数不会转换为绑定方法;只有在函数是类的属性时才会发生这种情况。

这意味着,当您将用户定义的函数分配给类变量时,将发生绑定的方法构造。但是,如果将其分配给实例,则不会发生。

所以,你可以利用这个优势,比如

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),因为没有绑定方法构造发生。

解决方案2:

使用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 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)

答案 1 :(得分:2)

请注意差异......:

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

<type 'function'>(是一个描述符)<type 'builtin_function_or_method'>相同(不是......)

你问&#34;如何安全地将普通函数保存在类/实例属性中,稍后从实例方法中调用它?&#34;。

如果通过&#34;普通功能&#34;你的意思是<type 'function'>,然后将其包装为staticmethod,以避免self作为第一个参数被调用时插入staticmethod。而且,将{{1}}包裹在内置的内容也可以。所以我想这是你最好的选择!

答案 2 :(得分:0)

如果要将函数存储为实例属性:

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

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

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

希望有所帮助。