python-3.x 显式调用__call__有效并使用__init__

pkmbmrz7  于 2023-01-27  发布在  Python
关注(0)|答案(2)|浏览(142)

我正在学习Python 3.X中的重载,为了更好地理解这个主题,我写了下面的代码,它在Python 3.X中可以工作,但在Python 2.X中不能。我以为下面的代码会失败,因为我没有为类Test定义__call__。但令我惊讶的是,它可以工作,并打印"constructor called".Demo

class Test: 
    def __init__(self):
        print("constructor called")
#Test.__getitem__()  #error as expected

Test.__call__() #this works in 3.X(but not in 2.X) and prints "constructor called"! WHY THIS DOESN'T GIVE ERROR in 3.x?

所以我的问题是,这段代码究竟是如何/为什么在3.x中工作的,而在2.x中却不行。我的意思是,我想知道背后的机制是什么。
更重要的是,当我使用__call__时,为什么这里使用__init__

n3schb8v

n3schb8v1#

在3.x中:

关于属性查找,typeobject

每次在对象上查找属性时,Python都遵循如下过程:
1.它是否直接是对象中实际数据的一部分?如果是,使用它并停止。
1.它直接是对象类的一部分吗?如果是,请在步骤4中继续。
1.否则,检查对象的类是否有__getattr____getattribute__覆盖,查看MRO中的基类,等等(当然,这是一个巨大的简化)。
1.如果在第2步或第3步中找到了check if it has a __get__,如果找到了,就查找它(是的,这意味着从第1步开始查找该对象上名为__get__的属性),调用它,并使用它的返回值,否则,直接使用返回的值。
函数有一个__get__自动;用于实现方法绑定,类是对象;这就是为什么可以在它们里面查找属性。也就是说:class Test:块的用途是定义数据类型;代码创建名为Test的对象,该对象表示定义的数据类型
但是由于Test类是一个对象,它必须是某个类的示例,这个类叫做type,有一个内置的实现。

>>> type(Test)
<class 'type'>

注意,type(Test)不是一个函数调用,而是预定义了名称type来引用一个类,在用户代码中创建的每个其他类(默认情况下)都是这个类的示例。
换句话说,type是默认的元类:阶级的阶级。

>>> type
<class 'type'>

有人可能会问,type属于什么类?答案出奇地简单--本身

>>> type(type) is type
True

由于上面的例子调用了type,我们得出结论,type是可调用的。为了是可调用的,它必须有一个__call__属性,它做到了:

>>> type.__call__
<slot wrapper '__call__' of 'type' objects>

type用一个参数调用时,它查找参数的类(大致相当于访问参数的__class__属性);当用三个参数调用时,它创建type的一个新示例,即一个新类。

type是如何工作的?

因为这是在挖掘语言的核心(为对象分配内存),所以不太可能在纯Python中实现它,至少对于参考C实现是这样(我不知道PyPy在这里有什么神奇之处),但是我们可以 * 近似地 * 像这样建模type类:

def _validate_type(obj, required_type, context):
    if not isinstance(obj, required_type):
        good_name = required_type.__name__
        bad_name = type(obj).__name__
        raise TypeError(f'{context} must be {good_name}, not {bad_name}')

class type:
    def __new__(cls, name_or_obj, *args):
        # __new__ implicitly gets passed an instance of the class, but
        # `type` is its own class, so it will be `type` itself.
        if len(args) == 0: # 1-argument form: check the type of an existing class.
            return obj.__class__
        # otherwise, 3-argument form: create a new class.
        try:
            bases, attrs = args
        except ValueError:
            raise TypeError('type() takes 1 or 3 arguments')
        _validate_type(name, str, 'type.__new__() argument 1')
        _validate_type(bases, tuple, 'type.__new__() argument 2')
        _validate_type(attrs, dict, 'type.__new__() argument 3')

        # This line would not work if we were actually implementing
        # a replacement for `type`, as it would route to `object.__new__(type)`,
        # which is explicitly disallowed. But let's pretend it does...
        result = super().__new__()
        # Now, fill in attributes from the parameters.
        result.__name__ = name_or_obj
        # Assigning to `__bases__` triggers a lot of other internal checks!
        result.__bases__ = bases
        for name, value in attrs.items():
            setattr(result, name, value)
        return result
    del __new__.__get__ # `__new__`s of builtins don't implement this.

    def __call__(self, *args):
        return self.__new__(self, *args)
    # this, however, does have a `__get__`.

当我们调用类(Test())时(概念上)会发生什么?

  1. Test()使用函数调用语法,但它不是一个函数。为了弄清楚应该发生什么,我们将调用转换为Test.__class__.__call__(Test)。(我们在这里直接使用__class__,因为使用type转换函数调用-要求type对自己进行分类-将以无限递归结束。)
    1.就是type,所以这个就变成了type.__call__(Test)
  2. type直接包含一个__call__type是它自己的类,记得吗?),所以它是直接使用的--我们不通过__get__描述符,我们调用函数,把Test作为self,没有其他参数。(我们现在有了一个函数,所以不需要再翻译函数调用语法,我们 * 可以 * -给定一个函数funcfunc.__class__.__call__.__get__(func)给我们一个未命名的内置“方法 Package 器”类型的示例,它在被调用时做的事情与func相同。在方法 Package 器上重复循环会创建一个单独的方法 Package 器,它仍然做同样的事情。)
    1.这将尝试调用Test.__new__(Test)(因为self绑定到Test)。Test.__new__没有在Test中显式定义,但因为Test是一个类,所以我们不会在Test的类(type)中查找,而是在Test的基(object)中查找。
  3. object.__new__(Test)已经存在,并且它有神奇的内置功能,可以为Test类的新示例分配内存,可以为该示例分配属性(即使Testobject的子类型,这是不允许的),并将其__class__设置为Test
    类似地,当我们调用type时,相同的逻辑链将type(Test)转换为type.__class__.__call__(type, Test),再转换为type.__call__(type, Test),然后转发到type.__new__(type, Test)。这次,type中直接有一个__new__属性,因此这不会退回到查找object。当name_or_obj被设置为Test时,我们简单地返回Test.__class__,即type,并且使用单独的name, bases, attrs参数,type.__new__创建type的示例。

最后:当我们显式调用Test.__call__()时会发生什么?

如果类中定义了一个__call__,则会使用它,因为它是直接找到的,但这会失败,因为没有足够的参数:描述符协议没有被使用,因为属性是直接找到的,所以self没有被绑定,所以参数丢失。
如果没有定义__call__方法,那么我们就在Test的类中查找,也就是type,那里有一个__call__,所以剩下的就像上一节中的步骤3-5一样。

vq8itlhq

vq8itlhq2#

在Python 3.x中,每个类都隐含地是内置类object的一个子类,至少在CPython实现中,object类有一个__call__方法,该方法在它的元类type中定义。
这意味着Test.__call__()Test()完全相同,并且将返回一个新的Test对象,调用您的自定义__init__方法。
在Python 2.x中,类默认是 * old style * 类,并且不是object的子类,因此__call__没有定义,在Python 2.x中,你可以通过使用 new style 类来获得相同的行为,也就是说,在object上进行显式继承:

# Python 2 new style class
class Test(object):
    ...

相关问题