Jakie są typowe zastosowania dekoratorów Python? [Zamknięte]

337

Chociaż lubię myśleć o sobie jako o dość kompetentnym kodzie Pythona, jednym z aspektów języka, którego nigdy nie byłem w stanie zrozumieć, są dekoratorzy.

Wiem, czym one są (powierzchownie), przeczytałem samouczki, przykłady, pytania na temat przepełnienia stosu i rozumiem składnię, mogę pisać własne, czasami używam @classmethod i @staticmethod, ale nigdy nie przychodzi mi do głowy, aby użyć dekorator, aby rozwiązać problem we własnym kodzie Python. Nigdy nie spotykam problemu, w którym myślę: „Hmm… to wygląda jak praca dla dekoratora!”

Zastanawiam się, czy moglibyście podać przykłady zastosowania dekoratorów we własnych programach i mam nadzieję, że będę miał „A-ha!” chwila i zdobądź je.

Dana
źródło
5
Dekoratory są również przydatne do zapamiętywania - czyli buforowania powolnego do obliczenia wyniku funkcji. Dekorator może zwrócić funkcję, która sprawdza dane wejściowe, a jeśli zostały już przedstawione, zwraca wynik z pamięci podręcznej.
Peter
1
Zauważ, że Python ma wbudowany dekorator, functools.lru_cachektóry robi dokładnie to, co powiedział Peter, od Python 3.2, wydany w lutym 2011 roku.
Taegyung
Treść biblioteki Python Decorator powinna dać ci dobry pomysł na inne zastosowania.
martineau

Odpowiedzi:

126

Używam dekoratorów głównie do pomiaru czasu

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):
    ...
RSabet
źródło
13
W Uniksie time.clock()mierzy czas procesora. Możesz time.time()zamiast tego użyć, jeśli chcesz zmierzyć czas naścienny.
Jabba
20
Świetny przykład! Nie mam pojęcia, co to robi. Wyjaśnienie, co tam robisz i jak dekorator rozwiązuje problem, byłoby bardzo miłe.
MeLight
7
Cóż, mierzy czas potrzebny myFunctiondo uruchomienia ...
RSabet
98

Użyłem ich do synchronizacji.

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

Jak wskazano w komentarzach, od Python 2.5 można używać withinstrukcji w połączeniu z threading.Lock (lub multiprocessing.Lockod wersji 2.6), aby uprościć implementację dekoratora do:

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

Niezależnie od tego używasz go w następujący sposób:

import threading
lock = threading.Lock()

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

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

Zasadniczo po prostu umieszcza lock.acquire()/ lock.release()po obu stronach wywołania funkcji.

John Fouhy
źródło
18
Być może uzasadnione, ale dekoratorzy są z natury mylący, szczególnie. do noobów z pierwszego roku, którzy za tobą próbują zmodyfikować kod. Unikaj tego z prostotą: po prostu do_something () umieść swój kod w bloku pod „with lock:”, a każdy wyraźnie zobaczy twój cel. Dekoratorzy są w dużym stopniu nadużywani przez ludzi, którzy chcą wyglądać na inteligentnych (a wielu tak naprawdę jest), ale potem kod przychodzi do zwykłych śmiertelników i zostaje zlikwidowany.
Kevin J. Rice,
18
@ KevinJ.Rice Ograniczanie kodu, aby „noobowie z pierwszego roku” mogli lepiej zrozumieć, że to okropna praktyka. Składnia dekoratora jest znacznie łatwiejsza do odczytania i znacznie oddziela kod.
TaylerJones
18
@TaylerJones, czytelność kodu jest prawie moim najwyższym priorytetem podczas pisania. Kod jest odczytywany 7+ razy za każdym razem, gdy jest modyfikowany. Trudny do zrozumienia kod (dla noobów lub ekspertów pracujących pod presją czasu) to dług techniczny, który należy spłacać za każdym razem, gdy ktoś odwiedza drzewo źródłowe.
Kevin J. Rice,
@TaylerJones Jednym z najważniejszych zadań dla programisty jest zapewnienie przejrzystości.
JDOaktown
71

Korzystam z dekoratorów do sprawdzania parametrów, które są przekazywane do moich metod Python za pośrednictwem niektórych RMI. Zamiast więc powtarzać liczenie tego samego parametru, mumbo-jumbo podnoszące wyjątki raz po raz.

Na przykład zamiast:

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

Po prostu deklaruję:

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

i accepts()wykonuje całą pracę dla mnie.

Szymon
źródło
15
Dla wszystkich zainteresowanych jest implementacja @acceptsw PEP 318.
martineau
2
Myślę, że jest literówka .. pierwsza metoda powinna być akceptowana .. zadeklarowałeś oba jako „myMethod”
DevC
1
@DevC Nie, to nie wygląda na literówkę. Ponieważ wyraźnie nie jest to implementacja „akceptuje (..)”, a tutaj „akceptuje (..)” wykonuje pracę, która w innym przypadku byłaby wykonywana przez dwie linie na początku „myMethod (..)” - to jest tylko pasująca interpretacja.
Evgeni Sergeevev
1
Przepraszam za bump, chciałem tylko zaznaczyć, że sprawdzanie typu przekazywanych argumentów i wywoływania TypeError w przeciwnym razie jest uważane za złą praktykę, ponieważ nie będzie akceptować np. Int, jeśli sprawdza tylko zmienne, i ponieważ zwykle sam kod powinien dostosować się do różnego rodzaju przekazywanych wartości, aby zapewnić maksymalną elastyczność.
Gustavo6046 16.04.16
2
Zalecanym sposobem sprawdzania typu w Pythonie jest wbudowana isinstance()funkcja, tak jak ma to miejsce w implementacji dekoratora przez PEP 318 . Ponieważ jego classinfoargumentem może być jeden lub więcej typów, użycie go złagodziłoby również (prawidłowe) zastrzeżenia @ Gustavo6046. Python ma również Numberabstrakcyjną klasę podstawową, więc isinstance(42, numbers.Number)możliwe są bardzo ogólne testy .
martineau
48

Dekoratory służą do wszystkiego, co chcesz transparentnie „zawinąć” z dodatkową funkcjonalnością.

Django używa ich do pakowania funkcji „wymagane logowanie” do funkcji przeglądania , a także do rejestrowania funkcji filtrów .

Do dodawania nazwanych dzienników do klas można użyć dekoratorów klas .

Każda wystarczająco ogólna funkcjonalność, którą można „przyczepić” do istniejącego zachowania klasy lub funkcji, jest uczciwą grą dla dekoracji.

W grupie dyskusyjnej Python-Dev omawia się także przypadki użycia, na które wskazuje PEP 318 - Dekoratorzy funkcji i metod .

cdleary
źródło
Cherrypy używa @ cherrypy.expose, aby dokładnie określić, które funkcje są publiczne, a które ukryte. To było moje pierwsze wprowadzenie i przyzwyczaiłem się do tego.
Marc Maxmeister,
26

W przypadku testów nosa możesz napisać dekorator, który udostępnia funkcję lub metodę testu jednostkowego z kilkoma zestawami parametrów:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected
Torsten Marek
źródło
23

Biblioteka Twisted używa dekoratorów w połączeniu z generatorami, aby dać złudzenie, że funkcja asynchroniczna jest synchroniczna. Na przykład:

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

Korzystając z tego, kod, który zostałby rozbity na mnóstwo małych funkcji zwrotnych, można napisać całkiem naturalnie jako pojedynczy blok, co znacznie ułatwia jego zrozumienie i utrzymanie.

DNS
źródło
14

Jednym z oczywistych zastosowań jest oczywiście logowanie:

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()
Mister Metaphor
źródło
10

Używam ich głównie do debugowania (owijania wokół funkcji, która wypisuje jej argumenty i wyniki) i weryfikacji (np. Do sprawdzania, czy argument jest poprawnego typu lub, w przypadku aplikacji internetowej, czy użytkownik ma wystarczające uprawnienia do wywołania określonego metoda).

DzinX
źródło
6

Korzystam z następującego dekoratora do tworzenia funkcji wątkowo bezpiecznej. Dzięki temu kod jest bardziej czytelny. Jest prawie podobny do zaproponowanego przez Johna Fouhy, ale różnica polega na tym, że działa się na jednej funkcji i nie ma potrzeby jawnego tworzenia obiektu blokady.

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
luc
źródło
1
Czy to oznacza, że ​​każda tak ozdobiona funkcja ma swój własny zamek?
zasmucają
1
@ grieve tak, za każdym razem, gdy dekorator jest używany (wywoływany), tworzy nowy obiekt blokady dla dekorowanej funkcji / metody.
martineau,
5
To naprawdę niebezpieczne. Metoda inc_var () jest „wątkowo bezpieczna”, ponieważ tylko jedna osoba może ją wywoływać jednocześnie. To powiedziawszy, ponieważ metoda działa na zmiennej elementu „var” i przypuszczalnie inne metody mogą również działać na zmiennej elementu „var”, a te dostępu nie są bezpieczne dla wątków, ponieważ zamek nie jest współdzielony. Takie postępowanie daje użytkownikowi klasy X fałszywe poczucie bezpieczeństwa.
Bob Van Zant
To nie jest bezpieczne, dopóki nie zostanie użyty pojedynczy zamek.
Chandu,
5

Dekoratory są używane albo do zdefiniowania właściwości funkcji, albo jako płyta zmieniająca ją; jest możliwe, ale sprzeczne z intuicją, aby zwracały zupełnie inne funkcje. Patrząc na inne odpowiedzi tutaj, wydaje się, że jednym z najczęstszych zastosowań jest ograniczenie zakresu innego procesu - rejestrowania, profilowania, kontroli bezpieczeństwa itp.

CherryPy używa wysyłania obiektów, aby dopasować adresy URL do obiektów i, ostatecznie, metod. Dekoratorzy na tych metod sygnalizować czy CherryPy jest nawet dopuszczone do stosowania tych metod. Na przykład adaptacja z samouczka :

class HelloWorld:

    ...

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

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

cherrypy.quickstart(HelloWorld())
Nikhil Chelliah
źródło
To nie jest prawda. Dekorator może całkowicie zmienić zachowanie funkcji.
rekurencyjny
W porządku. Ale jak często dekorator „całkowicie zmienia zachowanie funkcji?” Z tego, co widziałem, gdy nie są one używane do określania właściwości, są po prostu używane do kodu typu „kocioł”. Zredagowałem swoją odpowiedź.
Nikhil Chelliah
5

Ostatnio ich używałem, pracując nad aplikacją sieci społecznościowych. W przypadku społeczności / grup miałem upoważnić członka do utworzenia nowej dyskusji i odpowiedzi na wiadomość, że musisz być członkiem tej konkretnej grupy. Napisałem dekoratora @membership_requiredi umieściłem go tam, gdzie tego wymagałem.

aatifh
źródło
1

Używam tego dekoratora do naprawy parametru

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

to napisane, gdy refaktoryzuję, niektóre funkcje muszą przekazać argument „wanN”, ale w moich starych kodach przekazałem tylko N lub „N”

HVNSweeting
źródło
1

Dekorator może być używany do łatwego tworzenia zmiennych metod funkcji.

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
użytkownik1476056
źródło
6
Dziękuję za twój przykład, ale (apolgie) muszę powiedzieć WTF - dlaczego miałbyś to wykorzystać? Ma OGROMNY potencjał do dezorientacji ludzi. Oczywiście, szanuję potrzeby w zastosowaniach typu edge-case, ale napotykasz na często spotykany problem wielu niedoświadczonych programistów Pythona - niewystarczające wykorzystanie klas. Oznacza to, że wystarczy mieć prosty var var count, zainicjować go i użyć. Noobs zwykle piszą drop-thru (kod nie oparty na klasach) i próbują poradzić sobie z brakiem funkcjonalności klasy za pomocą skomplikowanych obejść. Proszę nie Proszę? przepraszam, że harfuję, dziękuję za odpowiedź, ale trafiłeś w gorący przycisk dla mnie.
Kevin J. Rice,
Byłbym na tym -1, jeśli pojawiłoby się to jako żądanie ściągnięcia dla mnie do recenzji kodu, a więc mam też -1 na tym jako dobry python.
Techdragon
Uroczy. Głupie, ale urocze. :) Nie przeszkadza mi okazjonalny atrybut funkcji, ale są one tak rzadką rzeczą w typowym kodzie Pythona, że ​​jeśli mam go użyć, wolę to zrobić jawnie, niż ukryć go pod dekoratorem.
PM 2,