2010-03-29 187 views
57

我需要將Python例程標記爲棄用,但我找不到用於棄用的標準庫裝飾器。我知道它的食譜和警告模塊,但我的問題是:如何棄用Python函數?

爲什麼在Python中沒有標準庫裝飾器用於棄用?

+4

現在有一個[不贊成](https://pypi.python.org/ pypi/deprecation)包 – muon 2017-11-01 21:13:30

+0

@ muon您可以將此作爲答案用於示例用法嗎? – 2018-02-22 16:01:41

+0

@StevenVascellaro當然! – muon 2018-02-22 16:32:01

回答

4

我想是因爲Python代碼不能靜態處理(就像它爲C++編譯器所做的那樣),所以在實際使用它之前,你不能警告使用某些東西。我不認爲用一堆消息向腳本用戶發送垃圾郵件是個好主意「警告:此腳本的開發人員正在使用已棄用的API」。

更新:但您可以創建裝飾器,將原始功能轉換爲另一個。新功能將標記/檢查開關,告訴該功能已被調用,並且僅在將開關轉入開啓狀態時纔會顯示消息。並且/或者在退出時它可以打印在程序中使用的所有被棄用功能的列表。

+1

是的,但如果用戶是我運行我的測試套件... – 2010-03-29 07:44:01

+2

而且,當從模塊**導入功能時,您應該能夠指示棄用**。裝飾者將是一個正確的工具。 – 2013-02-21 09:23:53

+0

@JanuszLenar,即使我們不使用已棄用的函數,也會顯示該警告。但我想我可以用一些提示更新我的答案。 – ony 2013-02-21 11:37:19

28

這裏的一些片段中,從那些通過引用萊安德羅改性:

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 

因爲在一些口譯露出的第一溶液(不帶過濾器處理)可能會導致警告抑制。

+10

爲什麼不使用'functools.wraps'而不是像這樣設置名稱和文檔? – Maximilian 2015-08-06 14:40:26

+1

@Maximilian:編輯補充說,爲了節省將來的代碼複製副本,我做錯了 – Eric 2016-06-28 03:59:45

+4

我不喜歡副作用(打開/關閉過濾器)。這不是裝飾者的工作。 – Kentzo 2017-04-18 17:48:21

16

這裏是另一種解決方案:

這個裝飾(實際上是一個decorator factory)允許你給一個原因消息。通過給出源文件名行號碼來幫助開發人員診斷問題也更加有用。

編輯:此代碼使用零的建議:它取代由warnings.warn(msg, category=DeprecationWarning, stacklevel=2)warnings.warn_explicit線, 其打印函數調用的位置,而不是函數定義的網站。它使調試更容易。

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:這個裝飾現在是過時的庫的一部分:

+4

工作,以及 - 我寧願用'warnings.warn(msg,category = DeprecationWarning,stacklevel = 2)替換'warn_explicit'行'它打印函數調用站點而不是函數定義站點。它使調試更容易。 – Zero 2016-11-28 23:24:58

+0

您好,我想在[一個GPLv3許可的庫](https://github.com/FIDUCEO/FCDR_HIRS/)中使用您的代碼片段。您是否願意根據GPLv3 [或任何更寬鬆的許可證](https://opensource.stackexchange.com/a/3/33)重新授權您的代碼,以便我可以合法地執行此操作? – gerrit 2017-07-07 17:19:30

+0

@gerrit:[所有用戶貢獻均屬於知識共享署名 - 相同方式共享](https://stackoverflow.com/help/licensing)。 – 2017-07-09 10:15:32

1

更新:我覺得比較好,當我們表現出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中的行。

+1

'warnings.warn'已經有一個「每個代碼行一次」設置 – Eric 2017-12-02 18:05:15

4

您可以創建一個文件utils的

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

謝謝,我用這個來發送用戶到正確的位置,而不是隻顯示棄用消息! – 2018-02-05 22:03:07