如何警告类(名称)弃用

75 投票
7 回答
47149 浏览
提问于 2025-04-17 11:05

我把一个库里的Python类改了名字。为了让大家在一段时间内还能用旧名字,我想给用户一个提示,告诉他们这个名字已经过时了,以后会被删掉。

我觉得为了保持向后兼容,使用一个别名就可以了,像这样:

class NewClsName:
    pass

OldClsName = NewClsName

但是我不知道怎么优雅地标记OldClsName为过时。也许我可以把OldClsName做成一个函数,发出一个警告(记录到日志里),然后根据参数创建NewClsName的对象(用*args**kvargs),但这样似乎不够优雅(或者说它其实是优雅的?)。

不过,我不知道Python标准库的过时警告是怎么工作的。我想可能有一些很好的方法来处理过时的情况,比如根据解释器的命令行选项来决定是把它当成错误处理还是静默处理。

我的问题是:怎么提醒用户使用了一个过时的类别名(或者说过时的类)。

编辑:我试过用函数的方法,但不太适合我,因为这个类有一些类方法(工厂方法),在OldClsName被定义为函数时是无法调用的。以下代码就不行:

class NewClsName(object):
    @classmethod
    def CreateVariant1( cls, ... ):
        pass

    @classmethod
    def CreateVariant2( cls, ... ):
        pass

def OldClsName(*args, **kwargs):
    warnings.warn("The 'OldClsName' class was renamed [...]",
                  DeprecationWarning )
    return NewClsName(*args, **kwargs)

OldClsName.CreateVariant1( ... )

原因是:

AttributeError: 'function' object has no attribute 'CreateVariant1'

难道继承是我唯一的选择吗?说实话,这看起来不太干净——它通过引入不必要的派生影响了类的层次结构。此外,OldClsName并不是NewClsName,在大多数情况下这不是问题,但在一些写得不好的代码中使用这个库时可能会造成麻烦。

我也可以创建一个无关的OldClsName类,并在里面实现构造函数和所有类方法的包装,但在我看来这更糟糕。

7 个回答

13

在Python 3.6及以上版本中,你可以很简单地处理关于子类的警告:

from warnings import warn

class OldClassName(NewClassName):
    def __init_subclass__(self):
        warn("Class has been renamed NewClassName", DeprecationWarning, 2)

重载(也就是重新定义)__new__方法应该可以让你在直接调用旧类的构造函数时发出警告,不过我现在还没测试过这个,因为我现在不需要。

23

请查看一下 warnings.warn 这个链接。

在这里,你会看到文档中的一个例子是关于弃用警告的:

def deprecation(message):
    warnings.warn(message, DeprecationWarning, stacklevel=2)
49

也许我可以把OldClsName做成一个函数,这个函数会在日志里发出警告,并根据参数(使用*args和**kvargs)构造NewClsName对象,但这样做似乎不够优雅(或者说它其实是优雅的?)。

没错,我觉得这是一种很常见的做法:

def OldClsName(*args, **kwargs):
    from warnings import warn
    warn("get with the program!")
    return NewClsName(*args, **kwargs)

唯一棘手的地方是,如果你有一些类是从OldClsName继承而来的,那我们就得想点办法了。如果你只是需要继续访问类的方法,这样做应该就可以了:

class DeprecationHelper(object):
    def __init__(self, new_target):
        self.new_target = new_target

    def _warn(self):
        from warnings import warn
        warn("Get with the program!")

    def __call__(self, *args, **kwargs):
        self._warn()
        return self.new_target(*args, **kwargs)

    def __getattr__(self, attr):
        self._warn()
        return getattr(self.new_target, attr)

OldClsName = DeprecationHelper(NewClsName)

我还没有测试过,但这个思路应该是对的——__call__会处理正常的实例化过程,__getattr__会捕捉对类方法的访问,同时还会发出警告,而不会影响你的类的继承关系。

撰写回答