我试图建立一个允许代码以透明方式为常规用户运行的弃用系统,但在开发人员模式下标记已弃用的对象。
我遇到的一个问题是,即使我处于开发者模式,我也可以将弃用的对象导入另一个模块。这意味着我错过了使用弃用对象的地方。
例如在module1.py中:
class MyObject(object):
pass
MyObject = MyObject if not dev_mode() else DeprecatedObject
然后在module2.py中:
from module1 import MyObject
我已经设置了DeprecatedObject,以便与它进行任何交互都会引发DeprecationWarning - 有没有什么办法可以让我在导入时出错?即。甚至导入module2.py都会引发异常。
我想象的是:
import warnings
class DeprecatedObject(object):
...
def __onimport__(self):
warnings.warn("deprecated", DeprecationWarning)
答案 0 :(得分:2)
模块级__getattr__
功能允许模块级别名称在导入时进行正确的弃用过程。此功能将在Python 3.7中提供,有关详细信息,请参阅PEP 562(因为您已经使用Python 2.7进行了标记,它无法帮助您,但我为了未来读者的利益而提及它)。
在Python 2.7上,你有两个较差的选择:
__init__
中触发弃用警告。 答案 1 :(得分:1)
首先,我建议您查看内置的warnings
模块。它有专门为这类东西制作的工具。发出非致命警告比提出异常更有意义。
现在,对于您的情况,一种可能的做法是用函数“替换”已弃用的类。这意味着将类重命名为其他类,并具有原始名称的函数,该函数检查开发者模式是否已启用并相应地执行。结果将是:
class MyDeprecatedClass:
pass
def MyClass(*args, **kwargs):
if dev_mode():
raise DeprecationWarning
else:
return MyDeprecatedClass(*args, **kwargs)
或者,警告:
def MyClass(*args, **kwargs):
from warnings import warn
if dev_mode():
warn("Dont use this!!!!!!!!!")
else:
return MyDeprecatedClass(*args, **kwargs)
这样做是检查开发者模式是否已启用,并且只会引发异常(或警告)。否则,它将给予它的所有参数传递给重命名类的构造函数,这意味着所有依赖它的旧函数都可以正常工作。
答案 2 :(得分:0)
您的初始方法几乎就是我所建议的,除了您允许两种类型的对象同时存在。我将从您的模块中的一个完整的if
语句开始,它只允许一次定义一个对象。更像是:
if dev_mode():
class MyObject:
# Define deprecated version here
...
else:
class MyObject:
# Define production version here
...
如果弃用版本和非弃用版本之间的区别很简单,例如,可以使用函数或类装饰器(如引发警告)轻松完成,则可以将上面的代码简化为:
if dev_mode():
def function_decorator(func, cls=None):
# You can use the second argument when calling manually from a class decorator
name = func.__name__ is cls is None else cls.__name__ + '.' + func.__name__
warnings.warn("Importing deprecated function: {}".format(name))
return func
def class_decorator(cls):
warnings.warn("Importing deprecated class: {}".format(cls.__name__))
# Make additional modifications here (like adding function_decorator to all the class methods)
return cls
else:
def function_decorator(func):
return func
def class_decorator(cls):
return cls
@class_decorator
class MyClass:
pass
使用模块级if
来避免浮动类的多个版本是此处的基本工具。您可以为流程添加任意数量的复杂层。我出于类似目的看到的一种技术(其中特定版本的类依赖于某些导入时间条件,如OS),是创建一个名为module1
的包,并在不同的类中实现两个不同版本的类模块完全。包结构如下所示:
module1/
|
+-- __init__.py
|
+-- _development.py
|
+-- _production.py
_development
和_production
都定义了相同的名称,但版本不同。模块名称前面的下划线表示永远不应该直接导入它们。您将module1
作为模块而不是使用其__init__
文件作为包公开,它看起来像这样:
__all__ = ['MyModule']
if dev_mode():
from ._development import MyModule
else:
from ._production import MyModule
如果您有很多名称,可以使用__all__
中的__init__
自动执行公开导入:
import importlib, sys
__all__ = ['MyClass']
self = sys.modules[__name__]
sub = importlib.import_module('_development' if dev_mode() else '_production')
for name in __all__:
setattr(self, name, getattr(sub, name))
这种分离形式允许您在没有两个单独的测试流程的情况下测试生产版本和开发版本。您的测试可以直接导入私有模块。