我希望在下面的代码中,当我输入
instance_of_A = A(
时,假定参数的名称是 init_argumentA
而不是 *meta_args, **meta_kwargs
。但不幸的是,显示了元类的 __call__
方法的参数。
class Meta(type):
def __call__(cls,*meta_args,**meta_kwargs):
# Something here
return super().__call__(*meta_args, **meta_kwargs)
class A(metaclass = Meta):
def __init__(self,init_argumentA):
# something here
class B(metaclass = Meta):
def __init__(self,init_argumentB):
# something here
我搜索了解决方案并发现了问题如何动态更改子类中方法的签名? 和 签名更改装饰器:正确记录附加参数。但没有一个,似乎完全是我想要的。第一个链接使用检查来更改赋予函数的变量数量,但我似乎无法让它适用于我的情况,我认为必须有一个更明显的解决方案。 第二个并不完全是我想要的,但这种方式可能是一个不错的选择。
编辑:我在 Spyder 工作。我想要这个是因为我有数千个元类型的类,每个类都有不同的参数,这是不可能记住的,所以我们的想法是用户在看到正确的参数出现时可以记住它。
使用您提供的代码,您可以更改
Meta
类
class Meta(type):
def __call__(cls, *meta_args, **meta_kwargs):
# Something here
return super().__call__(*meta_args, **meta_kwargs)
class A(metaclass=Meta):
def __init__(self, x):
pass
到
import inspect
class Meta(type):
def __call__(cls, *meta_args, **meta_kwargs):
# Something here
# Restore the signature of __init__
sig = inspect.signature(cls.__init__)
parameters = tuple(sig.parameters.values())
cls.__signature__ = sig.replace(parameters=parameters[1:])
return super().__call__(*meta_args, **meta_kwargs)
现在 IPython 或某些 IDE 将向您显示正确的签名。
我发现 @johnbaltis 的答案是 99%,但并不完全是确保签名到位所需要的。
如果我们使用
__init__
而不是 __call__
,如下所示,我们会得到所需的行为
import inspect
class Meta(type):
def __init__(cls, clsname, bases, attrs):
# Restore the signature
sig = inspect.signature(cls.__init__)
parameters = tuple(sig.parameters.values())
cls.__signature__ = sig.replace(parameters=parameters[1:])
return super().__init__(clsname, bases, attrs)
def __call__(cls, *args, **kwargs):
super().__call__(*args, **kwargs)
print(f'Instanciated: {cls.__name__}')
class A(metaclass=Meta):
def __init__(self, x: int, y: str):
pass
这将正确给出:
In [12]: A?
Init signature: A(x: int, y: str)
Docstring: <no docstring>
Type: Meta
Subclasses:
In [13]: A(0, 'y')
Instanciated: A
好吧 - 尽管你想要的原因似乎是模棱两可的,因为任何“诚实的”Python检查工具都应该显示
__init__
签名,你所要求的需要的是为每个类生成一个动态元类,其中 __call__
方法与类自己的 __init__
方法具有相同的签名。
要伪造
__init__
上的 __call__
签名,我们可以简单地使用 functools.wraps
。 (但您可能想检查答案
https://stackoverflow.com/a/33112180/108205)
对于动态创建额外的元类,可以在
__metaclass__.__new__
本身上完成,只需注意避免在 __new__
方法上无限递归 -threads.Lock 可以以比简单的更一致的方式帮助实现这一点全局标志。
from functools import wraps
creation_locks = {}
class M(type):
def __new__(metacls, name, bases, namespace):
lock = creation_locks.setdefault(name, Lock())
if lock.locked():
return super().__new__(metacls, name, bases, namespace)
with lock:
def __call__(cls, *args, **kwargs):
return super().__call__(*args, **kwargs)
new_metacls = type(metacls.__name__ + "_sigfix", (metacls,), {"__call__": __call__})
cls = new_metacls(name, bases, namespace)
wraps(cls.__init__)(__call__)
del creation_locks[name]
return cls
我最初想到使用元类
__new__
参数的命名参数来控制递归,但随后它将传递给创建的类的 __init_subclass__
方法(这将导致错误) - 因此使用 Lock。
不确定这是否对作者有帮助,但就我而言,我需要将
inspect.signature(Klass)
更改为 inspect.signature(Klass.__init__)
才能获得类 __init__
的签名,而不是元类 __call__
。
虽然这个问题是 6 年前提出的,但我想指出这个更简单的实现也有效:
from functools import wraps
class Meta(type):
@wraps(super().__call__)
def __call__(cls, *meta_args, **meta_kwargs):
# Something here
return super().__call__(*meta_args, **meta_kwargs)
class A(metaclass=Meta):
def __init__(self, x):
"""Documentation for 'A.__init__'"""
pass