12

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

# myglobal = 3
myglobal = DEPRECATED(3)

但除了如何让 DEPRECATED 完全像“3”一样的问题之外,我不确定 DEPRECATED 可以做什么来让您在每次访问它时都能检测到。我认为它可以做的最好的事情是遍历所有全局方法(因为 Python 中的所有内容都是一个对象,所以即使“3”也有方法,用于转换为字符串等)并将它们“装饰”以使其全部被弃用。但这并不理想。

有任何想法吗?有没有其他人解决过这个问题?

4

4 回答 4

14

您不能直接执行此操作,因为无法拦截模块访问。但是,您可以将该模块替换为您选择的充当代理的对象,以查找对某些属性的访问:

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

缺点是您现在在访问模块时执行了两次查找,因此对性能有轻微影响。

于 2009-05-28T19:16:29.253 回答
5

看哪:

代码

from types import *

def wrapper(f, warning):
    def new(*args, **kwargs):
        if not args[0].warned:
            print "Deprecated Warning: %s" % warning
            args[0].warned = True
        return f(*args, **kwargs)
    return new

class Deprecated(object):
    def __new__(self, o, warning):
        print "Creating Deprecated Object"
        class temp(o.__class__): pass
        temp.__name__ = "Deprecated_%s" % o.__class__.__name__
        output = temp.__new__(temp, o)

        output.warned = True
        wrappable_types = (type(int.__add__), type(zip), FunctionType)
        unwrappable_names = ("__str__", "__unicode__", "__repr__", "__getattribute__", "__setattr__")

        for method_name in dir(temp):
            if not type(getattr(temp, method_name)) in wrappable_types: continue
            if method_name in unwrappable_names: continue

            setattr(temp, method_name, wrapper(getattr(temp, method_name), warning))

        output.warned = False

        return output

输出

>>> 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

这显然可以改进,但要点就在那里。

于 2009-05-29T03:51:55.353 回答
5

您可以将您的模块变成一个类(参见例如这个 SO question)并将已弃用的全局变成一个属性,这样您就可以在访问它时执行一些代码并提供您想要的警告。然而,这似乎有点矫枉过正。

于 2009-05-28T19:08:02.250 回答
2

这是PEP 562(在 Python 3.7 中实现)的主要理由之一:

典型的解决方法是将__class__模块对象分配给自定义子类types.ModuleType或用sys.modules自定义包装器实例替换该项目。通过识别__getattr__直接在模块中定义的方式来简化此过程将很方便,该模块的作用类似于普通__getattr__方法,除了它将在模块实例上定义。例如:

# 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
于 2018-03-12T10:13:21.553 回答