dekorator di lib standar python (@deprecated khusus)

127

Saya perlu menandai rutinitas sebagai usang, tetapi tampaknya tidak ada dekorator perpustakaan standar untuk penghentian. Saya mengetahui resep untuk itu dan modul peringatan, tetapi pertanyaan saya adalah: mengapa tidak ada dekorator perpustakaan standar untuk tugas (umum) ini?

Pertanyaan tambahan: apakah ada dekorator standar di perpustakaan standar?

Stefano Borini
sumber
13
sekarang ada paket deprecation
muon
11
Saya memahami cara melakukannya, tetapi datang ke sini untuk mendapatkan beberapa wawasan tentang mengapa tidak ada di std lib (seperti yang saya asumsikan adalah kasus OP) dan tidak melihat jawaban yang bagus untuk pertanyaan sebenarnya
SwimBikeRun
4
Mengapa hal ini terjadi begitu sering sehingga pertanyaan mendapatkan lusinan jawaban yang bahkan tidak berusaha menjawab pertanyaan, dan secara aktif mengabaikan hal-hal seperti "Saya tahu resepnya"? Ini menjengkelkan!
Catskul
1
@Catskul karena poin internet palsu.
Stefano Borini
1
Anda dapat menggunakan Library Deprecated .
Laurent LAPORTE

Jawaban:

59

Berikut beberapa cuplikan, dimodifikasi dari yang dikutip oleh Leandro:

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

Karena pada beberapa interpreter, solusi pertama yang terbuka (tanpa penanganan filter) dapat mengakibatkan penekanan peringatan.

Patrizio Bertoni
sumber
14
Mengapa tidak menggunakan functools.wrapsdaripada mengatur nama dan dokumen seperti itu?
Maximilian
1
@Maximilian: Diedit untuk menambahkan itu, untuk menghemat copy-pasters masa depan dari kode ini dari melakukan kesalahan juga
Eric
17
Saya tidak suka efek samping (menghidupkan / mematikan filter). Bukan tugas dekorator untuk memutuskan ini.
Kentzo
1
Menghidupkan filter dan mematikan Mei pemicu bugs.python.org/issue29672
Gerrit
4
tidak menjawab pertanyaan sebenarnya.
Catskul
44

Ini solusi lainnya:

Dekorator ini ( sebenarnya pabrik dekorator ) memungkinkan Anda memberikan pesan alasan . Ini juga lebih berguna untuk membantu pengembang untuk mendiagnosis masalah dengan memberikan nama file sumber dan nomor baris .

EDIT : Kode ini menggunakan rekomendasi Zero: itu menggantikan warnings.warn_explicitbaris oleh warnings.warn(msg, category=DeprecationWarning, stacklevel=2), yang mencetak situs pemanggilan fungsi daripada situs definisi fungsi. Itu membuat debugging lebih mudah.

EDIT2 : Versi ini memungkinkan pengembang untuk menentukan pesan "alasan" opsional.

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

Anda dapat menggunakan dekorator ini untuk fungsi , metode , dan kelas .

Berikut ini contoh sederhananya:

@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()

Anda akan mendapatkan:

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: Dekorator ini sekarang menjadi bagian dari perpustakaan Tidak Berlaku Lagi:

Rilis stabil baru v1.2.10 🎉

Laurent LAPORTE
sumber
6
Bekerja, baik - Saya lebih suka mengganti warn_explicitbaris warnings.warn(msg, category=DeprecationWarning, stacklevel=2)yang mencetak situs pemanggilan fungsi daripada situs definisi fungsi. Itu membuat debugging lebih mudah.
Nol
Halo, saya ingin menggunakan potongan kode Anda di perpustakaan berlisensi GPLv3 . Apakah Anda bersedia untuk melisensikan kembali kode Anda di bawah GPLv3 atau lisensi yang lebih permisif , sehingga saya dapat melakukannya secara legal?
gerrit
1
@LaurentLAPORTE Saya tahu. CC-BY-SO tidak mengizinkan penggunaan dalam GPLv3 (karena bit yang mirip), itulah sebabnya saya bertanya apakah Anda bersedia merilis kode ini secara khusus tambahan di bawah lisensi yang kompatibel dengan GPL. Jika tidak, tidak apa-apa, dan saya tidak akan menggunakan kode Anda.
gerrit
2
tidak menjawab pertanyaan sebenarnya.
Catskul
15

Seperti yang disarankan muon , Anda dapat menginstal deprecationpaket untuk ini.

The deprecationperpustakaan menyediakan deprecateddekorator dan fail_if_not_removeddekorator untuk tes Anda.

Instalasi

pip install deprecation

Contoh Penggunaan

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

Lihat http://deprecation.readthedocs.io/ untuk dokumentasi lengkapnya.

Stevoisiak
sumber
4
tidak menjawab pertanyaan sebenarnya.
Catskul
1
Catatan PyCharm tidak mengenali ini
cz
12

Saya kira alasannya adalah kode Python tidak dapat diproses secara statis (seperti yang dilakukan untuk kompiler C ++), Anda tidak bisa mendapatkan peringatan tentang penggunaan beberapa hal sebelum benar-benar menggunakannya. Menurut saya, bukan ide yang baik untuk mengirim spam ke pengguna skrip Anda dengan banyak pesan "Peringatan: pengembang skrip ini menggunakan API yang sudah tidak berlaku lagi".

Perbarui: tetapi Anda dapat membuat dekorator yang akan mengubah fungsi asli menjadi fungsi lain. Fungsi baru akan menandai / memeriksa sakelar yang mengatakan bahwa fungsi ini telah dipanggil dan hanya akan menampilkan pesan saat sakelar berubah menjadi keadaan hidup. Dan / atau saat keluar itu mungkin mencetak daftar semua fungsi usang yang digunakan dalam program.

ony
sumber
3
Dan Anda harus dapat menunjukkan deprecation saat fungsi diimpor dari modul . Dekorator akan menjadi alat yang tepat untuk itu.
Janusz Lenar
@JanuszLenar, peringatan itu akan ditampilkan meskipun kita tidak menggunakan fungsi yang tidak digunakan lagi itu. Tapi saya rasa saya bisa memperbarui jawaban saya dengan beberapa petunjuk.
ony
8

Anda dapat membuat file 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

Dan kemudian impor dekorator deprecation sebagai berikut:

from .utils import deprecated

@deprecated("Use method yyy instead")
def some_method()"
 pass
Erika Dsouza
sumber
Terima kasih, saya menggunakan ini untuk mengarahkan pengguna ke tempat yang tepat, bukan hanya menampilkan pesan penghentian!
Attanasio Jerman
3
tidak menjawab pertanyaan sebenarnya.
Catskul
2

PEMBARUAN: Saya pikir lebih baik, ketika kami menampilkan DeprecationWarning hanya pertama kali untuk setiap baris kode dan ketika kami dapat mengirim beberapa pesan:

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

Hasil:

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

Kami cukup mengklik jalur peringatan dan pergi ke baris di PyCharm.

ADR
sumber
2
tidak menjawab pertanyaan sebenarnya.
Catskul
0

Menambah jawaban ini oleh Steven Vascellaro :

Jika Anda menggunakan Anaconda, instal deprecationpaket terlebih dahulu :

conda install -c conda-forge deprecation 

Kemudian tempel yang berikut ini di bagian atas file

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

Lihat http://deprecation.readthedocs.io/ untuk dokumentasi lengkapnya.

omerbp
sumber
4
tidak menjawab pertanyaan sebenarnya.
Catskul