【问题标题】:__call__ from metaclass shadows signature of __init__来自 __init__ 的元类阴影签名的 __call__
【发布时间】:2018-09-19 06:51:27
【问题描述】:

我想在下面的代码中输入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。但是没有,似乎完全是我想要的。第一个链接使用检查来更改赋予函数的变量数量,但我似乎无法让它适用于我的情况,我认为必须有一个更明显的解决方案。 第二个不完全是我想要的,但这种方式可能是一个不错的选择。

编辑:我在 Spyder 工作。我想要这个,因为我有数千个 Meta 类型的类,每个类都有不同的参数,这是不可能记住的,所以这个想法是当看到正确的参数出现时用户可以记住它。

【问题讨论】:

  • 您必须更具体地说明应该在何时显示它们。您是否在 Python shell、IDLE、IPython、Jupyter、Visual Studio 代码或其他工具中工作?您希望它们完全显示在 IDE 中,还是与 help() 或文档构建有关?
  • 感谢反馈,我更新了问题,希望现在更清楚
  • 这似乎是 IDE 的问题,而不是 Python 本身的问题
  • 好的,我无法重现。如果我在 spyder 中使用您的代码(在__init__s 中使用pass),当我打印A(B( 时,它会显示A.__init__B.__init__ 的签名。您能否添加更多信息,例如 spyder 版本、python 版本、工作代码示例(您的代码会产生 IndentationError)以及不正确建议的屏幕截图?

标签: python spyder metaclass


【解决方案1】:

我发现@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

【讨论】:

    【解决方案2】:

    不确定这是否对作者有帮助,但在我的情况下,我需要将 inspect.signature(Klass) 更改为 inspect.signature(Klass.__init__) 以获取 __init__ 类的签名,而不是元类 __call__

    【讨论】:

      【解决方案3】:

      使用您提供的代码,您可以更改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 将显示正确的签名。

      【讨论】:

        【解决方案4】:

        好的 - 尽管您想要这样做的原因似乎是模棱两可的,因为任何“诚实”的 Python 检查工具都应该显示__init__ 签名,但您所要求的是,您需要为每个类生成一个动态元类,__call__ 方法与类自己的__init__ 方法具有相同的签名。

        为了伪造__call__ 上的__init__ 签名,我们可以简单地使用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 .

        【讨论】:

          猜你喜欢
          • 2011-11-21
          • 2012-12-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-04-07
          • 1970-01-01
          • 2023-03-31
          • 2012-03-28
          相关资源
          最近更新 更多