English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
In Python, i decoratori di solito vengono utilizzati per decorare le funzioni, implementare funzionalità comuni e raggiungere l'obiettivo della riutilizzabilità del codice. Aggiungendo @xxxx prima della definizione della funzione, la funzione viene integrata con certi comportamenti, è incredibile! Tuttavia, questo è solo uno zucchero della sintassi.
Scena
Supponiamo che ci siano alcune funzioni di lavoro, che vengono utilizzate per elaborare i dati in modo diverso:
def work_bar(data): pass def work_foo(data): pass
Cosa fare se vogliamo stampare il log prima o dopo la chiamata di una funzione?
Soluzione stregone
logging.info('begin call work_bar') work_bar(1) logging.info('call work_bar done')
Cosa succede se ci sono molte chiamate di codice? Si pensa che abbia paura!
Imballaggio di funzione
La soluzione stregone non fa altro che avere troppo codice ridondante, ogni volta che si chiama una funzione è necessario scrivere logging. Si può encapsulare questa logica ridondante in una nuova funzione:
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')
Così, ogni volta che si chiama smart_work_bar è sufficiente:
smart_work_bar(1) # ... smart_work_bar(some_data)
Funzione chiusa universale
Sembra perfetto... tuttavia, quando work_foo ha anche lo stesso bisogno, dobbiamo ancora implementare di nuovo smart_work_foo? Questo sembra ovviamente non scientifico!
Non si preoccupare, possiamo utilizzare una funzione chiusa:
def log_call(func): def proxy(*args, **kwargs): logging.info('begin call: {name}'.format(name=func.func_name)) result = func(*args, **kwargs) logging.info('call done: {name}'.format(name=func.func_name)) return result return proxy
Questa funzione accetta un oggetto funzione (funzione代理ata) come parametro e restituisce una funzione proxy. Quando si chiama la funzione proxy, viene prima stampato il log, poi viene chiamata la funzione代理ata, viene completata la chiamata e infine viene restituito il risultato della chiamata. Non raggiunge quindi l'obiettivo della generalizzazione? - Per qualsiasi funzione proxy func, log_call può affrontare agevolmente.
smart_work_bar = log_call(work_bar) smart_work_foo = log_call(work_foo) smart_work_bar(1) smart_work_foo(1) # ... smart_work_bar(some_data) smart_work_foo(some_data)
Nella riga 1, log_call riceve il parametro work_bar, restituisce una funzione proxy e la assegna a smart_work_bar. Nella riga 4, chiama smart_work_bar, che è la funzione proxy, prima di scrivere il log e poi chiama func, che è work_bar, infine scrive di nuovo il log. Notare che nel proxy function, func è strettamente associato all'oggetto work_bar passato, questo è il concetto di closure.
Inoltre, è possibile sovrascrivere il nome della funzione proxy, ma aggiungere il prefisso 'smart_' sembra un po' ingombrante:
work_bar = log_call(work_bar) work_foo = log_call(work_foo) work_bar(1) work_foo(1)
Zucchero di sintassi
Prima di tutto, guardiamo il seguente codice:
def work_bar(data): pass work_bar = log_call(work_bar) def work_foo(data): pass work_foo = log_call(work_foo)
Anche se il codice non ha più ridondanza, è ancora non sufficientemente intuitivo. Ecco che arriva lo zucchero di sintassi~~~
@log_call def work_bar(data): pass
Quindi, attenzione (ecco il punto fondamentale), l'azione di @log_call qui è solo: informare il compilatore Python di inserire il codice work_bar = log_call(work_bar).
Decoratore di valutazione
Prima di tutto, proviamo a indovinare qual è l'azione del decoratore eval_now?
def eval_now(func): return func()
Sembra strano, giusto? Non è stato definito un proxy function, è un decoratore?
@eval_now def foo(): return 1 print foo
Questo codice outputta 1, ovvero chiama e valuta la funzione. Allora, a cosa serve? Non va bene scrivere foo = 1? In questo esempio semplice, è ovviamente possibile farlo. Vediamo un esempio più complesso - inizializzare un oggetto di log:
# altre code prima... # formato del log formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # handler stdout stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # handler stderr stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # oggetto logger logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) # altre code dopo...
Utilizzando il metodo eval_now:
# altre code prima... @eval_now def logger(): # formato del log formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # handler stdout stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # handler stderr stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # oggetto logger logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) return logger # altre code dopo...
Gli obiettivi dei due segmenti di codice sono gli stessi, ma l'ultimo è ovviamente più chiaro e ha un tocco di stile di blocco di codice. Inoltre, la chiamata di funzione viene completata nel namespace locale, evitando la contaminazione del namespace esterno da variabili temporanee (come formatter, ad esempio).
Decoratore con parametri
Definire un decoratore per registrare le chiamate di funzione lente:
def log_slow_call(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > 1: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
La riga 3 e 5 raccolgono il tempo corrente prima e dopo la chiamata di funzione, la riga 7 calcola il tempo di chiamata, se superiore a un secondo viene generato un messaggio di avviso di log.
@log_slow_call def sleep_seconds(seconds): time.sleep(seconds) sleep_seconds(0.1) # Nessun output di log sleep_seconds(2) # Output del log di avviso
Tuttavia, la configurazione del valore di soglia deve essere decisa caso per caso, diversi funzioni potrebbero impostare valori diversi. Se fosse possibile parametrizzare il valore di soglia sarebbe meglio:
def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
Tuttavia, la grammatica a sugaro @xxxx chiama sempre il decoratore con il funzione decorato come parametro, il che significa che non c'è modo di passare il parametro threshold. Cosa fare? - Incapsulare il parametro threshold in una funzione di chiusura:
def log_slow_call(threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy return decorator @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
In questo modo, la chiamata log_slow_call(threshold=0.5) restituisce la funzione decorator, che possiede la variabile di chiusura threshold con valore 0.5. decorator aggiunge decorazione alla funzione sleep_seconds.
Utilizzando il valore di soglia predefinito, la chiamata della funzione non può essere omessa:
@log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
Le Vergine potrebbero non essere soddisfatti del primo paio di parentesi, quindi possono migliorare così:
def log_slow_call(func=None, threshold=1): def decorator(func): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy if func is None: return decorator else: return decorator(func)
Questo metodo è compatibile con due modalità di utilizzo diverse, modalità A con valore di soglia predefinito (nessuna chiamata); modalità B con soglia personalizzata (con chiamata).
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds) # Caso B @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
Nella modalità di utilizzo A, ciò che accade è log_slow_call(sleep_seconds), ossia il parametro func non è vuoto, che chiama direttamente il decorator per impacchettare e restituire (il valore del limite è predefinito).
Nella modalità di utilizzo B, ciò che accade prima è log_slow_call(threshold=0.5), con il parametro func vuoto, che restituisce direttamente il nuovo decoratore decorator, associato all'oggetto variabile di chiusura threshold, con valore 0.5; quindi, decorator aggiunge decorazione alla funzione sleep_seconds, ossia decorator(sleep_seconds). Nota che in questo momento, il valore associato a threshold è 0.5, completando la personalizzazione.
Potresti aver notato che è meglio utilizzare questo modo di chiamata con parametri di nome - utilizzare i parametri di posizione potrebbe essere brutto:
# Caso B- @log_slow_call(None, 0.5) def sleep_seconds(seconds): time.sleep(seconds)
Certo, utilizzare i parametri di chiamata con nome è una pratica eccellente, chiara e ancora di più in presenza di molti parametri.
Decoratore intelligente
Il metodo presentato nella sezione precedente, con un livello di annidamento relativamente alto, potrebbe essere piuttosto faticoso (mentre il cervello non riesce a tenere il passo) e anche più facile a commettere errori se ogni decoratore simile viene implementato in questo modo.
Suppose there is an intelligent decorator smart_decorator, which decorates the decorator log_slow_call, so we can get the same ability. In this way, the definition of log_slow_call will become clearer, and it will also be easier to implement:
@smart_decorator def log_slow_call(func, threshold=1): def proxy(*args, **kwargs): start_ts = time.time() result = func(*args, **kwargs) end_ts = time.time() seconds = start_ts - end_ts if seconds > threshold: logging.warn('slow call: {name} in {seconds}s'.format( name=func.func_name, seconds=seconds, )) return result return proxy
After the brainstorming, how to implement smart_decorator? In fact, it's also simple:
def smart_decorator(decorator): def decorator_proxy(func=None, **kwargs): if func is not None: return decorator(func=func, **kwargs) def decorator_proxy(func): return decorator(func=func, **kwargs) return decorator_proxy return decorator_proxy
After smart_decorator is implemented, the idea is established! At this time, log_slow_call is decorator_proxy(outer layer), and the associated closure variable decorator is the log_slow_call defined at the beginning of this section (to avoid ambiguity, called real_log_slow_call). log_slow_call supports the following various usages:
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds)
In A mode, the executed is decorator_proxy(sleep_seconds)(outer layer), func is not empty, kwargs is empty; execute decorator(func=func, **kwargs) directly, that is real_log_slow_call(sleep_seconds), the result is the proxy associated with default parameters.
# Caso B # Uguale al Caso A @log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
Nella modalità B, esegui prima decorator_proxy(), func e kwargs sono vuoti, restituisce l'oggetto decorator_proxy (interno); quindi esegui decorator_proxy(sleep_seconds)(interno); infine esegui decorator(func, **kwargs), equivalente a real_log_slow_call(sleep_seconds), l'effetto è lo stesso della modalità A.
# Caso C @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
Nella modalità C, esegui prima decorator_proxy(threshold=0.5), func è vuoto ma kwargs non vuoto, restituisce l'oggetto decorator_proxy (interno); quindi esegui decorator_proxy(sleep_seconds)(interno); infine esegui decorator(sleep_seconds, **kwargs), equivalente a real_log_slow_call(sleep_seconds, threshold=0.5), la soglia può essere personalizzata!
Dichiarazione: il contenuto di questo articolo è stato prelevato da Internet, il copyright spetta ai rispettivi autori, il contenuto è stato contribuito e caricato autonomamente dagli utenti di Internet, questo sito non detiene il diritto di proprietà, non è stato editato manualmente e non assume alcuna responsabilità legale. Se trovi contenuti sospetti di violazione del copyright, ti preghiamo di inviare una e-mail a: notice#oldtoolbag.com (al momento dell'invio dell'e-mail, sostituisci # con @) per segnalare, fornendo prove pertinenti. Una volta verificata, questo sito rimuoverà immediatamente il contenuto sospetto di violazione del copyright.