Как пометить глобал как устаревший в Python? - PullRequest
9 голосов
/ 28 мая 2009

Я видел декораторы , которые позволяют помечать функцию как устаревшую, чтобы при каждом использовании этой функции выдавалось предупреждение. Я хотел бы сделать то же самое, но для глобальной переменной, но я не могу придумать способ обнаружения доступа к глобальной переменной. Я знаю о функции globals () и могу проверить ее содержимое, но это просто скажет мне, определен ли глобал (каким он будет, если функция устарела, а не все ли удалены), а не использовался ли он на самом деле , Лучшая альтернатива, которую я могу придумать, это что-то вроде этого:

# myglobal = 3
myglobal = DEPRECATED(3)

Но кроме проблемы того, как заставить DEPRECATED действовать точно так же, как '3', я не уверен, что может сделать DEPRECATED, что позволит вам обнаружить каждый раз, когда к нему обращаются. Я думаю, что лучшее, что он мог бы сделать, - это перебирать все методы глобала (поскольку все в Python является объектом, поэтому даже у «3» есть методы для преобразования в строку и т. П.) И «декорировать» их, чтобы все устарели. Но это не идеально.

Есть идеи? Кто-нибудь еще занимался этой проблемой?

Ответы [ 4 ]

13 голосов
/ 28 мая 2009

Вы не можете сделать это напрямую, так как нет способа перехватить доступ к модулю. Однако вы можете заменить этот модуль выбранным вами объектом, который действует как прокси-сервер и ищет доступ к определенным свойствам:

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

Недостатком является то, что вы теперь выполняете два поиска при доступе к модулю, поэтому наблюдается незначительное снижение производительности.

5 голосов
/ 29 мая 2009

Вот:

код

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

Это, очевидно, можно уточнить, но суть есть.

5 голосов
/ 28 мая 2009

Вы можете превратить свой модуль в класс (см., Например, этот вопрос SO ) и превратить этот устаревший глобальный элемент в свойство, чтобы вы могли выполнять часть своего кода при обращении к нему и предоставлять предупреждение по вашему желанию. , Однако это кажется излишним.

1 голос
/ 12 марта 2018

Это одно из основных обоснований для 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
...