【问题标题】:Using metaclasses in order to define methods, class methods / instance methods使用元类来定义方法、类方法/实例方法
【发布时间】:2020-10-24 00:46:42
【问题描述】:

我正在尝试更深入地了解元类在 python 中是如何工作的。我的问题如下,我想使用元类来为每个类定义一个方法,该方法将使用在元类中定义的类属性。例如,这有注册申请。

这是一个工作示例:

import functools


def dec_register(func):
    @functools.wraps(func)
    def wrapper_register(*args, **kwargs):
        (args[0].__class__.list_register_instances).append(args[0])
        return func(*args, **kwargs)
    return wrapper_register




dict_register_classes = {}
class register(type):
    def __new__(meta, name, bases, attrs):
        dict_register_classes[name] = cls = type.__new__(meta, name, bases, attrs)  # assigniation from right to left
        cls.list_register_instances = []
        cls.print_register = meta.print_register
        return cls

    def print_register(self):
        for element in self.list_register_instances:
            print(element)

    def print_register_class(cls):
        for element in cls.list_register_instances:
            print(element)

#
class Foo(metaclass=register):
    @dec_register
    def __init__(self):
        pass

    def print_register(self):
        pass

class Boo(metaclass=register):
    @dec_register
    def __init__(self):
        pass
    def print_register(self):
        pass

f = Foo()
f_ = Foo()
b = Boo()
print(f.list_register_instances)
print(b.list_register_instances)
print(dict_register_classes)

print("1")
f.print_register()
print("2")
Foo.print_register_class()
print("3")
f.print_register_class()
print("4")
Foo.print_register()

我最后做的测试没有像我预期的那样工作。如果我所说的没有使用正确的语法,我提前道歉,我试图尽可能清楚:

我在想cls.print_register = meta.print_register 行正在使用元类中定义的方法在类中定义一个方法。因此,它是我可以在对象上使用的一种方法。我也可以将它用作类方法,因为它是在元类中定义的。但是,尽管以下方法有效:

print("1")
f.print_register()

这不能正常工作:

print("4")
Foo.print_register()

有错误:

Foo.print_register()
TypeError: print_register() missing 1 required positional argument: 'self'

对于测试 2 和 3,我期望如果在类级别定义方法,它也应该在对象级别定义。但是,测试 3 引发了错误。

print("2")
Foo.print_register_class()
print("3")
f.print_register_class()

因此,你能解释一下我对类方法的理解是错误的吗?我希望能够在类或对象上调用方法print_register


也许知道实际上我试图重现以下非常简单的示例可能会有所帮助:

# example without anything fancy:
class Foo:
    list_register_instances = []
    def __init__(self):
        self.__class__.list_register_instances.append(self)

    @classmethod
    def print_register(cls):
        for element in cls.list_register_instances:
            print(element)

我不是对元类做同样的事情吗?类方法可以用于类或对象。

另外,如果您对代码结构有任何提示,我将不胜感激。我一定很不擅长元类的语法。

【问题讨论】:

    标签: python python-3.x class metaclass class-method


    【解决方案1】:

    从根本上说,因为您在元类的实例(您的类)上隐藏了print_register

    所以当您执行Foo.print_register 时,它会找到您在中定义的print_register

    class Foo(metaclass=register):
        ...
    def print_register(self):
        pass
    

    当然,这只是普通函数print_register,它需要self 参数。

    这(几乎)与普通类及其实例会发生的事情相同:

    class Foo:
        def bar(self):
            print("I am a bar")
    
    foo = Foo()
    
    foo.bar = lambda x: print("I've hijacked bar")
    
    foo.bar()
    

    注意:

    In [1]: class Meta(type):
       ...:     def print_register(self):
       ...:         print('hi')
       ...:
    
    In [2]: class Foo(metaclass=Meta):
       ...:     pass
       ...:
    
    In [3]: Foo.print_register()
    hi
    
    In [4]: class Foo(metaclass=Meta):
       ...:     def print_register(self):
       ...:         print('hello')
       ...:
    
    In [5]: Foo.print_register()
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-5-a42427fde947> in <module>
    ----> 1 Foo.print_register()
    
    TypeError: print_register() missing 1 required positional argument: 'self'
    

    但是,您也可以在元类构造函数中执行此操作!

    cls.print_register = meta.print_register
    

    这实际上就像在您的类定义中定义该函数......但不确定您为什么要这样做。

    在做与使用 classmethod 完全相同的事情,classmethod 是一个自定义描述符,它以您需要的方式处理方法与实例的绑定在类或实例上调用它。这与在类和实例上定义方法相同!您可以在您的元类 __new__ 中执行此操作,即 cls.print_register = classmethod(meta.print_register) 并将 def print_register(self) 排除在您的类定义之外:

    import functools
    
    def dec_register(func):
        @functools.wraps(func)
        def wrapper_register(*args, **kwargs):
            (args[0].__class__.list_register_instances).append(args[0])
            return func(*args, **kwargs)
        return wrapper_register
    
    
    dict_register_classes = {}
    class register(type):
        def __new__(meta, name, bases, attrs):
            dict_register_classes[name] = cls = type.__new__(meta, name, bases, attrs)  # assigniation from right to left
            cls.list_register_instances = []
            cls.print_register = classmethod(meta.print_register) # just create the classmethod manually!
            return cls
    
        def print_register(self):
            for element in self.list_register_instances:
                print(element)
    
        def print_register_class(cls):
            for element in cls.list_register_instances:
                print(element)
    
    #
    class Foo(metaclass=register):
        @dec_register
        def __init__(self):
            pass
    

    注意,print_register 不必在您的元类中定义,实际上,在这种情况下,我只需在模块级别定义它:

    def print_register(self):
        for element in self.list_register_instances:
            print(element)
    ...
    
    class register(type):
        def __new__(meta, name, bases, attrs):
            dict_register_classes[name] = cls = type.__new__(meta, name, bases, attrs)  # assigniation from right to left
            cls.list_register_instances = []
            cls.print_register = classmethod(print_register) 
            return cls
    
    ...
    

    我认为你对元类的理解很充分,实际上,据我所知,你对classmethod 的理解是不正确的。如果您想了解classmethod 的工作原理,实际上,方法-实例绑定对于常规函数的工作原理,您需要了解描述符Here's an enlightening link。函数对象是描述符,它们在实例上调用时将实例作为第一个参数绑定到自身(相反,它们创建一个方法对象并返回它,但它基本上是部分应用程序)。 classmethod 对象是另一种描述符,当在类或实例上调用时,它将 绑定到它装饰的函数的第一个参数。该链接描述了如何使用纯 python 编写classmethod

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-04-30
      相关资源
      最近更新 更多