装饰器以将新类生成到命名空间

时间:2018-06-28 18:34:26

标签: python decorator

我需要的特定用例是弃用类名。

假设我们在较早的版本中有类A,并且我们想弃用其名称,但保持向后兼容性:

class A(B):
    def __init__(self, *args, **kwargs):
        warnings.warn('deprecation!')
        super(A, self).__init__(*args, **kwargs)

...并且B现在具有正确的实现。

当我们创建类A时,我们将在此处遇到弃用警告。我们还可以将deprecated模块用于__init__上的装饰器。

但是,我想跳过此过程并编写更少的代码,并希望实现以下目标:

@deprecated_alias('A')
class B:
    # ... do something

我可以以某种方式将类名注入模块级名称空间中,以便可以像这样使用A吗?

2 个答案:

答案 0 :(得分:4)

  

我可以以某种方式将类名注入模块级名称空间中,以便可以像这样使用A吗?

是的。类装饰器应:

  • 使用__init__的三参数调用,使用覆盖的type方法创建新类型
  • 获取原始类的模块sys.modules[original_class.__module__]
  • 使用setattr
  • 将新类绑定到模块名称空间中
  • 保持原班不变

示例:

import sys

def deprecated_alias(name):
    def decorator(class_):
        mod = sys.modules[class_.__module__]
        if hasattr(mod, name):
            raise Exception('uhoh, name collision')
        NewClass = type(name, (class_,), {'__init__': ...})
        setattr(mod, name, NewClass)
        return class_
    return decorator

@deprecated_alias('A')
class B:
    pass

我不推荐这种方法-太神奇了。它将混淆IDE并破坏自动完成。

也许不是那么神奇的方法?也可以将其制成装饰器,如果需要控制继承的详细信息,请使用__subclasscheck__ / __subclasshook__

class A(B):
    def __init__(self, *args, **kwargs):
        warnings.warn('deprecation!')
        return B(*args, **kwargs)

答案 1 :(得分:2)

虽然这并不是您所要求的,但它的魔力却少得多,并且最终代码行数相同。它也更加明确:

import warnings

def deprecated(DeprecatedByClass):
    class Deprecated(DeprecatedByClass):
        def __new__(cls, *args, **kwargs):
            warnings.warn("deprecation!")
            return super(Deprecated, cls).__new__(cls, *args, **kwargs)

    return Deprecated

然后您可以像这样使用它:

class B:
    pass

A = deprecated(B)