__call__来自__init__的元类阴影签名

时间:2018-04-09 19:28:22

标签: python spyder metaclass

我希望在下面的代码中输入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类的类,每个类都有不同的参数,这是不可能记住的,所以想法是用户在看到正确的参数出现时就能记住它。

4 个答案:

答案 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