什么是Python中的"可调用"?

时间:2020-03-06 14:30:28  来源:igfitidea点击:

现在很清楚什么是元类,有一个相关的概念我经常使用,却不知道它的真正含义。

我想每个人都曾经在括号中犯了一个错误,导致了"对象不可调用"异常。此外,使用__init____new__会引起疑问,这个血腥的__call__可以用来做什么。

我们能给我一些解释,包括魔术方法的例子吗?

解决方案

Callable是具有__call__方法的对象。这意味着我们可以伪造可调用的函数,或者执行诸如Partial Function Application之类的整洁的事情,在该函数中,我们可以使用一个函数并添加一些可以增强其功能或者填充某些参数的函数,从而返回可以被调用的函数(在函数式编程圈中称为Currying) )。

某些印刷错误将使解释器尝试调用我们不想要的内容,例如(字符串)。在解释器尝试执行不可调用的应用程序时,这可能会产生错误。我们可以通过执行下面的脚本来查看在python解释器中发生的情况。

[nigel@k9 ~]$ python
Python 2.5 (r25:51908, Nov  6 2007, 15:55:44) 
[GCC 4.1.2 20070925 (Red Hat 4.1.2-27)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> 'aaa'()    # <== Here we attempt to call a string.
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable
>>>

很简单,"可调用"是可以像方法一样被调用的东西。内置函数" callable()"将告诉我们是否有某些东西可调用,就像检查call属性一样。函数可以像类一样被调用,类实例可以被调用。在此处和此处查看有关此内容的更多信息。

我们可以在其后加上"(args)",并期望它能正常工作。可调用对象通常是方法或者类。方法被调用,类被实例化。

可调用对象是可以调用的任何东西。

内置的callable(objects.c中的PyCallable_Check)检查参数是否为:

  • 具有__call__方法的类的实例,或者
  • 是具有非null tp_call(c struct)成员的类型,该成员以其他方式指示可调用性(例如在函数,方法等中)

名为__call__的方法是(根据文档)

Called when the instance is ''called'' as a function

例子

class Foo:
  def __call__(self):
    print 'called'

foo_instance = Foo()
foo_instance() #this is calling the __call__ method

__call__使任何对象都可以作为函数调用。

此示例将输出8:

class Adder(object):
  def __init__(self, val):
    self.val = val

  def __call__(self, val):
    return self.val + val

func = Adder(5)
print func(3)

在Python中,可调用对象是类型具有__call__方法的对象:

>>> class Foo:
...  pass
... 
>>> class Bar(object):
...  pass
... 
>>> type(Foo).__call__(Foo)
<__main__.Foo instance at 0x711440>
>>> type(Bar).__call__(Bar)
<__main__.Bar object at 0x712110>
>>> def foo(bar):
...  return bar
... 
>>> type(foo).__call__(foo, 42)
42

就如此容易 :)

这当然可以重载:

>>> class Foo(object):
...  def __call__(self):
...   return 42
... 
>>> f = Foo()
>>> f()
42

从Python的源object.c:

/* Test whether an object can be called */

int
PyCallable_Check(PyObject *x)
{
    if (x == NULL)
        return 0;
    if (PyInstance_Check(x)) {
        PyObject *call = PyObject_GetAttrString(x, "__call__");
        if (call == NULL) {
            PyErr_Clear();
            return 0;
        }
        /* Could test recursively but don't, for fear of endless
           recursion if some joker sets self.__call__ = self */
        Py_DECREF(call);
        return 1;
    }
    else {
        return x->ob_type->tp_call != NULL;
    }
}

它说:

  • 如果对象是某个类的实例,则可以调用,只要它具有__call__属性即可。
  • 否则对象x是可调用的,前提是x-> ob_type-> tp_call!= NULL

" tp_call"字段的描述:

ternaryfunc tp_call An optional
  pointer to a function that implements
  calling the object. This should be
  NULL if the object is not callable.
  The signature is the same as for
  PyObject_Call(). This field is
  inherited by subtypes.

我们始终可以使用内置的" callable"函数来确定给定对象是否可调用;或者更好的方法是调用它并稍后捕获TypeError。在Python 3.0和3.1中删除了" callable",使用" callable = lambda o:hasattr(o,'__call __')或者insinstance(o,collections.Callable)`。

示例,一个简单的缓存实现:

class Cached:
    def __init__(self, function):
        self.function = function
        self.cache = {}

    def __call__(self, *args):
        try: return self.cache[args]
        except KeyError:
            ret = self.cache[args] = self.function(*args)
            return ret

用法:

@Cached
def ack(x, y):
    return ack(x-1, ack(x, y-1)) if x*y else (x + y + 1)

来自标准库文件" site.py"的示例,内置的" exit()"和" quit()"函数的定义:

class Quitter(object):
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return 'Use %s() or %s to exit' % (self.name, eof)
    def __call__(self, code=None):
        # Shells like IDLE catch the SystemExit, but listen when their
        # stdin wrapper is closed.
        try:
            sys.stdin.close()
        except:
            pass
        raise SystemExit(code)
__builtin__.quit = Quitter('quit')
__builtin__.exit = Quitter('exit')

可调用对象是允许我们使用圆括号()并最终传递一些参数的对象,就像函数一样。

每次定义函数时,python都会创建一个可调用对象。
例如,我们可以通过以下方式定义函数func(相同):

class a(object):
    def __call__(self, *args):
        print 'Hello'

func = a()

# or ... 
def func(*args):
    print 'Hello'

我们可以使用此方法代替doit或者run之类的方法,我认为与obj.doit()相比,更清楚地看到obj()