如何在Python中将全局标记为已弃用?

如何在Python中将全局标记为已弃用?,python,hook,decorator,global,deprecated,Python,Hook,Decorator,Global,Deprecated,它允许您将函数标记为已弃用,以便在使用该函数时发出警告。我也想做同样的事情,但是对于全局变量,我想不出一种方法来检测全局变量访问。我知道globals()函数,我可以检查它的内容,但这只会告诉我是否定义了全局函数(如果函数已弃用且未完全删除,则仍然是全局函数),而不是它是否正在实际使用。我能想到的最好的选择是这样的: # myglobal = 3 myglobal = DEPRECATED(3) 但是,除了如何让弃用软件完全像“3”一样运行的问题之外,我不确定弃用软件能做什么,让您在每次访问它

它允许您将函数标记为已弃用,以便在使用该函数时发出警告。我也想做同样的事情,但是对于全局变量,我想不出一种方法来检测全局变量访问。我知道globals()函数,我可以检查它的内容,但这只会告诉我是否定义了全局函数(如果函数已弃用且未完全删除,则仍然是全局函数),而不是它是否正在实际使用。我能想到的最好的选择是这样的:

# 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