我希望在下面的代码中输入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
我搜索了一个解决方案,发现了问题How to dynamically change signatures of method in subclass? 和Signature-changing decorator: properly documenting additional argument。但没有,似乎完全是我想要的。第一个链接使用inspect来改变赋给函数的变量数量,但我似乎无法让它适用于我的情况,我认为必须有一个更明显的解决方案。 第二个不完全是我想要的,但这样的东西可能是一个很好的选择。
编辑:我在Spyder工作。我想要这个,因为我有数千个Meta类的类,每个类都有不同的参数,这是不可能记住的,所以想法是用户在看到正确的参数出现时就能记住它。
答案 0 :(得分:1)
使用您提供的代码,您可以更改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将为您显示正确的签名。
答案 1 :(得分:0)
好的 - 即使你想要它的原因似乎是模棱两可的,因为任何“诚实”的Python检查工具都应该显示__init__
签名,你要求的是每个类你需要什么生成一个动态元类,__call__
方法与该类自己的__init__
方法具有相同的签名。
如果伪造__init__
上的__call__
签名,我们只需使用functools.wraps
。 (但你可能想查看答案
https://stackoverflow.com/a/33112180/108205)
为了动态创建一个额外的元类,可以在__metaclass__.__new__
本身上完成,只需要注意__new__
方法上的avoud无限递归 - 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
我最初想过在metaclass __new__
参数中使用命名参数来控制递归,但是它会被传递给创建的类'__init_subclass__
方法(这将导致错误) - 所以Lock使用。
答案 2 :(得分:0)
不确定这是否对作者有帮助,但就我而言,我需要将inspect.signature(Klass)
更改为inspect.signature(Klass.__init__)
以获得类__init__
而不是元类__call__
的签名。
答案 3 :(得分:0)
我发现 @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