Apa kegunaan umum untuk dekorator Python? [Tutup]


337

Sementara saya suka menganggap diri saya sebagai pembuat kode Python yang cukup kompeten, salah satu aspek dari bahasa yang saya tidak pernah bisa grok adalah dekorator.

Saya tahu apa itu (secara dangkal), saya sudah membaca tutorial, contoh, pertanyaan tentang Stack Overflow, dan saya mengerti sintaksnya, bisa menulis sendiri, kadang-kadang menggunakan @classmethod dan @staticmethod, tetapi tidak pernah terpikir oleh saya untuk menggunakan dekorator untuk memecahkan masalah dalam kode Python saya sendiri. Saya tidak pernah menemukan masalah di mana saya berpikir, "Hmm ... ini terlihat seperti pekerjaan untuk dekorator!"

Jadi, saya bertanya-tanya apakah kalian mungkin menawarkan beberapa contoh di mana Anda telah menggunakan dekorator dalam program Anda sendiri, dan mudah-mudahan saya akan memiliki "A-ha!" saat dan mendapatkannya .


5
Selain itu, dekorator berguna untuk Memoizing - yang merupakan caching hasil fungsi yang lambat untuk dihitung. Dekorator dapat mengembalikan fungsi yang memeriksa input, dan jika sudah dipresentasikan, kembalikan hasil yang di-cache.
Peter

1
Perhatikan bahwa Python memiliki dekorator built-in functools.lru_cache,, yang melakukan persis apa yang Peter katakan, sejak Python 3.2, dirilis pada Februari 2011.
Taegyung

Isi Pustaka Penghias Python harus memberi Anda ide bagus tentang kegunaan lain untuk mereka.
Martineau

Jawaban:


126

Saya menggunakan dekorator terutama untuk tujuan pengaturan waktu

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...

13
Di bawah Unix, time.clock()mengukur waktu CPU. Anda mungkin ingin menggunakannya time.time()sebagai gantinya jika Anda ingin mengukur waktu jam dinding.
Jabba

20
Contoh yang bagus! Tidak tahu apa fungsinya. Penjelasan apa yang Anda lakukan di sana, dan bagaimana dekorator memecahkan masalahnya akan sangat menyenangkan.
MeLight

7
Ya, itu mengukur waktu yang diperlukan untuk myFunctionmenjalankan ...
RSabet

98

Saya telah menggunakannya untuk sinkronisasi.

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

Seperti yang ditunjukkan dalam komentar, karena Python 2.5 Anda dapat menggunakan withpernyataan bersamaan dengan objek threading.Lock(atau multiprocessing.Locksejak versi 2.6) untuk menyederhanakan implementasi dekorator menjadi hanya:

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

Apapun, Anda kemudian menggunakannya seperti ini:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

Pada dasarnya itu hanya menempatkan lock.acquire()/ lock.release()di kedua sisi panggilan fungsi.


18
Mungkin dibenarkan, tetapi dekorator pada dasarnya membingungkan, esp. untuk noobs tahun pertama yang datang di belakang Anda dan mencoba untuk mod kode Anda. Hindari ini dengan kesederhanaan: cukup do_something () lampirkan kodenya dalam blok di bawah 'dengan kunci:' dan semua orang dapat dengan jelas melihat tujuan Anda. Dekorator sangat sering digunakan oleh orang-orang yang ingin terlihat pintar (dan banyak yang sebenarnya) tetapi kemudian kode tersebut menjadi manusia biasa dan diperalat.
Kevin J. Rice

18
@ KevinJ.Rice Membatasi kode Anda sehingga 'tahun pertama noobs' dapat lebih memahami itu adalah praktik yang mengerikan. Sintaks dekorator jauh lebih mudah dibaca dan sangat memisahkan kode.
TaylerJones

18
@TaylerJones, keterbacaan kode hanya tentang prioritas tertinggi saya saat menulis. Kode dibaca 7+ kali untuk setiap kali dimodifikasi. Kode yang sulit dipahami (untuk noobs atau untuk pakar yang bekerja di bawah tekanan waktu) adalah utang teknis yang harus dibayar setiap kali seseorang mengunjungi pohon kode sumber.
Kevin J. Rice

@TaylerJones Salah satu tugas paling penting bagi seorang programmer adalah memberikan kejelasan.
JDOaktown

71

Saya menggunakan dekorator untuk memeriksa parameter yang diteruskan ke metode Python saya melalui beberapa RMI. Jadi alih-alih mengulangi penghitungan parameter yang sama, pengecualian-meningkatkan mumbo-jumbo lagi dan lagi.

Misalnya, alih-alih:

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

Saya hanya menyatakan:

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

dan accepts()melakukan semua pekerjaan untuk saya.


15
Bagi siapa pun yang tertarik, ada implementasi @acceptsdi PEP 318.
martineau

2
Saya pikir ada kesalahan ketik .. metode pertama harus menerima .. Anda menyatakan keduanya sebagai "myMethod"
DevC

1
@DevC Tidak, ini tidak terlihat seperti kesalahan ketik. Karena itu jelas bukan implementasi dari "accepts (..)", dan di sini "accepts (..)" melakukan pekerjaan yang seharusnya dilakukan oleh dua baris di awal "myMethod (..)" - itulah hanya interpretasi yang cocok.
Evgeni Sergeev

1
Maaf untuk benjolan, saya hanya ingin menunjukkan bahwa memeriksa jenis argumen yang disahkan dan menaikkan TypeError jika tidak dianggap praktik yang buruk karena tidak akan menerima misalnya int jika hanya memeriksa pelampung, dan karena biasanya kode itu sendiri harus beradaptasi untuk berbagai jenis nilai yang diberikan untuk fleksibilitas maksimum.
Gustavo6046

2
Cara yang disarankan untuk melakukan pengecekan tipe dalam Python adalah melalui isinstance()fungsi bawaan, seperti yang dilakukan dalam implementasi dekorator PEP 318 . Karena classinfoargumennya dapat berupa satu atau lebih jenis, menggunakannya juga akan mengurangi keberatan (Gust) @ Gustavo6046. Python juga memiliki Numberkelas dasar abstrak, jadi tes yang sangat umum seperti isinstance(42, numbers.Number)mungkin dilakukan.
martineau

48

Dekorator digunakan untuk apa pun yang Anda ingin "bungkus" transparan dengan fungsionalitas tambahan.

Django menggunakannya untuk membungkus fungsionalitas "diperlukan masuk" pada fungsi tampilan , serta untuk mendaftarkan fungsi filter .

Anda bisa menggunakan dekorator kelas untuk menambahkan log bernama ke kelas .

Fungsionalitas generik apa pun yang dapat Anda "tempel" ke kelas yang ada atau perilaku fungsi adalah permainan yang adil untuk dekorasi.

Ada juga diskusi tentang kasus penggunaan pada newsgroup Python-Dev yang ditunjuk oleh PEP 318 - Dekorator untuk Fungsi dan Metode .


Cherrypy menggunakan @ cherrypy.expose untuk menjaga fungsi mana yang bersifat publik dan fungsi tersembunyi. Itu adalah perkenalan pertama saya dan saya terbiasa melakukannya di sana.
Marc Maxmeister

26

Untuk ujian ulang, Anda dapat menulis dekorator yang memasok fungsi atau metode uji unit dengan beberapa set parameter:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected

23

Perpustakaan Twisted menggunakan dekorator yang dikombinasikan dengan generator untuk memberikan ilusi bahwa fungsi asinkron bersifat sinkron. Sebagai contoh:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

Dengan menggunakan ini, kode yang akan dipecah menjadi satu ton fungsi callback kecil dapat ditulis secara alami sebagai satu blok, sehingga jauh lebih mudah untuk dipahami dan dipelihara.


14

Satu penggunaan yang jelas adalah untuk logging, tentu saja:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()

10

Saya menggunakannya terutama untuk debugging (membungkus fungsi yang mencetak argumen dan hasilnya) dan verifikasi (misalnya untuk memeriksa apakah argumen adalah tipe yang benar atau, dalam kasus aplikasi web, jika pengguna memiliki hak yang cukup untuk memanggil tertentu metode).


6

Saya menggunakan dekorator berikut untuk membuat fungsi threadsafe. Itu membuat kode lebih mudah dibaca. Ini hampir mirip dengan yang diusulkan oleh John Fouhy tetapi perbedaannya adalah bahwa seseorang bekerja pada fungsi tunggal dan bahwa tidak perlu membuat objek kunci secara eksplisit.

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var

1
Apakah ini berarti setiap fungsi, yang didekorasi demikian, memiliki kuncinya sendiri?
berduka

1
@ maaf ya, setiap kali dekorator digunakan (disebut) itu menciptakan objek kunci baru untuk fungsi / metode yang sedang didekorasi.
martineau

5
Itu sangat berbahaya. Metode inc_var () adalah "threadsafe" di mana hanya satu orang yang dapat memanggilnya sekaligus. Yang mengatakan, karena metode ini beroperasi pada variabel anggota "var" dan mungkin metode lain juga dapat beroperasi pada variabel anggota "var" dan akses tersebut tidak aman karena kunci tidak dibagi. Melakukan hal-hal dengan cara ini memberikan pengguna keamanan rasa kelas salah.
Bob Van Zant

Itu tidak aman sebelum kunci tunggal digunakan.
Chandu

5

Dekorator digunakan untuk mendefinisikan properti fungsi atau sebagai pelat yang mengubahnya; itu mungkin tetapi kontra-intuitif bagi mereka untuk mengembalikan fungsi yang sama sekali berbeda. Melihat respons lain di sini, sepertinya salah satu kegunaan yang paling umum adalah membatasi ruang lingkup beberapa proses lainnya - baik itu pencatatan, profil, pemeriksaan keamanan, dll.

CherryPy menggunakan pengiriman objek untuk mencocokkan URL dengan objek dan, akhirnya, metode. Dekorator pada metode-metode tersebut memberi sinyal apakah CherryPy bahkan diperbolehkan menggunakan metode tersebut. Misalnya, diadaptasi dari tutorial :

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())

Ini tidak benar. Seorang dekorator dapat sepenuhnya mengubah perilaku suatu fungsi.
Rekursif

Baik. Tetapi seberapa sering dekorator "benar-benar mengubah perilaku suatu fungsi?" Dari apa yang saya lihat, ketika mereka tidak digunakan untuk menentukan properti, mereka hanya digunakan untuk kode boilerplate. Saya telah mengedit respons saya.
Nikhil Chelliah

5

Saya menggunakannya baru-baru ini, ketika bekerja pada aplikasi web jejaring sosial. Untuk Komunitas / Grup, saya seharusnya memberikan otorisasi keanggotaan untuk membuat diskusi baru dan membalas pesan Anda harus menjadi anggota grup tersebut. Jadi, saya menulis dekorator @membership_requireddan meletakkannya di tempat yang saya inginkan dalam pandangan saya.


1

Saya menggunakan dekorator ini untuk memperbaiki parameter

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

ini ditulis ketika saya refactor beberapa fungsi perlu melewati argumen "ingin" tetapi dalam kode lama saya, saya melewati N atau 'N' saja


1

Dekorator dapat digunakan untuk dengan mudah membuat variabel metode fungsi.

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1

6
Terima kasih atas contoh Anda, tetapi (apolgies) saya harus mengatakan WTF - Mengapa Anda menggunakan ini? Ini memiliki potensi BESAR untuk membingungkan orang. Tentu saja, saya menghormati kebutuhan untuk penggunaan tepi-kasus, tetapi Anda memukul pada masalah umum yang dimiliki oleh banyak pengembang Python yang tidak berpengalaman - tidak cukup menggunakan kelas. Artinya, hanya memiliki var of class sederhana, menginisialisasi, dan menggunakannya. Noobs cenderung menulis drop-thru (kode berbasis non-kelas) dan mencoba untuk mengatasi kurangnya fungsionalitas kelas dengan solusi yang rumit. Tolong jangan? Silahkan? maaf harpa, terima kasih atas jawaban Anda, tetapi Anda telah menekan tombol panas untuk saya.
Kevin J. Rice

Saya akan -1 pada ini jika muncul sebagai permintaan tarik bagi saya untuk meninjau kode, dan jadi saya juga -1 pada ini sebagai python baik.
Techdragon

Imut. Konyol, tapi imut. :) Saya tidak keberatan dengan atribut fungsi sesekali, tetapi mereka adalah hal yang jarang terjadi dalam kode Python khas bahwa jika saya akan menggunakannya, saya lebih suka melakukannya secara eksplisit, daripada menyembunyikannya di bawah dekorator.
PM 2Ring
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.