python装饰器的通俗理解_简单理解Python装饰器

Python有大量强大又贴心的特性,如果要列个最受欢迎排行榜,那么装饰器绝对会在其中。

刚接触装饰器,会觉得代码不多却难以理解。其实装饰器的语法本身挺简单的,复杂是因为同时混杂了其它的概念。下面我们一起抛去无关概念,简单地理解下Python的装饰器。

装饰器的原理

在解释器下跑个装饰器的例子,直观地感受一下

# make_bold就是装饰器,实现方式这里略去

>>> @make_bold

... def get_content():

... return "hello world"

...

>>> get_content()

"hello world"

被make_bold装饰的get_content,调用后返回结果会自动被b标签包住。怎么做到的呢,简单4步就能明白了。

1. 函数是对象

我们定义个get_content函数。这时get_content也是个对象,它能做所有对象的操作。

它有id,有type,有值。

>>> id(get_content)

140090200473112

>>> type(get_content)

>>> get_content

跟其他对象一样可以被赋值给其它变量。

>>> func_name = get_content

>>> func_name()

"hello world"

它可以当参数传递,也可以当返回值

>>> def foo(bar):

... print(bar())

... return bar

...

>>> func = foo(get_content)

hello world

>>> func()

"hello world"

2. 自定义函数对象

我们可以用class来构造函数对象。有成员函数__call__的就是函数对象了,函数对象被调用时正是调用的__call__。

class FuncObj(object):

def __init__(self, name):

print("Initialize")

self.name= name

def __call__(self):

print("Hi", self.name)

我们来调用看看。可以看到,函数对象的使用分两步:构造和调用(同学们注意了,这是考点)。

>>> fo = FuncObj("python")

Initialize

>>> fo()

"Hi python"

3. @是个语法糖

装饰器的@没有做什么特别的事,不用它也可以实现一样的功能,只不过需要更多的代码。

@make_bold

def get_content():

return "hello world"

上面的代码等价于下面的

def get_content():

return "hello world"

get_content = make_bold(get_content)

make_bold是个函数,要求入参是函数对象,返回值是函数对象。@的语法糖其实是省去了上面最后一行代码,使可读性更好。用了装饰器后,每次调用get_content,真正调用的是make_bold返回的函数对象。

4. 用类实现装饰器

入参是函数对象,返回是函数对象,如果第2步里的类的构造函数改成入参是个函数对象,不就正好符合要求吗?我们来试试实现make_bold。

class make_bold(object):

def __init__(self, func):

print("Initialize")

self.func = func

def __call__(self):

print("Call")

return "{}".format(self.func())

大功告成,看看能不能用。

>>> @make_bold

... def get_content():

... return "hello world"

...

Initialize

>>> get_content()

Call

"hello world"

成功实现装饰器!是不是很简单?

这里分析一下之前强调的构造和调用两个过程。我们去掉@语法糖好理解一些。

# 构造,使用装饰器时构造函数对象,调用了__init__

>>> get_content = make_bold(get_content)

Initialize

# 调用,实际上直接调用的是make_bold构造出来的函数对象

>>> get_content()

Call

"hello world"

到这里就彻底清楚了,完结撒花,可以关掉网页了~~~(如果只是想知道装饰器原理的话)

函数版装饰器

阅读源码时,经常见到用嵌套函数实现的装饰器,怎么理解?同样仅需4步。

1. def的函数对象初始化

用class实现的函数对象很容易看到什么时候构造的,那def定义的函数对象什么时候构造的呢?

# 这里的全局变量删去了无关的内容

>>> globals()

{}

>>> def func():

... pass

...

>>> globals()

{"func": }

不像一些编译型语言,程序在启动时函数已经构造那好了。上面的例子可以看到,执行到def会才构造出一个函数对象,并赋值给变量make_bold。

这段代码和下面的代码效果是很像的。

class NoName(object):

def __call__(self):

pass

func = NoName()

2. 嵌套函数

Python的函数可以嵌套定义。

def outer():

print("Before def:", locals())

def inner():

pass

print("After def:", locals())

return inner

#inner是在outer内定义的,所以算outer的局部变量。

#执行到def inner时函数对象才创建,因此每次调用outer都会创建一个新的inner。

#下面可以看出,每次返回的inner是不同的。

>>> outer()

Before def: {}

After def: {"inner": .inner at 0x7f0b18fa0048>}

.inner at 0x7f0b18fa0048>

>>> outer()

Before def: {}

After def: {"inner": .inner at 0x7f0b18fa00d0>}

.inner at 0x7f0b18fa00d0>

3. 闭包

嵌套函数有什么特别之处?因为有闭包。

def outer():

msg = "hello world"

def inner():

print(msg)

return inner

下面的试验表明,inner可以访问到outer的局部变量msg。

>>> func = outer()

>>> func()

hello world

闭包有2个特点

inner能访问outer及其祖先函数的命名空间内的变量(局部变量,函数参数)。

调用outer已经返回了,但是它的命名空间被返回的inner对象引用,所以还不会被回收。

这部分想深入可以去了解Python的LEGB规则。

4. 用函数实现装饰器

装饰器要求入参是函数对象,返回值是函数对象,嵌套函数完全能胜任。

def make_bold(func):

print("Initialize")

def wrapper():

print("Call")

return "{}".format(func())

return wrapper

用法跟类实现的装饰器一样。可以去掉@语法糖分析下构造和调用的时机。

>>> @make_bold

... def get_content():

... return "hello world"

...

Initialize

>>> get_content()

Call

"hello world"

因为返回的wrapper还在引用着,所以存在于make_bold命名空间的func不会消失。make_bold可以装饰多个函数,wrapper不会调用混淆,因为每次调用make_bold,都会有创建新的命名空间和新的wrapper。

到此函数实现装饰器也理清楚了,完结撒花,可以关掉网页了~~~(后面是使用装饰的常见问题)

常见问题

1. 怎么实现带参数的装饰器?

带参数的装饰器,有时会异常的好用。我们看个例子。

>>> @make_header(2)

... def get_content():

... return "hello world"

...

>>> get_content()

"

hello world

"

#怎么做到的呢?其实这跟装饰器语法没什么关系。去掉@语法糖会变得很容易理解。

@make_header(2)

def get_content():

return "hello world"

# 等价于

def get_content():

return "hello world"

unnamed_decorator = make_header(2)

get_content = unnamed_decorator(get_content)

上面代码中的unnamed_decorator才是真正的装饰器,make_header是个普通的函数,它的返回值是装饰器。

来看一下实现的代码。

def make_header(level):

print("Create decorator")

# 这部分跟通常的装饰器一样,只是wrapper通过闭包访问了变量level

def decorator(func):

print("Initialize")

def wrapper():

print("Call")

return "{1}".format(level, func())

return wrapper

# make_header返回装饰器

return decorator

看了实现代码,装饰器的构造和调用的时序已经很清楚了。

>>> @make_header(2)

... def get_content():

... return "hello world"

...

Create decorator

Initialize

>>> get_content()

Call

"

hello world

"

2. 如何装饰有参数的函数?

为了有条理地理解装饰器,之前例子里的被装饰函数有意设计成无参的。我们来看个例子。

@make_bold

def get_login_tip(name):

return "Welcome back, {}".format(name)

最直接的想法是把get_login_tip的参数透传下去。

class make_bold(object):

def __init__(self, func):

self.func = func

def __call__(self, name):

return "{}".format(self.func(name))

如果被装饰的函数参数是明确固定的,这么写是没有问题的。但是make_bold明显不是这种场景。它既需要装饰没有参数的get_content,又需要装饰有参数的get_login_tip。这时候就需要可变参数了。

class make_bold(object):

def __init__(self, func):

self.func = func

def __call__(self, *args, **kwargs):

return "{}".format(self.func(*args, **kwargs))

当装饰器不关心被装饰函数的参数,或是被装饰函数的参数多种多样的时候,可变参数非常合适。可变参数不属于装饰器的语法内容,这里就不深入探讨了。

3. 一个函数能否被多个装饰器装饰?

下面这么写合法吗?

@make_italic

@make_bold

def get_content():

return "hello world"

合法。上面的的代码和下面等价,留意一下装饰的顺序。

def get_content():

return "hello world"

get_content = make_bold(get_content) # 先装饰离函数定义近的

get_content = make_italic(get_content)

4. functools.wraps有什么用?

Python的装饰器倍感贴心的地方是对调用方透明。调用方完全不知道也不需要知道调用的函数被装饰了。这样我们就能在调用方的代码完全不改动的前提下,给函数patch功能。

为了对调用方透明,装饰器返回的对象要伪装成被装饰的函数。伪装得越像,对调用方来说差异越小。有时光伪装函数名和参数是不够的,因为Python的函数对象有一些元信息调用方可能读取了。为了连这些元信息也伪装上,functools.wraps出场了。它能用于把被调用函数的__module__,__name__,__qualname__,__doc__,__annotations__赋值给装饰器返回的函数对象。

import functools

def make_bold(func):

@functools.wraps(func)

def wrapper(*args, **kwargs):

return "{}".format(func(*args, **kwargs))

return wrapper

对比一下效果。

>>> @make_bold

... def get_content():

... """Return page content"""

... return "hello world"

>>>

# 不用functools.wraps的结果

>>> get_content.__name__

"wrapper"

>>> get_content.__doc__

>>>

# 用functools.wraps的结果

>>> get_content.__name__

"get_content"

>>> get_content.__doc__

"Return page content"

实现装饰器时往往不知道调用方会怎么用,所以养成好习惯加上functools.wraps吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/552686.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vasp和ms_科学网—VASP如何计算铁磁和考虑强关联作用 - 叶小球的博文

关注:1) U参数的加入2) 自旋极化的考虑3) 铁磁、反铁磁的考虑来自文章的计算方法介绍The similar MSUs of Pu di- and trihydride provide aframework within which intermediate compositions can be exploredcomputationally.The calculationspresented here are…

java点_java常见基础点

1. 重载与重写重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可不同。重写:子类对父类允许访问的方法重新编写,方法名参数列表必须相同,返回值…

java 反射 父类的属性_用反射的方式获取父类中的所有属性和方法

package com.syh.jdbc.reflection_super;/*** 父类* author syh**/public class Parent {public String publicField "1";String defaultField "2";protected String protectedField "3";private String privateField "4" ;public…

java cassandra连接池_java操作cassandra(连接池)

package com.chu.cassandratest;import java.util.concurrent.Semaphore;import java.util.concurrent.TimeUnit;import org.apache.thrift.transport.TTransportException;/*** cassandra连接池* author chuer* date 2014年12月31日 上午10:05:26*/public class CassandraConn…

java中变量命名规范_关于java中变量命名规范的详细介绍

Java是一种区分字母的大小写的语言,所以我们在定义变量名的时候应该注意区分大小写的使用和一些规范,接下来我们简单的来讲讲Java语言中包、类、变量等的命名规范。(一)Package(包)的命名Package的名字应该都是由一个小写单词组成,例如com、x…

java找重复字符串_在java中怎样查找重复字符串

在一段java编程代码中,字符串是不可缺少的一个要素,属于java中的基础知识,字符串不仅在java面试题中会出现,在编写代码时更要掌握怎样使用字符串。在前面我们也学习过关于字符串截取的知识,你应该有所掌握吧、格式化字…

android java函数_java – 在android中创建全局函数

像这样创建类并在此处添加您的函数:package com.mytest;import android.content.Context;import android.net.ConnectivityManager;import android.net.NetworkInfo;public class MyGlobals{Context mContext;// constructorpublic MyGlobals(Context context){this…

java final定义_Java中final关键字的用法

final在Java中并不常用,然而它却为我们提供了诸如在C语言中定义常量的功能,不仅如此,final还可以让你控制你的成员、方法或者是一个类是否可被覆写或继承等功能,这些特点使final在Java中拥有了一个不可或缺的地位,也是…

java replacefirst第n_Java中replace()、replaceFirst()和replaceAll()区别

str.replace(str中被替换的,替换后的字符)replace和replaceAll是JAVA中常用的替换字符的方法,它们的区别是:1)replace的参数是char和CharSequence,即可以支持字符的替换,也支持字符串的替换(CharSequence即字符串序列的意思,说白了也就是字符串);2)replaceAll的参数…

java中的多态与继承_【Java学习笔记之十六】浅谈Java中的继承与多态

1、 什么是继承,继承的特点?子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为。特点:在继承关系中,父类更通用、子类更具体。父类具有更一般…

python excel 单元格格式_python设置单元格数值格式

python xlwt如何设置单元格格式python xlwt模块怎么设置excel单元格的属性如图,默认是general。我想写入的时候就是Text类型.请问应该怎么做。from xlwt import Workbook,Stylewb Workbook()ws wb.add_sheet(Type examples)ws.row(0).write(0,1)ws.row(0).set_cell_text(1,1)…

python调用.a静态库_Python 调用 C

了解了相关资料不折腾的方法有(以往文章有):pypy,numba,numpy但都不是 纯正的 C折腾的:cffi,Cython,Boost.Python,Cpython 自带模块,SWIG 等挺折腾的You can write an extension you…

java给一个数组随机数_Java案例-数组随机数

.数组案例分析定义一个int型的一维数组,包含10个元素,分别赋一些随机整数,然后求出所有元素的最大值Max,最小值Min,平均值Avg,和Sum值,并输出出来。具体实现代码:package teacher01;…

ibatis 数据库获取不到 java_如何拦截ibatis中所有的执行sql,并记录进数据库

通过spring aop去拦截SqlMapClientTemplate下的方法,即可进行对所有执行sql的拦截,并进行操作。package com.detain.system.aop;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annot…

java 自旋方法_JAVA循环使用CAS实现自旋操作

大家碰到了实现一个线程安全的计数器的需求改怎么做呢?根据经验你应该知道我们要在多线程中实现共享变量的原子性和可见性问题,于是锁成为一个不可避免的话题,下文讨论的是与之对应的无锁CAS。为什么要无锁我们一想到在多线程下保证安全的方式…

java变量小明扑克牌_算法练习篇之:扑克牌顺子

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼代码实现import java.util.Arrays;public class isContinuous {//扑克牌顺子(必须连续的五个数字)public boolean continuous(int[] num){int zero0,dis0;//zero为大小王的个数,dis为不连续序列中两个数字相隔距离if (n…

java stream foreach_Java 8 Lambda Stream forEach具有多个语句

我仍在学习Lambda&#xff0c;请原谅我做错了什么final Long tempId 12345L;List updatedEntries new LinkedList<>();for (Entry entry : entryList) {entry.setTempId(tempId);updatedEntries.add(entityManager.update(entry, entry.getId()));}//entryList.stream(…

java tls 实例_grpc加密TLS初体验(go、java版本)

grpc加密TLS初体验(go、java版本)grpc-go、java的安装编译helloworld可以参考如下文章openssl的安装、生成秘钥证书可以参考如下文章示例代码go版本服务端代码package mainimport ("fmt""log""net"pb "github.com/grpc/grpc-common/go/hell…

java的svn插件maver_项目版本管理工具---MAVENSVN

在进行实际项目开发时往往不是由一个人去完成一整个项目&#xff0c;而是分模块进行完成最后将所有项目进行聚合&#xff0c;那么就可以用到maven和svn。MAVEN是用来管理项目的&#xff0c;我认为它最大的优势就在于依赖和聚合吧&#xff0c;而svn的优势就在于版本控制&#xf…

java第一次上机_java第一次上机实验--验证码

1 package javashiyan;23 import java.awt.Color;4 import java.awt.event.ActionEvent;5 import java.awt.event.ActionListener;67 import javax.swing.*;89 public class Yanzhen extends JFrame10 {11 //定义成员变量12 private Mypanel mp;13 private JButton b;14 privat…