


Detailed usage introduction of Python decorators (code examples)
This article brings you a detailed usage introduction (code example) about Python decorators. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
In Python, decorators are generally used to decorate functions to implement public functions and achieve code reuse. Add @xxxx before the function definition, and then the function will inject certain behaviors, which is amazing! However, this is just syntactic sugar.
Scenario
Assume that there are some working functions used to process data differently:
def work_bar(data): pass def work_foo(data): pass
We want to do this before/after the function call Output log, what should I do?
Fool’s solution
logging.info('begin call work_bar') work_bar(1) logging.info('call work_bar done')
What if there are multiple code calls? It scares me just thinking about it!
Function packaging
The fool's solution is nothing more than having too much code redundancy, and each function call must be written againlogging
. This part of the redundant logic can be encapsulated into a new function:
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')
In this way, smart_work_bar
can be called every time:
smart_work_bar(1) # ... smart_work_bar(some_data)
General closure
Looks perfect... However, when work_foo
also has the same need, do we need to implement smart_work_foo
again? This is obviously unscientific!
Don’t worry, we can use closures:
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
This function receives a function object (proxy function) as a parameter and returns a proxy function. When calling the proxy function, the log is output first, then the proxy function is called, the log is output after the call is completed, and finally the call result is returned. In this way, doesn’t it achieve the purpose of generalization? ——For any proxy function func
, log_call
can be easily handled.
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)
In line 1
, log_call
receives the parameter work_bar
, returns a proxy function proxy
, and assigns it to smart_work_bar
. In line 4
, call smart_work_bar
, which is the proxy function proxy
, first output the log, and then call func
, which is work_bar
, and finally output the log. Notice that in the proxy function, func
is closely related to the work_bar
object passed in. This is the closure.
Again, you can overwrite the proxy function name. Prefixing a new name with smart_
is still a bit cumbersome:
work_bar = log_call(work_bar) work_foo = log_call(work_foo) work_bar(1) work_foo(1)
Syntactic sugar
Let’s take a look at the following code first:
def work_bar(data): pass work_bar = log_call(work_bar) def work_foo(data): pass work_foo = log_call(work_foo)
Although the code is not redundant, it is still not intuitive enough. At this time, syntax sugar comes~~~
@log_call def work_bar(data): pass
So, pay attention to one thing (Emphasis ), the function of @log_call
here is just: tell Python
Compiler inserts codework_bar = log_call(work_bar)
.
Evaluation Decorator
Let’s first guess what the decorator eval_now
does?
def eval_now(func): return func()
It seems strange. There is no proxy function defined. Is it considered a decorator?
@eval_now def foo(): return 1 print foo
This code outputs 1
, which is to call and evaluate the function. So what's the use? Can't we just write foo = 1
directly? In this simple example, it is of course possible to write like this. Let's look at a more complex example - initializing a log object:
# some other code before... # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) # again some other code after...
Use eval_now
:
# some other code before... @eval_now def logger(): # log format formatter = logging.Formatter( '[%(asctime)s] %(process)5d %(levelname) 8s - %(message)s', '%Y-%m-%d %H:%M:%S', ) # stdout handler stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler.setFormatter(formatter) stdout_handler.setLevel(logging.DEBUG) # stderr handler stderr_handler = logging.StreamHandler(sys.stderr) stderr_handler.setFormatter(formatter) stderr_handler.setLevel(logging.ERROR) # logger object logger = logging.Logger(__name__) logger.setLevel(logging.DEBUG) logger.addHandler(stdout_handler) logger.addHandler(stderr_handler) return logger # again some other code after...
The purpose of the two pieces of code is the same, But the latter is obviously clearer and has the style of code blocks. More importantly, function calls are initialized in the local namespace to avoid temporary variables (such as formatter
, etc.) from polluting external namespaces (such as the global).
Decorator with parameters
Define a decorator for recording slow function calls:
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
3
、5## The # line samples the current time before and after the function call, and the
7 line calculates the call time. If it takes more than one second, a warning log is output.
@log_slow_call def sleep_seconds(seconds): time.sleep(seconds) sleep_seconds(0.1) # 没有日志输出 sleep_seconds(2) # 输出警告日志
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
@xxxxThe syntactic sugar always calls the decorator with the decorated function as the parameter, which means there is no chance to pass
thresholdparameter. How to do it? ——Use a closure to encapsulate the
threshold parameter:
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)
log_slow_call(threshold=0.5) calls the return function
decorator, and the function has Closure variable
threshold, value is
0.5.
decoratorRedecoration
sleep_seconds.
@log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
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)
A default threshold (no call); usage
B custom threshold (with call).
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds) # Case B @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
A, what happens is
log_slow_call(sleep_seconds), that is, the
func parameter is non-empty, this is a direct adjustment
decoratorWrap and return (the threshold is the default).
用法B
中,先发生的是log_slow_call(threshold=0.5)
,func
参数为空,直接返回新的装饰器decorator
,关联闭包变量threshold
,值为0.5
;然后,decorator
再装饰函数sleep_seconds
,即decorator(sleep_seconds)
。注意到,此时threshold
关联的值是0.5
,完成定制化。
你可能注意到了,这里最好使用关键字参数这种调用方式——使用位置参数会很丑陋:
# Case B- @log_slow_call(None, 0.5) def sleep_seconds(seconds): time.sleep(seconds)
当然了,函数调用尽量使用关键字参数是一种极佳实践,含义清晰,在参数很多的情况下更是如此。
智能装饰器
上节介绍的写法,嵌套层次较多,如果每个类似的装饰器都用这种方法实现,还是比较费劲的(脑子不够用),也比较容易出错。
假设有一个智能装饰器smart_decorator
,修饰装饰器log_slow_call
,便可获得同样的能力。这样,log_slow_call
定义将变得更清晰,实现起来也更省力啦:
@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
脑洞开完,smart_decorator
如何实现呢?其实也简单:
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
smart_decorator
实现了以后,设想就成立了!这时,log_slow_call
,就是decorator_proxy
(外层),关联的闭包变量decorator
是本节最开始定义的log_slow_call
(为了避免歧义,称为real_log_slow_call
)。log_slow_call
支持以下各种用法:
# Case A @log_slow_call def sleep_seconds(seconds): time.sleep(seconds)
用法A
中,执行的是decorator_proxy(sleep_seconds)
(外层),func
非空,kwargs
为空;直接执行decorator(func=func, **kwargs)
,即real_log_slow_call(sleep_seconds)
,结果是关联默认参数的proxy
。
# Case B # Same to Case A @log_slow_call() def sleep_seconds(seconds): time.sleep(seconds)
用法B
中,先执行decorator_proxy()
,func
及kwargs
均为空,返回decorator_proxy
对象(内层);再执行decorator_proxy(sleep_seconds)
(内层);最后执行decorator(func, **kwargs)
,等价于real_log_slow_call(sleep_seconds)
,效果与用法A
一致。
# Case C @log_slow_call(threshold=0.5) def sleep_seconds(seconds): time.sleep(seconds)
用法C
中,先执行decorator_proxy(threshold=0.5)
,func
为空但kwargs
非空,返回decorator_proxy
对象(内层);再执行decorator_proxy(sleep_seconds)
(内层);最后执行decorator(sleep_seconds, **kwargs)
,等价于real_log_slow_call(sleep_seconds, threshold=0.5)
,阈值实现自定义!
The above is the detailed content of Detailed usage introduction of Python decorators (code examples). For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



VS Code extensions pose malicious risks, such as hiding malicious code, exploiting vulnerabilities, and masturbating as legitimate extensions. Methods to identify malicious extensions include: checking publishers, reading comments, checking code, and installing with caution. Security measures also include: security awareness, good habits, regular updates and antivirus software.

In VS Code, you can run the program in the terminal through the following steps: Prepare the code and open the integrated terminal to ensure that the code directory is consistent with the terminal working directory. Select the run command according to the programming language (such as Python's python your_file_name.py) to check whether it runs successfully and resolve errors. Use the debugger to improve debugging efficiency.

VS Code can run on Windows 8, but the experience may not be great. First make sure the system has been updated to the latest patch, then download the VS Code installation package that matches the system architecture and install it as prompted. After installation, be aware that some extensions may be incompatible with Windows 8 and need to look for alternative extensions or use newer Windows systems in a virtual machine. Install the necessary extensions to check whether they work properly. Although VS Code is feasible on Windows 8, it is recommended to upgrade to a newer Windows system for a better development experience and security.

VS Code can be used to write Python and provides many features that make it an ideal tool for developing Python applications. It allows users to: install Python extensions to get functions such as code completion, syntax highlighting, and debugging. Use the debugger to track code step by step, find and fix errors. Integrate Git for version control. Use code formatting tools to maintain code consistency. Use the Linting tool to spot potential problems ahead of time.

PHP is suitable for web development and rapid prototyping, and Python is suitable for data science and machine learning. 1.PHP is used for dynamic web development, with simple syntax and suitable for rapid development. 2. Python has concise syntax, is suitable for multiple fields, and has a strong library ecosystem.

VS Code is available on Mac. It has powerful extensions, Git integration, terminal and debugger, and also offers a wealth of setup options. However, for particularly large projects or highly professional development, VS Code may have performance or functional limitations.

Golang is more suitable for high concurrency tasks, while Python has more advantages in flexibility. 1.Golang efficiently handles concurrency through goroutine and channel. 2. Python relies on threading and asyncio, which is affected by GIL, but provides multiple concurrency methods. The choice should be based on specific needs.

PHP is mainly procedural programming, but also supports object-oriented programming (OOP); Python supports a variety of paradigms, including OOP, functional and procedural programming. PHP is suitable for web development, and Python is suitable for a variety of applications such as data analysis and machine learning.
