函数,可调用对象以及如何在Python

时间:2016-10-21 17:47:41

标签: python

我想知道函数和可调用对象之间更复杂的差异。例如,如果你这样做:

def foo1():
   return 2 + 4

class foo2:
   def __call__():
      return 2 + 4

import sys 
sys.getsizeof(foo1)  # returns 136
sys.getsizeof(foo2)  # returns 1016

功能和可调用对象之间显然存在很大差异。但是,我找不到很多关于幕后发生的事情的文档。我知道函数是一流的对象,但我也知道类比常规函数要多得多。 class foo2是使用元类type()创建的。

我的问题是:

  1. 创建函数def foo1():时,它与此有何不同 使用元类定义类的过程?是否有type()的版本,但对于函数,是一个元函数?
  2. 假设有人想编写自己的元函数(这背后的真正原因),最好只使用装饰器,还是使用可调用类的元类?它们会提供什么样的优势(使得可调用类的元类看起来很笨拙)?
  3. 让可调用对象拥有可以在其中存储信息的功能背后的唯一目的是什么?

2 个答案:

答案 0 :(得分:16)

函数也是可调用对象:

>>> foo1.__call__
<method-wrapper '__call__' of function object at 0x105bafd90>
>>> callable(foo1)
True

但是课程需要跟踪更多信息;你在这里给它一个__call__方法并不重要。 任何类都大于函数:

>>> import sys
>>> def foo1():
...    return 2 + 4
...
>>> class foo3:
...    pass
...
>>> sys.getsizeof(foo1)
136
>>> sys.getsizeof(foo3)
1056

函数对象是一种不同的对象类型:

>>> type(foo1)
<class 'function'>

并且相当紧凑,因为肉实际上并不在函数对象中,而是在其他对象中由函数对象引用:

>>> sys.getsizeof(foo1.__code__)
144
>>> sys.getsizeof(foo1.__dict__)
240

这真的是真的;不同类型的对象具有不同的大小,因为它们跟踪不同的东西或使用组合来将东西存储在其他对象中。

如果您愿意,可以使用type(foo1)返回值(或types.FunctionType,它是同一个对象)来生成新的函数对象:

>>> import types
>>> types.FunctionType(foo1.__code__, globals(), 'somename')
<function foo1 at 0x105fbc510>

这基本上是解释器在执行def function(..): ...语句时所执行的操作。

使用__call__使自定义类在对API有意义时可调用。例如,enum.Enum() class is callable,特别是因为使用调用语法为您提供了与订阅不同的语法,该语法用于其他目的。并且xmlrpc.client.ServerProxy() object生成的方法对象是_Method的实例,因为它们代理远程调用,而不是本地函数。

答案 1 :(得分:2)

  

是否有类型()的版本,但对于函数,是一个元函数?

排序。函数有一个类型,该类型至少可以用来构造代码对象和全局字典的新函数。 (代码对象可以使用compile()构造,也可以从现有函数中获取,或者,如果您是受虐狂,则使用代码类型的构造函数从字节码字符串和其他信息构建。)函数类型不是元 - 因为函数是实例,而不是类。您可以使用type(lambda:0)(将任何函数放在括号中)来获取此类型,并执行help(type(lambda:0))以查看其参数。函数类型是type的实例。

  

说有人想写自己的元函数

你不能将函数类型子类化,抱歉。

class FunkyFunc(type(lambda: 0)): pass

TypeError: Error when calling the metaclass bases
    type 'function' is not an acceptable base type
  

让可调用对象拥有可以在其中存储信息的功能背后的唯一目的是什么?

它有很多用途。你的例子是一个(尽管函数实例可以有属性,类可以提供更好的属性文档);反面,使一个普通的旧数据对象可以调用,是另一个(我给了in this answer的一种时髦的例子)。如果实例可调用,则可以使用类来编写装饰器。您可以在元类上使用__call__来自定义实例构造。并且您可以使用它来编写具有不同行为的函数,就像您实际上可以将函数类型子类化一样。 (如果您想在函数中使用C风格的“静态”变量,可以将其编写为具有__call__方法的类,并使用属性来存储静态数据。)

关于函数对象的有趣之处在于,因为它们是可调用的,所以它们本身具有__call__方法。但是方法是可调用的,因此__call__方法也有__call__方法,而__call__方法(可调用)也有__call__方法,依此类推 ad infinitum。: - )