简体   繁体   English

Python - 将 class Z099FB995346F31C749F6E40DB0F395EZ 中的位置 arguments 传递到 metaclass

[英]Python - passing positional arguments in class header to metaclass

Passing positional arguments in form *(1, 2, 3) is syntactically alowed after keyword arguments.在关键字 arguments 之后,在语法上允许以*(1, 2, 3)形式传递位置 arguments。 This code:这段代码:

class Meta(type):
    def __new__(meta, *args, **kwargs):
        return super().__new__(meta, *args)

class A(metaclass=Meta, sandwich='tasty', *(10,)): pass

gives me an error:给我一个错误:

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Why is positional argument intermixed with superclass list?为什么位置参数与超类列表混合在一起? I think Python should be able to handle this special case because such implementation forces metaclass user to pass arguments in keyword-only manner and such interface choice is taken away from metaclass programmer.我认为 Python 应该能够处理这种特殊情况,因为这样的实现强制元类用户以仅关键字的方式传递 arguments 并且这样的接口选择从元类程序员中消失了。

tl;dr tl;博士

Starred expressions with a single asterisk * in class definitions get processed before the keyword arguments even if they appear after the keyword arguments. class 定义中带有单个星号*的加星号表达式在关键字 arguments 之前处理,即使它们出现在关键字 arguments 之后。 This leads to *(10,) being interpretated as base class and since 10 is not a valid base class the error message occurs.这导致*(10,)被解释为基数 class 并且由于10不是有效的基数 class,因此会出现错误消息。

Explanation解释

The first point is that the argument list of the __new__ method in the metaclass contains *args .第一点是元类中__new__方法的参数列表包含*args To better understand what's going on, *args could be replaced with name, bases, namespace which are the arguments that are passed to a metaclass when it is referenced in a class definition.为了更好地理解发生了什么, *args可以替换为name, bases, namespace ,它们是 arguments,当它在 class 定义中被引用时传递给元类。

So replace所以更换

class Meta(type):
    def __new__(meta, *args, **kwargs):
        return super().__new__(meta, *args)

with

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

The second point is that the class definition contains a starred expression.第二点是 class 定义包含星号表达式。

class A(metaclass=Meta, sandwich='tasty', *(10,)): pass

A class definition however is not a function definition.然而, class定义不是function定义。 They may appear similar, but they follow different rules because they serve different purposes.它们可能看起来相似,但它们遵循不同的规则,因为它们用于不同的目的。

A function definition allows arbitrary positional arguments, keyword arguments and starred expressions with single asterisk * (variable number of positional arguments) and double asterisk ** (variable number of keyword arguments). function定义允许任意位置的 arguments、关键字 arguments 和带星号的表达式,带有单个星号* (可变数量的位置参数)和双星号** (可变数量)。

A class definition usually includes a list of all base classes. class定义通常包括所有基类的列表。 A special case here is being able to define a metaclass with a keyword argument metaclass=Meta and an arbitrary number of further keyword arguments without any special meaning.这里的一个特殊情况是能够使用关键字参数metaclass=Meta和任意数量的进一步关键字 arguments 定义一个元类,而没有任何特殊含义。 The starred expression with double asterisk ** (variable number of keyword arguments) is also supported.还支持带有双星号** (可变数量的关键字参数)的星号表达式。

A class definition is an executable statement. class 定义是一个可执行语句。 The inheritance list usually gives a list of base classes (see Metaclasses for more advanced uses), so each item in the list should evaluate to a class object which allows subclassing. inheritance 列表通常提供基类列表(请参阅元类以了解更高级的用途),因此列表中的每个项目都应评估为 class object 允许子类化。

https://docs.python.org/3/reference/compound_stmts.html#class-definitions https://docs.python.org/3/reference/compound_stmts.html#class-definitions

I did not find official documentation that defines the rules when using starred expressions in class definitions, but the rule for using them in calls coincidently fits with the observable behaviour.在 class 定义中使用星号表达式时,我没有找到定义规则的官方文档,但是在调用中使用它们的规则恰好符合可观察到的行为。

(...) although the *expression syntax may appear after explicit keyword arguments, it is processed before the keyword arguments – see below). (...) 虽然 * 表达式语法可能出现显式关键字 arguments 之后,但它在关键字 arguments之前处理 - 见下文)。 So:所以:

def f(a, b):
...     print(a, b)
...
>>> f(b=1, *(2,))
2 1
>>> f(a=1, *(2,))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got multiple values for keyword argument 'a'
>>> f(1, *(2,))
1 2

https://docs.python.org/dev/reference/expressions.html#callshttps://docs.python.org/dev/reference/expressions.html#calls

Since metaclass is a keyword argument the starred expression is processed before metaclass, so using由于元类是一个关键字参数,星号表达式在元类之前处理,所以使用

class A(metaclass=Meta, sandwich='tasty', *(10,)):
    pass

corresponds to对应于

class A(10, metaclass=Meta, sandwich='tasty'):
    pass

which can be seen in the abstract syntax tree可以在抽象语法树中看到

>>> import ast
>>> source = '''
... class Meta(type):
...     def __new__(meta, *args, **kwargs):
...         return super().__new__(meta, *args)
... 
... class A(metaclass=Meta, sandwich='tasty', *(10,)): pass
... '''
>>> 
>>> ast.dump(ast.parse(source))
"Module(body=[ClassDef(name='Meta', bases=[Name(id='type', ctx=Load())], keywords=[], body=[FunctionDef(name='__new__', args=arguments(posonlyargs=[], args=[arg(arg='meta')], vararg=arg(arg='args'), kwonlyargs=[], kw_defaults=[], kwarg=arg(arg='kwargs'), defaults=[]), body=[Return(value=Call(func=Attribute(value=Call(func=Name(id='super', ctx=Load()), args=[], keywords=[]), attr='__new__', ctx=Load()), args=[Name(id='meta', ctx=Load()), Starred(value=Name(id='args', ctx=Load()), ctx=Load())], keywords=[]))], decorator_list=[])], decorator_list=[]), ClassDef(name='A', bases=[Starred(value=Tuple(elts=[Constant(value=10)], ctx=Load()), ctx=Load())], keywords=[keyword(arg='metaclass', value=Name(id='Meta', ctx=Load())), keyword(arg='sandwich', value=Constant(value='tasty'))], body=[Pass()], decorator_list=[])], type_ignores=[])"

where bases is defined as a starred expression containing the tuple (10,)其中bases定义为包含元组(10,)的星号表达式

bases=[Starred(value=Tuple(elts=[Constant(value=10)], ctx=Load()), ctx=Load())]

So any non-keyword arguments in the class definition are interpreted as base classes and since 10 is not a valid base class this results in the mentioned error message.因此,class 定义中的任何非关键字 arguments 都被解释为基类,并且由于10不是有效的基类 class,因此会导致上述错误消息。

TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases

Passing arguments to metaclass将 arguments 传递给元类

I'm not sure if it considered best practice but defining the variable positional arguments as class attribute might be meaningful way to handle things.我不确定它是否考虑了最佳实践,但将变量位置 arguments 定义为 class 属性可能是处理事情的有意义的方式。

class Meta(type):
    def __new__(self, name, bases, classdict, *args, **kwargs):
        for key in kwargs:
            print(key + '=' + kwargs[key])
        print('*args', *classdict['args'])
        return super().__new__(self, name, bases, classdict)

class A(metaclass=Meta, sandwich='tasty'):
    args = (10,)

I am well aware of the age of the question, but since the first two points could possibly be of more general interest I chose to reply with an answer.我很清楚这个问题的年代,但由于前两点可能更普遍,我选择回答。

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM