简体   繁体   English

如何从Python 3.x中的类定义传递参数到元类?

[英]How to pass arguments to the metaclass from the class definition in Python 3.x?

This is a Python 3.x version of the How to pass arguments to the metaclass from the class definition? 这是如何从类定义向元类传递参数的Python 3.x版本 question, listed separately by request since the answer is significantly different from Python 2.x. 问题,由请求单独列出,因为答案与Python 2.x有很大不同。


In Python 3.x, how do I pass arguments to a metaclass's __prepare__ , __new__ , and __init__ functions so a class author can give input to the metaclass on how the class should be created? 在Python 3.x中,如何将参数传递给元类的__prepare__ __new____init__ __new____init__函数,以便类作者可以向元类提供有关如何创建类的输入?

As my use case, I'm using metaclasses to enable automatic registration of classes and their subclasses into PyYAML for loading/saving YAML files. 作为我的用例,我正在使用元类来自动将类及其子类注册到PyYAML中以加载/保存YAML文件。 This involves some extra runtime logic not available in PyYAML's stock YAMLObjectMetaClass . 这涉及PyYAML库存YAMLObjectMetaClass没有的额外运行时逻辑。 In addition, I want to allow class authors to optionally specify the tag/tag-format-template that PyYAML uses to represent the class and/or the function objects to use for construction and representation. 另外,我想允许类作者有选择地指定PyYAML用来表示用于构造和表示的类和/或函数对象的标记/标记格式模板。 I've already figured out that I can't use a subclass of PyYAML's YAMLObjectMetaClass to accomplish this--"because we don't have access to the actual class object in __new__ " according to my code comment--so I'm writing my own metaclass that wraps PyYAML's registration functions. 我已经发现我不能使用PyYAML的YAMLObjectMetaClass的子类来完成这个 - “因为根据我的代码注释,我们无法访问__new__的实际类对象 - 所以我正在写我自己的元类包装了PyYAML的注册函数。

Ultimately, I want to do something along the lines of: 最终,我想做的事情是:

from myutil import MyYAMLObjectMetaClass

class MyClass(metaclass=MyYAMLObjectMetaClass):
    __metaclassArgs__ = ()
    __metaclassKargs__ = {"tag": "!MyClass"}

...where __metaclassArgs__ and __metaclassKargs__ would be arguments going to the __prepare__ , __new__ , and __init__ methods of MyYAMLObjectMetaClass when the MyClass class object is getting created. ...当创建MyClass类对象时, __init__ __metaclassArgs____metaclassKargs__将成为__metaclassKargs____prepare__ __new____init__ __new____init__方法的MyYAMLObjectMetaClass

Of course, I could use the "reserved attribute names" approach listed in the Python 2.x version of this question , but I know there is a more elegant approach available. 当然,我可以使用此问题的Python 2.x版本中列出的“保留属性名称”方法,但我知道有一种更优雅的方法可用。

After digging through Python's official documentation, I found that Python 3.x offers a native method of passing arguments to the metaclass, though not without its flaws. 在深入研究Python的官方文档之后,我发现Python 3.x提供了一种将参数传递给元类的本地方法,尽管并非没有缺陷。

Simply add additional keyword arguments to your class declaration: 只需在类声明中添加其他关键字参数:

class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
  pass

...and they get passed into your metaclass like so: ......它们会像这样传递到你的元类中:

class MyMetaClass(type):

  @classmethod
  def __prepare__(metacls, name, bases, **kargs):
    #kargs = {"myArg1": 1, "myArg2": 2}
    return super().__prepare__(name, bases, **kargs)

  def __new__(metacls, name, bases, namespace, **kargs):
    #kargs = {"myArg1": 1, "myArg2": 2}
    return super().__new__(metacls, name, bases, namespace)
    #DO NOT send "**kargs" to "type.__new__".  It won't catch them and
    #you'll get a "TypeError: type() takes 1 or 3 arguments" exception.

  def __init__(cls, name, bases, namespace, myArg1=7, **kargs):
    #myArg1 = 1  #Included as an example of capturing metaclass args as positional args.
    #kargs = {"myArg2": 2}
    super().__init__(name, bases, namespace)
    #DO NOT send "**kargs" to "type.__init__" in Python 3.5 and older.  You'll get a
    #"TypeError: type.__init__() takes no keyword arguments" exception.

You have to leave kargs out of the call to type.__new__ and type.__init__ (Python 3.5 and older; see "UPDATE" below) or will get you a TypeError exception due to passing too many arguments. 您必须将kargs从调用中type.__new__type.__new__type.__init__ (Python 3.5及更早版本;请参阅下面的“更新”)或由于传递过多参数而导致出现TypeError异常。 This means that--when passing in metaclass arguments in this manner--we always have to implement MyMetaClass.__new__ and MyMetaClass.__init__ to keep our custom keyword arguments from reaching the base class type.__new__ and type.__init__ methods. 这意味着 - 当以这种方式传递元类参数时 - 我们总是必须实现MyMetaClass.__new__MyMetaClass.__init__以保持我们的自定义关键字参数不会到达基类type.__new__type.__init__方法。 type.__prepare__ seems to handle the extra keyword arguments gracefully (hence why I pass them through in the example, just in case there's some functionality I don't know about that relies on **kargs ), so defining type.__prepare__ is optional. type.__prepare__似乎优雅地处理额外的关键字参数(因此我在示例中传递它们,以防万一有一些我不知道的功能依赖于**kargs ),因此定义type.__prepare__是可选的。

UPDATE UPDATE

In Python 3.6, it appears type was adjusted and type.__init__ can now handle extra keyword arguments gracefully. 在Python 3.6中,它显示type已调整, type.__init__现在可以优雅地处理额外的关键字参数。 You'll still need to define type.__new__ (throws TypeError: __init_subclass__() takes no keyword arguments exception). 您仍然需要定义type.__new__ TypeError: __init_subclass__() takes no keyword arguments抛出TypeError: __init_subclass__() takes no keyword arguments异常)。

Breakdown 分解

In Python 3, you specify a metaclass via keyword argument rather than class attribute: 在Python 3中,您通过关键字参数而不是class属性指定元类:

class MyClass(metaclass=MyMetaClass):
  pass

This statement roughly translates to: 该声明大致转化为:

MyClass = metaclass(name, bases, **kargs)

...where metaclass is the value for the "metaclass" argument you passed in, name is the string name of your class ( 'MyClass' ), bases is any base classes you passed in (a zero-length tuple () in this case), and kargs is any uncaptured keyword arguments (an empty dict {} in this case). ...其中metaclass是您传入的“metaclass”参数的值, name是您的类的字符串名称( 'MyClass' ), bases是您传入的任何基类(此处为零长度元组() case), kargs是任何未捕获的关键字参数(在这种情况下为空dict {} )。

Breaking this down further, the statement roughly translates to: 进一步打破这一点,该声明大致转化为:

namespace = metaclass.__prepare__(name, bases, **kargs)  #`metaclass` passed implicitly since it's a class method.
MyClass = metaclass.__new__(metaclass, name, bases, namespace, **kargs)
metaclass.__init__(MyClass, name, bases, namespace, **kargs)

...where kargs is always the dict of uncaptured keyword arguments we passed in to the class definition. ...其中kargs总是我们传递给类定义的未捕获关键字参数的dict

Breaking down the example I gave above: 打破我上面给出的例子:

class C(metaclass=MyMetaClass, myArg1=1, myArg2=2):
  pass

...roughly translates to: ...大致翻译为:

namespace = MyMetaClass.__prepare__('C', (), myArg1=1, myArg2=2)
#namespace={'__module__': '__main__', '__qualname__': 'C'}
C = MyMetaClass.__new__(MyMetaClass, 'C', (), namespace, myArg1=1, myArg2=2)
MyMetaClass.__init__(C, 'C', (), namespace, myArg1=1, myArg2=2)

Most of this information came from Python's Documentation on "Customizing Class Creation" . 大部分信息来自Python关于“自定义类创建”的文档

Here's a version of the code from my answer to that other question about metaclass arguments which has been updated so that it'll work in both Python 2 and 3. It essentially does the same thing that Benjamin Peterson's six module's with_metaclass() function does — which namely is to explicitly create a new base class using the desired metaclass on-the-fly, whenever needed and thereby avoiding errors due to the metaclass syntax differences between the two versions of Python (because the way to do that didn't change). 下面是我的回答是一个代码的版本, 其他问题已更新,它会在这两个 Python 2 3的工作有关元类的论点它本质上是做本杰明·彼得森的同样的事情, 6个模块的with_metaclass()函数的作用-即在需要时使用所需的元类显式创建一个新的基类,从而避免由于两个版本的Python之间的元类语法差异而导致的错误(因为这样做的方式没有改变) 。

from __future__ import print_function
from pprint import pprint

class MyMetaClass(type):
    def __new__(cls, class_name, parents, attrs):
        if 'meta_args' in attrs:
            meta_args = attrs['meta_args']
            attrs['args'] = meta_args[0]
            attrs['to'] = meta_args[1]
            attrs['eggs'] = meta_args[2]
            del attrs['meta_args'] # clean up
        return type.__new__(cls, class_name, parents, attrs)

# Creates base class on-the-fly using syntax which is valid in both
# Python 2 and 3.
class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
    meta_args = ['spam', 'and', 'eggs']

myobject = MyClass()

pprint(vars(MyClass))
print(myobject.args, myobject.to, myobject.eggs)

Output: 输出:

dict_proxy({'to': 'and', '__module__': '__main__', 'args': 'spam',
            'eggs': 'eggs', '__doc__': None})
spam and eggs

In Python 3, you specify a metaclass via keyword argument rather than class attribute: 在Python 3中,您通过关键字参数而不是class属性指定元类:

It's worth to say, that this style is not backward compatible to python 2. If you want to support both python 2 and 3, you should use: 值得一提的是,这种风格并不向后兼容python 2.如果你想同时支持python 2和3,你应该使用:

from six import with_metaclass
# or
from future.utils import with_metaclass

class Form(with_metaclass(MyMetaClass, object)):
    pass

Here's the simplest way to pass arguments to a metaclass in Python 3: 这是在Python 3中将参数传递给元类的最简单方法:

Python 3.x Python 3.x

class MyMetaclass(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        return super().__new__(mcs, name, bases, namespace)

    def __init__(cls, name, bases, namespace, custom_arg='default'):
        super().__init__(name, bases, namespace)

        print('Argument is:', custom_arg)


class ExampleClass(metaclass=MyMetaclass, custom_arg='something'):
    pass

You can also create a base class for metaclasses that only use __init__ with extra arguments: 您还可以为仅使用带有额外参数的__init__元类创建基类:

class ArgMetaclass(type):
    def __new__(mcs, name, bases, namespace, **kwargs):
        return super().__new__(mcs, name, bases, namespace)


class MyMetaclass(ArgMetaclass):
    def __init__(cls, name, bases, namespace, custom_arg='default'):
        super().__init__(name, bases, namespace)

        print('Argument:', custom_arg)


class ExampleClass(metaclass=MyMetaclass, custom_arg='something'):
    pass

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将参数从类定义传递给元类? - How to pass arguments to the metaclass from the class definition? 如何使用Python 3.x将回调及其参数从包装函数传递给装饰器? - How to pass callbacks and their arguments from wrapped function to decorator with Python 3.x? Python 3.x - 如何从需要参数的函数返回值 - Python 3.x - How to return a value from a function requiring arguments 如何使用变量 arguments / exec() 返回另一个 function 中的定义的子函数? (Python 3.x) - How to return a sub-function with variable arguments / exec() a definition inside another function ? (Python 3.x) python 元类冲突:除了父 class 之外,如何将附加参数传递给元 class - python metaclass conflict : how to pass additional parameter to meta class apart from the parent class Python 3.x:如何从类中保存和加载数据 - Python 3.x: How to save and load data from within a class 如何从带参数构建的 3.x 运行 python 2.x 函数? PSS/E - How can i run a python 2.x function from a 3.x built with arguments? PSS/E 从python 3.x中的父类继承 - Inheriting from a parent class in python 3.x Python - 将 class Z099FB995346F31C749F6E40DB0F395EZ 中的位置 arguments 传递到 metaclass - Python - passing positional arguments in class header to metaclass 如何在Python 3.x中将未知参数应用于函数? - How do I apply unknown arguments to a function in Python 3.x?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM