Dar*_*zak 43 python class backwards-compatibility deprecation-warning
我重命名了一个python类,它是库的一部分.我愿意在一段时间内使用其以前的名称,但是我想警告用户它已被弃用,将来会被删除.
我认为,为了提供向后兼容性,使用类似的别名就足够了:
class NewClsName:
pass
OldClsName = NewClsName
Run Code Online (Sandbox Code Playgroud)
我不知道如何以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( ... )
Run Code Online (Sandbox Code Playgroud)
因为:
AttributeError: 'function' object has no attribute 'CreateVariant1'
Run Code Online (Sandbox Code Playgroud)
继承是我唯一的选择吗?说实话,它对我来说看起来不太干净 - 它通过引入不必要的派生来影响类层次结构.此外,OldClsName is not NewClsName在大多数情况下,这不是问题,但如果使用该库的代码编写不当,可能会出现问题.
我还可以创建一个虚拟的,不相关的OldClsName类,并为其中的所有类方法实现构造函数和包装器,但在我看来,这是更糟糕的解决方案.
Ada*_*mKG 33
也许我可以使OldClsName成为一个发出警告(记录)并从其参数构造NewClsName对象的函数(使用*args和**kvargs),但它看起来不够优雅(或者它可能是?).
是的,我认为这是非常标准的做法:
def OldClsName(*args, **kwargs):
from warnings import warn
warn("get with the program!")
return NewClsName(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
唯一棘手的事情是如果你有从中继承的东西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)
Run Code Online (Sandbox Code Playgroud)
我没有对它进行过测试,但是这应该给你一个想法 - __call__将处理正常的即时路由,__getattr__将捕获对类方法的访问并仍然生成警告,而不会弄乱你的类heirarchy.
jco*_*ado 13
请看看warnings.warn.
如您所见,文档中的示例是弃用警告:
def deprecation(message):
warnings.warn(message, DeprecationWarning, stacklevel=2)
Run Code Online (Sandbox Code Playgroud)
小智 8
在 python >= 3.6 中,您可以轻松处理子类化警告:
class OldClassName(NewClassName):
def __init_subclass__(self):
warn("Class has been renamed NewClassName", DeprecationWarning, 2)
Run Code Online (Sandbox Code Playgroud)
重载__new__应该允许您在直接调用旧的类构造函数时发出警告,但我没有测试过,因为我现在不需要它。
以下是解决方案应满足的要求列表:
isinstance和issubclass检查这可以通过自定义元类来实现:
class DeprecatedClassMeta(type):
def __new__(cls, name, bases, classdict, *args, **kwargs):
alias = classdict.get('_DeprecatedClassMeta__alias')
if alias is not None:
def new(cls, *args, **kwargs):
alias = getattr(cls, '_DeprecatedClassMeta__alias')
if alias is not None:
warn("{} has been renamed to {}, the alias will be "
"removed in the future".format(cls.__name__,
alias.__name__), DeprecationWarning, stacklevel=2)
return alias(*args, **kwargs)
classdict['__new__'] = new
classdict['_DeprecatedClassMeta__alias'] = alias
fixed_bases = []
for b in bases:
alias = getattr(b, '_DeprecatedClassMeta__alias', None)
if alias is not None:
warn("{} has been renamed to {}, the alias will be "
"removed in the future".format(b.__name__,
alias.__name__), DeprecationWarning, stacklevel=2)
# Avoid duplicate base classes.
b = alias or b
if b not in fixed_bases:
fixed_bases.append(b)
fixed_bases = tuple(fixed_bases)
return super().__new__(cls, name, fixed_bases, classdict,
*args, **kwargs)
def __instancecheck__(cls, instance):
return any(cls.__subclasscheck__(c)
for c in {type(instance), instance.__class__})
def __subclasscheck__(cls, subclass):
if subclass is cls:
return True
else:
return issubclass(subclass, getattr(cls,
'_DeprecatedClassMeta__alias'))
Run Code Online (Sandbox Code Playgroud)
DeprecatedClassMeta.__new__方法不仅为它是元类的类调用,而且为此类的每个子类调用。这提供了一个机会来确保不会实例DeprecatedClass化或子类化任何实例。
实例化很简单。元类覆盖了 的__new__方法DeprecatedClass以始终返回 的实例NewClass。
子类化并不难。DeprecatedClassMeta.__new__接收基类和需求的列表替换的实例DeprecatedClass与NewClass。
最后,isinstance和issubclass检查是通过PEP 3119实现__instancecheck__和__subclasscheck__定义的。
class NewClass:
foo = 1
class NewClassSubclass(NewClass):
pass
class DeprecatedClass(metaclass=DeprecatedClassMeta):
_DeprecatedClassMeta__alias = NewClass
class DeprecatedClassSubclass(DeprecatedClass):
foo = 2
class DeprecatedClassSubSubclass(DeprecatedClassSubclass):
foo = 3
assert issubclass(DeprecatedClass, DeprecatedClass)
assert issubclass(DeprecatedClassSubclass, DeprecatedClass)
assert issubclass(DeprecatedClassSubSubclass, DeprecatedClass)
assert issubclass(NewClass, DeprecatedClass)
assert issubclass(NewClassSubclass, DeprecatedClass)
assert issubclass(DeprecatedClassSubclass, NewClass)
assert issubclass(DeprecatedClassSubSubclass, NewClass)
assert isinstance(DeprecatedClass(), DeprecatedClass)
assert isinstance(DeprecatedClassSubclass(), DeprecatedClass)
assert isinstance(DeprecatedClassSubSubclass(), DeprecatedClass)
assert isinstance(NewClass(), DeprecatedClass)
assert isinstance(NewClassSubclass(), DeprecatedClass)
assert isinstance(DeprecatedClassSubclass(), NewClass)
assert isinstance(DeprecatedClassSubSubclass(), NewClass)
assert NewClass().foo == 1
assert DeprecatedClass().foo == 1
assert DeprecatedClassSubclass().foo == 2
assert DeprecatedClassSubSubclass().foo == 3
Run Code Online (Sandbox Code Playgroud)
你为什么不只是子类?这样就不会破坏用户代码。
class OldClsName(NewClsName):
def __init__(self, *args, **kwargs):
warnings.warn("The 'OldClsName' class was renamed [...]",
DeprecationWarning)
NewClsName.__init__(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
从 Python 3.7 开始,您可以使用__getattr__(和__dir__)提供模块属性访问的自定义。一切都在PEP 562 中进行了解释。在下面的示例中,我实现__getattr__并__dir__为了弃用“OldClsName”而使用“NewClsNam”:
# your_lib.py
import warnings
__all__ = ["NewClsName"]
DEPRECATED_NAMES = [('OldClsName', 'NewClsName')]
class NewClsName:
@classmethod
def create_variant1(cls):
return cls()
def __getattr__(name):
for old_name, new_name in DEPRECATED_NAMES:
if name == old_name:
warnings.warn(f"The '{old_name}' class or function is renamed '{new_name}'",
DeprecationWarning,
stacklevel=2)
return globals()[new_name]
raise AttributeError(f"module {__name__} has no attribute {name}")
def __dir__():
return sorted(__all__ + [names[0] for names in DEPRECATED_NAMES])
Run Code Online (Sandbox Code Playgroud)
在__getattr__函数中,如果发现不推荐使用的类或函数名称,则会发出警告消息,显示调用者的源文件和行号(带有stacklevel=2)。
在用户代码中,我们可以:
# your_lib_usage.py
from your_lib import NewClsName
from your_lib import OldClsName
def use_new_class():
obj = NewClsName.create_variant1()
print(obj.__class__.__name__ + " is created in use_new_class")
def use_old_class():
obj = OldClsName.create_variant1()
print(obj.__class__.__name__ + " is created in use_old_class")
if __name__ == '__main__':
use_new_class()
use_old_class()
Run Code Online (Sandbox Code Playgroud)
当用户运行他的脚本时your_lib_usage.py,它会得到如下内容:
NewClsName is created in use_new_class
NewClsName is created in use_old_class
/path/to/your_lib_usage.py:3: DeprecationWarning: The 'OldClsName' class or function is renamed 'NewClsName'
from your_lib import OldClsName
Run Code Online (Sandbox Code Playgroud)
注意:堆栈跟踪通常是用 STDERR 编写的。
要查看错误警告,您可能需要在 Python 命令行中添加“-W”标志,例如:
NewClsName is created in use_new_class
NewClsName is created in use_old_class
/path/to/your_lib_usage.py:3: DeprecationWarning: The 'OldClsName' class or function is renamed 'NewClsName'
from your_lib import OldClsName
Run Code Online (Sandbox Code Playgroud)