__call__ 来自 __init__ 的元类阴影签名
__call__ from metaclass shadows signature of __init__
我想在下面的代码中输入 instance_of_A = A(
,假定参数的名称是 init_argumentA
而不是 *meta_args, **meta_kwargs
。但不幸的是,显示了 metaclass 的 __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
我搜索了解决方案并找到了问题如何动态更改子方法的签名class?
和 。但是none,似乎完全就是我想要的。第一个 link 使用 inspect 来更改赋予函数的变量数量,但我似乎无法让它适用于我的情况,我认为必须有一个更明显的解决方案。
第二个不完全是我想要的,但那样的东西可能是一个不错的选择。
编辑:我在 Spyder 工作。我想要这个是因为我有成千上万个 Meta 类型的 classes 并且每个 class 都有不同的参数,这是不可能记住的,所以这个想法是用户在看到正确的参数时可以记住它出现。
好的 - 尽管你想要的原因似乎是模棱两可的,因为任何 "honest" Python 检查工具都应该显示 __init__
签名,需要什么你问的是,对于每个 class 你生成一个动态元 class,__call__
方法具有与 class 自己的 __init__
相同的签名方法。
为了在 __call__
上伪造 __init__
签名,我们可以简单地使用 functools.wraps
。 (但您可能想查看答案
)
并且为了动态创建一个额外的元class,可以在__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
我最初想到的是对 metaclass __new__
参数使用命名参数来控制递归,但随后它会被传递给创建的 class' __init_subclass__
方法(这将导致错误) - 因此 Lock 使用。
使用您提供的代码,您可以更改 Meta
class
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 会显示正确的签名。
不确定这是否对作者有帮助,但在我的情况下,我需要将 inspect.signature(Klass)
更改为 inspect.signature(Klass.__init__)
以获得 class __init__
的签名而不是 metaclass __call__
.
我发现 的答案是 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
我想在下面的代码中输入 instance_of_A = A(
,假定参数的名称是 init_argumentA
而不是 *meta_args, **meta_kwargs
。但不幸的是,显示了 metaclass 的 __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
我搜索了解决方案并找到了问题如何动态更改子方法的签名class?
和
编辑:我在 Spyder 工作。我想要这个是因为我有成千上万个 Meta 类型的 classes 并且每个 class 都有不同的参数,这是不可能记住的,所以这个想法是用户在看到正确的参数时可以记住它出现。
好的 - 尽管你想要的原因似乎是模棱两可的,因为任何 "honest" Python 检查工具都应该显示 __init__
签名,需要什么你问的是,对于每个 class 你生成一个动态元 class,__call__
方法具有与 class 自己的 __init__
相同的签名方法。
为了在 __call__
上伪造 __init__
签名,我们可以简单地使用 functools.wraps
。 (但您可能想查看答案
)
并且为了动态创建一个额外的元class,可以在__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
我最初想到的是对 metaclass __new__
参数使用命名参数来控制递归,但随后它会被传递给创建的 class' __init_subclass__
方法(这将导致错误) - 因此 Lock 使用。
使用您提供的代码,您可以更改 Meta
class
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 会显示正确的签名。
不确定这是否对作者有帮助,但在我的情况下,我需要将 inspect.signature(Klass)
更改为 inspect.signature(Klass.__init__)
以获得 class __init__
的签名而不是 metaclass __call__
.
我发现
如果我们使用 __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