如何在Python中将全局标记为已弃用?
它允许您将函数标记为已弃用,以便在使用该函数时发出警告。我也想做同样的事情,但是对于全局变量,我想不出一种方法来检测全局变量访问。我知道globals()函数,我可以检查它的内容,但这只会告诉我是否定义了全局函数(如果函数已弃用且未完全删除,则仍然是全局函数),而不是它是否正在实际使用。我能想到的最好的选择是这样的:如何在Python中将全局标记为已弃用?,python,hook,decorator,global,deprecated,Python,Hook,Decorator,Global,Deprecated,它允许您将函数标记为已弃用,以便在使用该函数时发出警告。我也想做同样的事情,但是对于全局变量,我想不出一种方法来检测全局变量访问。我知道globals()函数,我可以检查它的内容,但这只会告诉我是否定义了全局函数(如果函数已弃用且未完全删除,则仍然是全局函数),而不是它是否正在实际使用。我能想到的最好的选择是这样的: # myglobal = 3 myglobal = DEPRECATED(3) 但是,除了如何让弃用软件完全像“3”一样运行的问题之外,我不确定弃用软件能做什么,让您在每次访问它
# myglobal = 3
myglobal = DEPRECATED(3)
但是,除了如何让弃用软件完全像“3”一样运行的问题之外,我不确定弃用软件能做什么,让您在每次访问它时都能检测到它。我认为它能做的最好的事情是迭代所有全局方法(因为Python中的所有东西都是对象,所以即使“3”也有方法,用于转换为字符串等),并“装饰”它们,使它们都不受欢迎。但这并不理想
有什么想法吗?还有其他人解决了这个问题吗?您可以将您的模块变成一个类(参见示例),并将不推荐使用的全局模块变成一个属性,这样您就可以在访问时执行一些代码,并提供所需的警告。但是,这似乎有点过分。您不能直接这样做,因为无法拦截模块访问。但是,您可以将该模块替换为您选择的充当代理的对象,以查找对某些属性的访问:
import sys, warnings
def WrapMod(mod, deprecated):
"""Return a wrapped object that warns about deprecated accesses"""
deprecated = set(deprecated)
class Wrapper(object):
def __getattr__(self, attr):
if attr in deprecated:
warnings.warn("Property %s is deprecated" % attr)
return getattr(mod, attr)
def __setattr__(self, attr, value):
if attr in deprecated:
warnings.warn("Property %s is deprecated" % attr)
return setattr(mod, attr, value)
return Wrapper()
oldVal = 6*9
newVal = 42
sys.modules[__name__] = WrapMod(sys.modules[__name__],
deprecated = ['oldVal'])
现在,您可以将其用作:
>>> import mod1
>>> mod1.newVal
42
>>> mod1.oldVal
mod1.py:11: UserWarning: Property oldVal is deprecated
warnings.warn("Property %s is deprecated" % attr)
54
缺点是,当您访问该模块时,现在正在执行两次查找,因此会对性能造成轻微影响。请注意:
代码
输出
这显然是可以改进的,但要点就在这里。这是(在Python 3.7中实现)的主要原理之一: 典型的解决方法是将模块对象的
\uuuu类\uuu
分配给
类型.ModuleType的自定义子类
或替换系统模块
项
使用自定义包装器实例。简化会很方便
此过程通过识别模块中直接定义的\uuuu getattr\uuuu
来完成
这与普通的\uuuu getattr\uuuu
方法类似,只是它会
可以在模块实例上定义。例如:
# lib.py
from warnings import warn
deprecated_names = ["old_function", ...]
def _deprecated_old_function(arg, other):
...
def __getattr__(name):
if name in deprecated_names:
warn(f"{name} is deprecated", DeprecationWarning)
return globals()[f"_deprecated_{name}"]
raise AttributeError(f"module {__name__} has no attribute {name}")
# main.py
from lib import old_function # Works, but emits the warning
难道你不能删除这个变量,然后重新运行所有的单元测试吗?这样就不需要标记一些不推荐的东西了。这样做的目的是通知API的用户,他们的代码将来会被破坏。如果您正在编写一个库,您不一定知道谁在使用您的库,也不一定能够访问他们使用它编写的代码。因此,您将其标记为已弃用,以便向他们传达您打算在将来删除的函数或变量。+1这相当不错。我认为,如果弃用是一个函数,而不是使用一个具有新。此外,使用warnings模块将为您处理“已经警告过”逻辑,并允许用户以与普通python警告相同的方式过滤/重置它们。(您还可能使用当前方式覆盖原始对象上现有的“警告”属性)在某些情况下,它的行为与原始对象略有不同(例如,“type(a)is int”,这是一种糟糕的样式),但99.9%的时间它应该工作。@Brian,实际上您应该执行isinstance(a,int)这将正常工作,因为新类继承了您传递给弃用类的任何内容。另外,关于不推荐使用的非函数,将其变为函数是微不足道的。我同意(这就是我提到它的糟糕风格的原因),这只是意味着有一点可能,您会破坏任何依赖于这些细节的糟糕编写的代码。我提到将其作为一个函数是因为它看起来更清晰,因为根本不使用class对象,new返回一个完全不同的类-所需的只是删除新行,并将类行更改为“def Deprecated(o,warning):”请参阅我对下面评论的答复。很高兴知道,Guido van Rossum是python的创建者,用于修改导入系统的这种
sys.modules[\uuuuu name\uuuu]
方式,正如问题的第二个参数warnings所指出的。warn
是警告类别。有一个专门针对弃用的类别,DeprecationWarning
。因此,请使用warnings.warn(“属性%s已弃用”%attr,弃用警告)
。
>>> a=Deprecated(1, "Don't use 1")
Creating Deprecated Object
>>> a+9
Deprecated Warning: Don't use 1
10
>>> a*4
4
>>> 2*a
2
# lib.py
from warnings import warn
deprecated_names = ["old_function", ...]
def _deprecated_old_function(arg, other):
...
def __getattr__(name):
if name in deprecated_names:
warn(f"{name} is deprecated", DeprecationWarning)
return globals()[f"_deprecated_{name}"]
raise AttributeError(f"module {__name__} has no attribute {name}")
# main.py
from lib import old_function # Works, but emits the warning