декоратори в стандартній лібці python (спеціально @ визначено)


127

Мені потрібно відмітити підпрограми як застарілі, але, мабуть, немає стандартного декоратора бібліотеки для депрекації. Мені відомі рецепти до нього та модуль попереджень, але моє питання: чому не існує стандартного декоратора бібліотеки для цього (загального) завдання?

Додаткове запитання: чи взагалі є стандартні декоратори в стандартній бібліотеці?


13
тепер є несхвалення пакет
мюон

11
Я розумію способи цього зробити, але прийшов сюди, щоб зрозуміти, чому це не в std lib (як я вважаю, це справа ОП) і не бачу хорошої відповіді на власне питання
SwimBikeRun

4
Чому трапляється так часто, що на запитання отримують десятки відповідей, які навіть не намагаються відповісти на запитання, а активно ігнорують речі типу "Я знаю рецепти"? Це божевільно!
Кацкуль

1
@Catskul через підроблені інтернет-точки.
Стефано

1
Ви можете використовувати застарілу бібліотеку.
Лоран

Відповіді:


59

Ось фрагмент, модифікований із цитованих Леандро:

import warnings
import functools

def deprecated(func):
    """This is a decorator which can be used to mark functions
    as deprecated. It will result in a warning being emitted
    when the function is used."""
    @functools.wraps(func)
    def new_func(*args, **kwargs):
        warnings.simplefilter('always', DeprecationWarning)  # turn off filter
        warnings.warn("Call to deprecated function {}.".format(func.__name__),
                      category=DeprecationWarning,
                      stacklevel=2)
        warnings.simplefilter('default', DeprecationWarning)  # reset filter
        return func(*args, **kwargs)
    return new_func

# Examples

@deprecated
def some_old_function(x, y):
    return x + y

class SomeClass:
    @deprecated
    def some_old_method(self, x, y):
        return x + y

Оскільки в деяких інтерпретаторах перший підданий розчин (без обробки фільтрів) може призвести до попередження придушення.


14
Чому б не використовувати, functools.wrapsа не встановлювати ім’я та doc подібним чином?
Максиміліан

1
@Maximilian: Відредаговано, щоб додати, щоб врятувати майбутніх копіювальників цього коду від того, щоб вони не робили неправильно
Ерік,

17
Мені не подобається побічний ефект (включення / вимкнення фільтра). Це не вирішувати справу декоратора.
Кенцо

1
Увімкнення та вимкнення фільтра може спричинити bugs.python.org/issue29672
gerrit

4
не відповідає на власне питання.
Кацкуль

44

Ось ще одне рішення:

Цей декоратор ( фабрика декораторів насправді) дозволяє надати причину . Також корисніше допомогти розробнику діагностувати проблему, вказавши ім'я вихідного файла та номер рядка .

EDIT : Цей код використовує рекомендацію Zero: він замінює warnings.warn_explicitрядок на warnings.warn(msg, category=DeprecationWarning, stacklevel=2), який друкує сайт виклику функції, а не сайт визначення функції. Це полегшує налагодження.

EDIT2 : Ця версія дозволяє розробнику вказати необов'язкове повідомлення "причина".

import functools
import inspect
import warnings

string_types = (type(b''), type(u''))


def deprecated(reason):
    """
    This is a decorator which can be used to mark functions
    as deprecated. It will result in a warning being emitted
    when the function is used.
    """

    if isinstance(reason, string_types):

        # The @deprecated is used with a 'reason'.
        #
        # .. code-block:: python
        #
        #    @deprecated("please, use another function")
        #    def old_function(x, y):
        #      pass

        def decorator(func1):

            if inspect.isclass(func1):
                fmt1 = "Call to deprecated class {name} ({reason})."
            else:
                fmt1 = "Call to deprecated function {name} ({reason})."

            @functools.wraps(func1)
            def new_func1(*args, **kwargs):
                warnings.simplefilter('always', DeprecationWarning)
                warnings.warn(
                    fmt1.format(name=func1.__name__, reason=reason),
                    category=DeprecationWarning,
                    stacklevel=2
                )
                warnings.simplefilter('default', DeprecationWarning)
                return func1(*args, **kwargs)

            return new_func1

        return decorator

    elif inspect.isclass(reason) or inspect.isfunction(reason):

        # The @deprecated is used without any 'reason'.
        #
        # .. code-block:: python
        #
        #    @deprecated
        #    def old_function(x, y):
        #      pass

        func2 = reason

        if inspect.isclass(func2):
            fmt2 = "Call to deprecated class {name}."
        else:
            fmt2 = "Call to deprecated function {name}."

        @functools.wraps(func2)
        def new_func2(*args, **kwargs):
            warnings.simplefilter('always', DeprecationWarning)
            warnings.warn(
                fmt2.format(name=func2.__name__),
                category=DeprecationWarning,
                stacklevel=2
            )
            warnings.simplefilter('default', DeprecationWarning)
            return func2(*args, **kwargs)

        return new_func2

    else:
        raise TypeError(repr(type(reason)))

Ви можете використовувати цей декоратор для функцій , методів та занять .

Ось простий приклад:

@deprecated("use another function")
def some_old_function(x, y):
    return x + y


class SomeClass(object):
    @deprecated("use another method")
    def some_old_method(self, x, y):
        return x + y


@deprecated("use another class")
class SomeOldClass(object):
    pass


some_old_function(5, 3)
SomeClass().some_old_method(8, 9)
SomeOldClass()

Ви отримаєте:

deprecated_example.py:59: DeprecationWarning: Call to deprecated function or method some_old_function (use another function).
  some_old_function(5, 3)
deprecated_example.py:60: DeprecationWarning: Call to deprecated function or method some_old_method (use another method).
  SomeClass().some_old_method(8, 9)
deprecated_example.py:61: DeprecationWarning: Call to deprecated class SomeOldClass (use another class).
  SomeOldClass()

EDIT3: Цей декоратор зараз є частиною застарілої бібліотеки:

Новий стабільний випуск v1.2.10 🎉


6
Працює, добре - я вважаю за краще замінювати warn_explicitрядок, з warnings.warn(msg, category=DeprecationWarning, stacklevel=2)яким друкується сайт виклику функції, а не сайт із визначенням функції. Це полегшує налагодження.
Нуль

Я хотів би використати ваш фрагмент коду в бібліотеці, що має ліцензію GPLv3 . Чи бажаєте ви повторно дозволити свій код під GPLv3 чи будь-якою більш дозвільною ліцензією , щоб я міг законно це зробити?
Герріт


1
@LaurentLAPORTE Я знаю. CC-BY-SO не дозволяє використовувати всередині GPLv3 (через біт схожості), тому я запитую, чи бажаєте ви випустити цей код спеціально додатково під ліцензією, сумісною з GPL. Якщо ні, то це добре, і я не буду використовувати ваш код.
Герріт

2
не відповідає на власне питання.
Кацкуль

15

Як запропонував Muon , ви можете встановити deprecationпакет для цього.

deprecationБібліотека надає deprecatedдекоратор і fail_if_not_removedдекоратор для ваших тестів.

Установка

pip install deprecation

Приклад використання

import deprecation

@deprecation.deprecated(deprecated_in="1.0", removed_in="2.0",
                        current_version=__version__,
                        details="Use the bar function instead")
def foo():
    """Do some stuff"""
    return 1

Дивіться http://deprecation.readthedocs.io/ для повної документації.


4
не відповідає на власне питання.
Кацкуль

1
Примітка PyCharm цього не визнає
cz

11

Я думаю, причина полягає в тому, що код Python не може бути оброблений статично (як це робиться для компіляторів C ++), ви не можете отримати попередження про використання деяких речей, перш ніж реально використовувати його. Я не думаю, що це гарна ідея спамувати користувача вашого сценарію з купою повідомлень "Увага! Цей розробник цього сценарію використовує застарілий API".

Оновлення: але ви можете створити декоратор, який перетворить оригінальну функцію в іншу. Нова функція позначатиме / перевіряє перемикач, який повідомляє, що ця функція вже викликалася і відображатиметься повідомлення лише про ввімкнення перемикача в стан. І / або при виході він може надрукувати список усіх застарілих функцій, що використовуються в програмі.


3
І ви повинні мати можливість вказувати на анулювання, коли функція імпортується з модуля . Декоратор буде правильним інструментом для цього.
Януш Ленар

@JanuszLenar, це попередження з’явиться, навіть якщо ми не будемо використовувати цю застарілу функцію. Але я думаю, що можу оновити свою відповідь деяким підказом.
ony

8

Ви можете створити файл утиліти

import warnings

def deprecated(message):
  def deprecated_decorator(func):
      def deprecated_func(*args, **kwargs):
          warnings.warn("{} is a deprecated function. {}".format(func.__name__, message),
                        category=DeprecationWarning,
                        stacklevel=2)
          warnings.simplefilter('default', DeprecationWarning)
          return func(*args, **kwargs)
      return deprecated_func
  return deprecated_decorator

А потім імпортуйте декоратор депресії таким чином:

from .utils import deprecated

@deprecated("Use method yyy instead")
def some_method()"
 pass

Дякую, я використовую це, щоб відправити користувача в потрібне місце, а не просто показувати повідомлення про аномацію!
Німецький Аттанасьо

3
не відповідає на власне питання.
Кацкуль

2

ОНОВЛЕННЯ: Я думаю, що краще, коли ми показуємо DeprecationWarning лише перший раз для кожного рядка коду і коли ми можемо надіслати якесь повідомлення:

import inspect
import traceback
import warnings
import functools

import time


def deprecated(message: str = ''):
    """
    This is a decorator which can be used to mark functions
    as deprecated. It will result in a warning being emitted
    when the function is used first time and filter is set for show DeprecationWarning.
    """
    def decorator_wrapper(func):
        @functools.wraps(func)
        def function_wrapper(*args, **kwargs):
            current_call_source = '|'.join(traceback.format_stack(inspect.currentframe()))
            if current_call_source not in function_wrapper.last_call_source:
                warnings.warn("Function {} is now deprecated! {}".format(func.__name__, message),
                              category=DeprecationWarning, stacklevel=2)
                function_wrapper.last_call_source.add(current_call_source)

            return func(*args, **kwargs)

        function_wrapper.last_call_source = set()

        return function_wrapper
    return decorator_wrapper


@deprecated('You must use my_func2!')
def my_func():
    time.sleep(.1)
    print('aaa')
    time.sleep(.1)


def my_func2():
    print('bbb')


warnings.simplefilter('always', DeprecationWarning)  # turn off filter
print('before cycle')
for i in range(5):
    my_func()
print('after cycle')
my_func()
my_func()
my_func()

Результат:

before cycle
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:45: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa
aaa
aaa
aaa
aaa
after cycle
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:47: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:48: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa
C:/Users/adr-0/OneDrive/Projects/Python/test/unit1.py:49: DeprecationWarning: Function my_func is now deprecated! You must use my_func2!
aaa

Process finished with exit code 0

Ми можемо просто натиснути на попереджувальний шлях і перейти до рядка в PyCharm.


2
не відповідає на власне питання.
Кацкуль

0

Доповнивши цю відповідь Стівен Васчелларо :

Якщо ви використовуєте Anaconda, спочатку встановіть deprecationпакет:

conda install -c conda-forge deprecation 

Потім вставте наступне у верхній частині файлу

import deprecation

@deprecation.deprecated(deprecated_in="1.0", removed_in="2.0",
                    current_version=__version__,
                    details="Use the bar function instead")
def foo():
    """Do some stuff"""
    return 1

Дивіться http://deprecation.readthedocs.io/ для повної документації.


4
не відповідає на власне питання.
Кацкуль
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.