Python dekoratörleri için bazı yaygın kullanımlar nelerdir? [kapalı]


337

Kendimi makul derecede yetkin bir Python kodlayıcı olarak düşünmek isterken, hiç bilmediğim dilin bir yönü dekoratörler.

Ne olduklarını biliyorum (yüzeysel olarak), Yığın Taşması ile ilgili öğreticiler, örnekler, sorular okudum ve sözdizimini anlıyorum, kendim yazabiliyorum, bazen @classmethod ve @staticmethod kullanıyorum, ama asla bir Dekoratör kendi Python kodumdaki bir sorunu çözmek için. Asla "Hmm ... bu bir dekoratör için bir iş gibi görünüyor!"

Bu yüzden, dekoratörleri kendi programlarınızda nerede kullandığınıza dair bazı örnekler sunup sunamayacağınızı merak ediyorum ve umarım bir "A-ha!" an ve almak onları.


5
Ayrıca, dekoratörler Memoizing için yararlıdır - bu, bir fonksiyonun hesaplanması yavaş sonucunu önbelleğe alır. Dekoratör, girişleri kontrol eden bir işlevi döndürebilir ve zaten sunulmuşsa, önbelleğe alınmış bir sonuç döndürür.
Peter

1
Python'un, functools.lru_cachePython 3.2'den beri Şubat 2011'de piyasaya sürüldüğü Peter'ın söylediklerini yapan yerleşik bir dekoratöre sahip olduğunu unutmayın .
Taegyung

Python Dekoratör Kütüphanesinin İçeriği, onlar için diğer kullanımlar hakkında size iyi bir fikir vermelidir.
martineau

Yanıtlar:


126

Dekoratörleri çoğunlukla zamanlama amacıyla kullanıyorum

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
Unix altında time.clock()CPU süresini ölçer. time.time()Duvar saati süresini ölçmek istiyorsanız bunun yerine kullanmak isteyebilirsiniz.
Jabba

20
Harika bir örnek! Ne yaptığını bilmiyorum. Orada ne yaptığınıza ve dekoratörün sorunu nasıl çözdüğüne dair bir açıklama çok güzel olurdu.
MeLight

7
Peki, myFunctionkoşmak için gereken süreyi ölçer ...
RSabet

98

Onları senkronizasyon için kullandım.

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

Yorumlarda belirtildiği gibi, Python 2.5'ten beri, dekoratörün uygulamasını basitleştirmek için withbir threading.Lock(veya multiprocessing.Lock2.6 sürümünden beri) nesnesiyle birlikte bir ifade kullanabilirsiniz :

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

Ne olursa olsun, o zaman böyle kullanın:

import threading
lock = threading.Lock()

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

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

Temelde sadece fonksiyon çağrısının her iki tarafına lock.acquire()/ koyar lock.release().


18
Muhtemelen haklı, ama dekoratörler doğal olarak kafa karıştırıcı, esp. arkanıza gelen ve kodunuzu değiştirmeye çalışan ilk yıl noobs. Bunu basit bir şekilde önleyin: do_something () kodunu 'lock:' altında bir blok içine alın ve herkes amacınızı açıkça görebilir. Dekoratörler, akıllı görünmek isteyen (ve aslında çoğu) isteyen insanlar tarafından büyük ölçüde kullanılıyor, ancak kod sadece ölümlülere geliyor ve etkilendi.
Kevin J. Rice

18
@ KevinJ.Rice Kodunuzu 'ilk yıl noobs'larının korkunç bir uygulama olduğunu daha iyi anlayabilmesi için kısıtlamak. Dekoratör sözdiziminin okunması çok daha kolaydır ve kodu büyük ölçüde ayırır.
TaylerJones

18
@TaylerJones, kod okunabilirliği yazma sırasında benim en yüksek önceliğimdir. Kod her değiştirilişinde 7+ kez okunur. Anlaşılması zor kod (noobs veya zaman baskısı altında çalışan uzmanlar için), biri kaynak ağacı her ziyaret ettiğinde ödenmesi gereken teknik borçtur.
Kevin J. Rice

@TaylerJones Bir programcı için en önemli görevlerden biri netlik sağlamaktır.
JDOaktown

71

Bazı RMI aracılığıyla Python yöntemlerime geçirilen tip kontrol parametreleri için dekoratörler kullanıyorum. Bu nedenle, aynı parametre sayımını tekrarlamak yerine, istisna oluşturan mumbo-jumbo tekrar tekrar.

Örneğin:

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

Sadece beyan ederim:

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

ve accepts()tüm işleri benim için yapıyor.


15
İlgilenenler @acceptsiçin PEP 318'de bir uygulama var.
martineau

2
Bence yazım hatası var .. ilk yöntem kabul edilmelidir .. ikisini de "myMethod" olarak ilan
DevC

1
@DevC Hayır, bir yazım hatası gibi görünmüyor. Bu açıkça bir "kabul (..)" uygulaması olmadığından ve burada "kabul (())" aksi takdirde "myMethod (..)" başlangıcında iki satır tarafından yapılacak işi yapar - bu sadece uygun yorum.
Evgeni Sergeev

1
Yumru için üzgünüm, sadece geçilen argümanların türünü kontrol etmenin ve bir TypeError'u yükseltmenin kötü bir uygulama olarak kabul edildiğini belirtmek istedim çünkü örneğin sadece şamandıraları kontrol ederse bir int'i kabul etmeyecek ve normalde Kodun kendisi, maksimum esneklik için geçirilen farklı değer türlerine uyum sağlamalıdır.
Gustavo6046

2
Python'da tip kontrolü yapmanın önerilen yolu , dekoratörün isinstance()PEP 318 uygulamasında yapıldığı gibi yerleşik işlevdir . Onun bu yana classinfoargüman da Gustavo6046 en (geçerli) itirazlar @ azaltacaktır kullanmadan, bir veya birden fazla türleri olabilir. Python'un da Numbersoyut bir temel sınıfı vardır, bu nedenle çok genel testler isinstance(42, numbers.Number)mümkündür.
martineau

48

Dekoratörler, ek işlevlerle şeffaf bir şekilde "sarmak" istediğiniz herhangi bir şey için kullanılır.

Django bunları görüntüleme işlevlerinde "oturum açma gerekli" işlevselliğini kaydırmak ve filtre işlevlerini kaydetmek için kullanır .

Sınıflara adlandırılmış günlükler eklemek için sınıf dekoratörleri kullanabilirsiniz .

Mevcut bir sınıfa veya işlevin davranışına "yapışabileceğiniz" yeterince genel işlevler, dekorasyon için adil bir oyundur.

PEPhon-Dev haber grubunda PEP 318 - Fonksiyonlar ve Yöntemler için Dekoratörler tarafından işaret edilen kullanım örneklerinin bir tartışması da var .


Cherrypy, hangi işlevlerin genel ve hangilerinin gizli işlevler olduğunu düz tutmak için @ cherrypy.expose öğesini kullanır. Bu benim ilk girişimdi ve ben de orada oluşmaya alıştım.
Marc Maxmeister

26

Nosetestler için, birkaç parametre setiyle birim test fonksiyonu veya yöntemi sağlayan bir dekoratör yazabilirsiniz:

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

23

Twisted kütüphanesi, eşzamansız bir işlevin eşzamanlı olduğu yanılsamasını vermek için jeneratörlerle birlikte dekoratörleri kullanır. Örneğin:

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

Bunu kullanarak, bir ton küçük geri arama fonksiyonuna bölünmüş olan kod, doğal olarak tek bir blok olarak yazılabilir, bu da anlaşılmasını ve bakımını kolaylaştırır.


14

Bariz bir kullanım elbette günlüğe kaydetme içindir:

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

Bunları çoğunlukla hata ayıklama (argümanlarını ve sonucunu yazdıran bir fonksiyonun etrafındaki sarmalayıcı) ve doğrulama (örneğin, bir argümanın doğru tipte olup olmadığını kontrol etmek için veya web uygulaması durumunda, kullanıcının belirli bir numarayı aramak için yeterli ayrıcalığa sahip olup olmadığını kontrol etmek için kullanıyorum. yöntem).


6

Bir iş parçacığı güvenli yapmak için aşağıdaki dekoratör kullanıyorum. Kodu daha okunaklı hale getirir. John Fouhy tarafından önerilene neredeyse benzer, ancak fark, tek bir işlev üzerinde çalışmak ve açık bir şekilde bir kilit nesnesi oluşturmaya gerek olmamasıdır.

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
Bu, bu şekilde dekore edilmiş her işlevin kendi kilidi olduğu anlamına mı geliyor?
yas

1
@grieve yes, dekoratör her kullanıldığında (çağrıldığında), dekore edilen işlev / yöntem için yeni bir kilit nesnesi oluşturur.
martineau

5
Bu gerçekten tehlikeli. İnc_var () yöntemi aynı anda yalnızca bir kişinin arayabildiği için "threadsafe" dir. Bununla birlikte, yöntem "var" üye değişkeni üzerinde çalıştığı ve muhtemelen diğer değişkenler de "var" üye değişkeni üzerinde çalışabileceği ve kilit paylaşılmadığından, bu girişler güvenli değildir. İşleri bu şekilde yapmak X sınıfı kullanıcısına yanlış bir güvenlik hissi verir.
Bob Van Zant

Tek kilit kullanılana kadar güvenli değildir.
Chandu

5

Dekoratörler ya bir işlevin özelliklerini tanımlamak için ya da onu değiştiren ortak plaka olarak kullanılır; tamamen farklı fonksiyonları geri getirmeleri mümkün ancak karşı sezgiseldir. Buradaki diğer yanıtlara bakıldığında, en yaygın kullanımlardan biri, günlüğe kaydetme, profil oluşturma, güvenlik kontrolleri vb.Gibi diğer bazı işlemlerin kapsamını sınırlamak gibi görünüyor.

CherryPy, URL'leri nesnelerle ve sonunda yöntemlerle eşleştirmek için nesne dağıtımı kullanır. Bu yöntemler üzerindeki dekoratörler CherryPy'nin bu yöntemleri kullanmasına izin verilip verilmediğini gösterir. Örneğin , öğreticiden uyarlanmıştır :

class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())

Bu doğru değil. Dekoratör bir işlevin davranışını tamamen değiştirebilir.
özyinelemeli

Tamam. Ancak bir dekoratör ne sıklıkla "bir işlevin davranışını tamamen değiştirir?" Gördüğüm kadarıyla, özellikleri belirtmek için kullanılmadıklarında, sadece kaynak plakası kodu için kullanılırlar. Cevabımı düzenledim.
Nikhil Chelliah

5

Sosyal ağ web uygulaması üzerinde çalışırken son zamanlarda kullandım. Topluluk / Gruplar için, yeni tartışma oluşturmak ve o grubun üyesi olmanız gereken bir mesajı yanıtlamak için üyelik yetkisi vermem gerekiyordu. Böylece, bir dekoratör yazdım @membership_requiredve bence gerekli olan yere koydum.


1

Bu dekoratörü parametreyi düzeltmek için kullanıyorum

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']

Bu, bazı fonksiyonların "wanN" argümanını geçmesi gerektiğinde yeniden yazdığımda yazdı, ancak eski kodlarımda sadece N veya 'N' yi geçtim


1

Dekoratör, işlev yöntemi değişkenlerini kolayca oluşturmak için kullanılabilir.

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
Örneđin için teţekkürler, ama (apoljiler) WTF demeliyim - Bunu neden kullanasýn? İnsanları şaşırtmak için BÜYÜK bir potansiyele sahiptir. Tabii ki, son durum kullanım gereksinimlerine saygı duyuyorum, ancak birçok deneyimsiz Python geliştiricisinin sahip olduğu ortak bir soruna çarpıyorsunuz - sınıfları yeterince kullanmıyorsunuz. Yani, basit bir sınıf var saymak, başlatmak ve kullanmak. Noobs, bırakma (sınıf tabanlı olmayan kod) yazma ve sınıfsal işlevsellik eksikliğiyle ayrıntılı çözümlerle başa çıkmaya çalışır. Lütfen yapma? Lütfen? Arp için özür dilerim, cevabınız için teşekkür ederim, ama benim için bir butona bastınız.
Kevin J. Rice

Eğer bana kod incelemesi için bir çekme isteği olarak geldi, ben -1 üzerinde olurdu ve bu yüzden de iyi python olarak bu -1.
Techdragon

Şirin. Aptalca ama sevimli. :) Ara sıra işlev özniteliğini umursamıyorum, ancak tipik bir Python kodunda çok nadir bir şeydir, birini kullanacaksam, bir dekoratör altında gizlemek yerine bunu açıkça yapmayı tercih ederim.
PM 2Ring
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.