Python 中的元类:需要澄清的几个问题

发布于 2024-11-18 09:29:38 字数 220 浏览 1 评论 0原文

在与元类崩溃之后,我深入研究了 Python 中的元编程主题,恕我直言,我有几个问题在可用文档中没有明确回答。

  1. 在元类中同时使用 __new__ 和 __init__ 时,它们的参数必须定义相同吗?
  2. 在元类中定义类 __init__ 的最有效方法是什么?
  3. 有没有办法在元类中引用类实例(通常是self)?

After crashing with metaclasses i delved into the topic of metaprogramming in Python and I have a couple of questions that are, imho, not clearly anwered in available docs.

  1. When using both __new__ and __init__ in a metaclass, their arguments must be defined the same?
  2. What's most efficient way to define class __init__ in a metaclass?
  3. Is there any way to refer to class instance (normally self) in a metaclass?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

原野 2024-11-25 09:29:38
  1. 同时使用__new____init__
    在元类中,它们的参数必须
    定义相同吗?

    我认为Alex Martelli解释了
    最简洁地说:

    类名(Base1,Base2): <>
        __metaclass__==合适的_metaclass
    

    表示

    Name =suitable_metaclass('Name', (Base1,Base2), <>)
    

    所以别再想了
    合适的元类作为元类
    暂时将其视为
    班级。每当你看到

    suitable_metaclass('名称', (Base1,Base2), <>)
    

    它告诉你
    合适的_metaclass的__new__
    方法必须有签名
    类似的东西

    def __new__(metacls, 名称, 基数, dct)
    

    和一个 __init__ 方法,例如

    def __init__(cls, 名称, 基数, dct)
    

    因此签名并不完全相同,但它们仅在第一个参数上有所不同。

  2. 最有效的定义方式是什么
    元类中的类 __init__

    高效是什么意思?这是
    不需要定义__init__
    除非你愿意。

  3. 有没有办法引用类
    实例(通常是自身)
    元类?

    不,您不需要这样做。
    任何取决于班级的事情
    实例应在
    类定义,而不是在
    元类。

  1. When using both __new__ and __init__
    in a metaclass, their arguments must
    be defined the same?

    I think Alex Martelli explains
    it most succinctly:

    class Name(Base1,Base2): <<body>>
        __metaclass__==suitable_metaclass
    

    means

    Name = suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    So stop thinking about
    suitable_metaclass as a metaclass
    for a moment and just regard it as a
    class. Whenever you see

    suitable_metaclass('Name', (Base1,Base2), <<dict-built-by-body>>)
    

    it tells you that
    suitable_metaclass's __new__
    method must have a signature
    something like

    def __new__(metacls, name, bases, dct)
    

    and a __init__ method like

    def __init__(cls, name, bases, dct)
    

    So the signatures are not exactly the same, but they differ only in the first argument.

  2. What's most efficient way to define
    class __init__ in a metaclass?

    What do you mean by efficient? It is
    not necessary to define the __init__
    unless you want to.

  3. Is there any way to refer to class
    instance (normally self) in a
    metaclass?

    No, and you should not need to.
    Anything that depends on the class
    instance should be dealt with in the
    class definition, rather than in the
    metaclass.

Bonjour°[大白 2024-11-25 09:29:38

对于 1:any 类的 __init____new__ 必须接受相同的参数,因为它们将使用相同的参数进行调用。 __new__ 通常会接受更多它忽略的参数(例如 object.__new__ 接受任何参数并忽略它们),因此 __new__ 不会'在继承过程中不必重写,但通常只有在根本没有 __new__ 时才这样做。

这在这里不是问题,因为正如前面所说,元类总是使用相同的参数集来调用,因此您不会遇到麻烦。至少有论据。但是,如果您要修改传递给父类的参数,则需要同时修改它们。

对于2:您通常不会在元类中定义类__init__。您可以编写一个包装器并在元类的 __new____init__ 中替换该类的 __init__,也可以重新定义 元类上的 __call__ 。如果您使用继承,前者会表现得很奇怪。

import functools

class A(type):
    def __call__(cls, *args, **kwargs):
        r = super(A, cls).__call__(*args, **kwargs)
        print "%s was instantiated" % (cls.__name__, )
        print "the new instance is %r" % (r, )
        return r


class B(type):
    def __init__(cls, name, bases, dct):
        super(B, cls).__init__(name, bases, dct)
        if '__init__' not in dct:
            return
        old_init = dct['__init__']
        @functools.wraps(old_init)
        def __init__(self, *args, **kwargs):
            old_init(self, *args, **kwargs)
            print "%s (%s) was instantiated" % (type(self).__name__, cls.__name__)
            print "the new instance is %r" % (self, )
        cls.__init__ = __init__


class T1:
    __metaclass__ = A

class T2:
    __metaclass__ = B
    def __init__(self): 
        pass

class T3(T2):
    def __init__(self):
        super(T3, self).__init__()

调用它的结果:

>>> T1()
T1 was instantiated
the new instance is <__main__.T1 object at 0x7f502c104290>
<__main__.T1 object at 0x7f502c104290>
>>> T2()
T2 (T2) was instantiated
the new instance is <__main__.T2 object at 0x7f502c0f7ed0>
<__main__.T2 object at 0x7f502c0f7ed0>
>>> T3()
T3 (T2) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
T3 (T3) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
<__main__.T3 object at 0x7f502c104290>

对于 3:是的,来自 __call__,如上所示。

For 1: The __init__ and __new__ of any class have to accept the same arguments, because they would be called with the same arguments. It's common for __new__ to take more arguments that it ignores (e.g. object.__new__ takes any arguments and it ignores them) so that __new__ doesn't have to be overridden during inheritance, but you usually only do that when you have no __new__ at all.

This isn't a problem here, because as it was stated, metaclasses are always called with the same set of arguments always so you can't run into trouble. With the arguments at least. But if you're modifying the arguments that are passed to the parent class, you need to modify them in both.

For 2: You usually don't define the class __init__ in a metaclass. You can write a wrapper and replace the __init__ of the class in either __new__ or __init__ of the metaclass, or you can redefine the __call__ on the metaclass. The former would act weirdly if you use inheritance.

import functools

class A(type):
    def __call__(cls, *args, **kwargs):
        r = super(A, cls).__call__(*args, **kwargs)
        print "%s was instantiated" % (cls.__name__, )
        print "the new instance is %r" % (r, )
        return r


class B(type):
    def __init__(cls, name, bases, dct):
        super(B, cls).__init__(name, bases, dct)
        if '__init__' not in dct:
            return
        old_init = dct['__init__']
        @functools.wraps(old_init)
        def __init__(self, *args, **kwargs):
            old_init(self, *args, **kwargs)
            print "%s (%s) was instantiated" % (type(self).__name__, cls.__name__)
            print "the new instance is %r" % (self, )
        cls.__init__ = __init__


class T1:
    __metaclass__ = A

class T2:
    __metaclass__ = B
    def __init__(self): 
        pass

class T3(T2):
    def __init__(self):
        super(T3, self).__init__()

And the result from calling it:

>>> T1()
T1 was instantiated
the new instance is <__main__.T1 object at 0x7f502c104290>
<__main__.T1 object at 0x7f502c104290>
>>> T2()
T2 (T2) was instantiated
the new instance is <__main__.T2 object at 0x7f502c0f7ed0>
<__main__.T2 object at 0x7f502c0f7ed0>
>>> T3()
T3 (T2) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
T3 (T3) was instantiated
the new instance is <__main__.T3 object at 0x7f502c104290>
<__main__.T3 object at 0x7f502c104290>

For 3: Yes, from __call__ as shown above.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文