简体   繁体   English

如何将参数从类定义传递给元类?

[英]How to pass arguments to the metaclass from the class definition?

I'm trying to dynamically generate classes in python 2.7, and am wondering if you can easily pass arguments to the metaclass from the class object.我正在尝试在 python 2.7 中动态生成类,并且想知道您是否可以轻松地将参数从类对象传递给元类。

I've read this post, which is awesome, but doesn't quite answer the question.我读过这篇文章,很棒,但没有完全回答问题。 at the moment I am doing:目前我正在做:

def class_factory(args, to, meta_class):
    Class MyMetaClass(type):
        def __new__(cls, class_name, parents, attrs):
            attrs['args'] = args
            attrs['to'] = to
            attrs['eggs'] = meta_class

    class MyClass(object):
        metaclass = MyMetaClass
        ...

but this requires me to do the following但这需要我做以下事情

MyClassClass = class_factory('spam', 'and', 'eggs')
my_instance = MyClassClass()

Is there a cleaner way of doing this?有更清洁的方法吗?

While the question is for Python 2.7 and already has an excellent answer, I had the same question for Python 3.3 and this thread was the closest thing to an answer I could find with Google.虽然这个问题是针对 Python 2.7 的,并且已经有了一个很好的答案,但我对 Python 3.3 也有同样的问题,这个线程是我在谷歌上找到的最接近答案的东西。 I found a better solution for Python 3.x by digging through the Python documentation, and I'm sharing my findings for anyone else coming here looking for a Python 3.x version.通过深入研究 Python 文档,我找到了适用于 Python 3.x 的更好解决方案,并且我将与来这里寻找 Python 3.x 版本的其他人分享我的发现。

Passing Arguments to the Metaclass in Python 3.x将参数传递给 Python 3.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, **kwargs):
    #kwargs = {"myArg1": 1, "myArg2": 2}
    return super().__prepare__(name, bases, **kwargs)

  def __new__(metacls, name, bases, namespace, **kwargs):
    #kwargs = {"myArg1": 1, "myArg2": 2}
    return super().__new__(metacls, name, bases, namespace)
    #DO NOT send "**kwargs" 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, **kwargs):
    #myArg1 = 1  #Included as an example of capturing metaclass args as positional args.
    #kwargs = {"myArg2": 2}
    super().__init__(name, bases, namespace)
    #DO NOT send "**kwargs" 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 kwargs out of the call to type.__new__ and type.__init__ (Python 3.5 and older; see "UPDATE") or will get you a TypeError exception due to passing too many arguments.您必须将kwargs排除在对type.__new__type.__init__ (Python 3.5 及更早版本;参见“UPDATE”)的调用之外,否则会因传递太多参数而导致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 **kwargs ), so defining type.__prepare__ is optional. type.__prepare__似乎可以优雅地处理额外的关键字参数(因此我在示例中传递它们的原因,以防万一有一些我不知道依赖于**kwargs的功能),所以定义type.__prepare__是可选的。

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异常)。

Breakdown分解

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

class MyClass(metaclass=MyMetaClass):
  pass

This statement roughly translates to:这句话大致翻译为:

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

...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 kwargs is any uncaptured keyword arguments (an empty dict {} in this case). ...其中metaclass是您传入的“元类”参数的值, name是您的类的字符串名称( 'MyClass' ), bases是您传入的任何基类(此中的零长度元组() case), kwargs是任何未捕获的关键字参数(在这种情况下是一个空的dict {} )。

Breaking this down further, the statement roughly translates to:进一步分解,该声明大致翻译为:

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

...where kwargs is always the dict of uncaptured keyword arguments we passed in to the class definition. ...其中kwargs始终是我们传递给类定义的未捕获关键字参数的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 的“自定义类创建”文档

Yes, there's an easy way to do it.是的,有一种简单的方法可以做到这一点。 In the metaclass's __new__() method just check in the class dictionary passed as the last argument.在元类的__new__()方法中,只需检查作为最后一个参数传递的类字典。 Anything defined in the class statement will be there. class语句中定义的任何内容都将存在。 For example:例如:

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)

class MyClass(object):
    __metaclass__ = MyMetaClass
    meta_args = ['spam', 'and', 'eggs']

myobject = MyClass()

from pprint import pprint
pprint(dir(myobject))
print myobject.args, myobject.to, myobject.eggs

Output:输出:

['__class__',
 '__delattr__',
 '__dict__',
 '__doc__',
 '__format__',
 '__getattribute__',
 '__hash__',
 '__init__',
 '__metaclass__',
 '__module__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__',
 'args',
 'eggs',
 'to']
spam and eggs

Update更新

The code above will only work in Python 2 because syntax for specifying a metaclass was changed in an incompatible way in Python 3.上面的代码只能在 Python 2 中工作,因为指定元类的语法在 Python 3 中以不兼容的方式发生了变化。

To make it work in Python 3 (but no longer in Python 2) is super simple to do and only requires changing the definition of MyClass to:要使其在 Python 3 中工作(但不再在 Python 2 中工作)非常简单,只需要将MyClass的定义更改为:

class MyClass(metaclass=MyMetaClass):
    meta_args = ['spam', 'and', 'eggs']

It's also possible to workaround the syntax differences and produce code that works in both Python 2 and 3 by creating base classes "on-the-fly" which involves explicitly invoking the metaclass and using the class that is created as the base class of the one being defined.也可以通过“即时”创建基类来解决语法差异并生成适用于 Python 2 和 3 的代码,这涉及显式调用元类并使用创建的类作为基类被定义。

class MyClass(MyMetaClass("NewBaseClass", (object,), {})):
    meta_args = ['spam', 'and', 'eggs']

Class construction in Python 3 has also been modified and support was added that allows other ways of passing arguments, and in some cases using them might be easier than the technique shown here. Python 3 中的类构造也进行了修改,并添加了允许其他传递参数方式的支持,在某些情况下,使用它们可能比此处显示的技术更容易。 It all depends on what you're trying to accomplish.这完全取决于您要完成的任务。

See @John Crawford's detailed answer for a description of of the process in the new versions of Python.有关新版本 Python 中流程的描述,请参阅@John Crawford 的详细回答

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

相关问题 如何从Python 3.x中的类定义传递参数到元类? - How to pass arguments to the metaclass from the class definition in Python 3.x? 使用元类来替换类定义? - Using a metaclass to substitute a class definition? python 元类冲突:除了父 class 之外,如何将附加参数传递给元 class - python metaclass conflict : how to pass additional parameter to meta class apart from the parent class 如何从Metaclass取消注册类条目? - How to deregister a class entry from Metaclass? 为什么元类的定义改变了经典类的含义? - Why the metaclass definition changed the mro of classic class? 有没有办法在类定义后设置元类? - Is there a way to set metaclass after the class definition? 从抽象类继承时如何避免重复参数定义? - How to avoid duplicating arguments definition when inheriting from abstract class? 如何确定 class 的元类? - How to determine the metaclass of a class? Python - 将 class Z099FB995346F31C749F6E40DB0F395EZ 中的位置 arguments 传递到 metaclass - Python - passing positional arguments in class header to metaclass 如何在没有元类的情况下将不同的 arguments 传递给 __new__ vs __init__ - How to pass different arguments to __new__ vs __init__ without a metaclass
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM