Python中使用@的理解
Python函数中使用@
稍提一下的基础
fun
和fun()
的区别
以一段代码为例:
def fun():
print('fun')
return None
a = fun() #fun函数并将返回值给a
print('a的值为',a)
b = fun #将fun函数地址赋给b
b() #调用b,b和fun指向的地址相同
print('b的值为',b)
'''输出
fun
a的值为 None
fun
b的值为 <function fun at 0x00000248E1EBE0D0>
'''
根据输出可以看出,a=fun()
是将函数fun
的返回值(None
)赋给a
,而b=fun
是将函数的地址赋给b
,如果调用函数,需要b()
类似的,其他内置函数也可以通过这种方法,相当于起了一个同名的函数
>>>a = abs
>>>a(-1)
1
除此之外,原来的函数名也被覆盖为其他函数,例如
def fun():
print('fun')
abs = fun
abs() #输出fun
综上,可以理解为fun,abs在不带括号时为变量,该变量包含了函数在内容的地址
返回函数
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017434209254976
以廖老师的教程为例
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
>>> f = lazy_sum(1, 3, 5, 7, 9)
>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>>>f()
25
在单步调试中可以发现,当读到def sum():
时,解释器会直接跳到return sum
将sum
函数的地址返回给f
,因此f()
即为执行sum()
(不是非常准确,但先如此理解)
如果对返回函数还是有些不理解的话,可以假设lazy_sum()
的返回值改为1
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return 1
f = lazy_sum(1,3,5,7,9)
print(f)#Q输出1
print(f())#报错'int' object is not callable
此时无论lazy_sum()
的参数如何修改,都会把1
赋给f
,而1()
是不可调用的,因此会报错
⭐️返回函数中的闭包问题也要了解一下,内嵌函数可以访问外层函数的变量
参数的嵌套调用
仍然上述例子,此时将lazy_sum()
改为空函数,内嵌的sum()
需要参数:
def lazy_sum():
def sum(*args):
ax = 0
for n in args:
ax = ax + n
return ax
return sum
f = lazy_sum()(1,3,5,7,9)
print(f)#输出25
按照运算的优先级,可以理解为:
- 执行
lazy_sum()
,返回sum
; - 执行
sum(1,3,5,7,9)
,返回25; - 将
25
赋给f
如果有了以上基础,再来看@的用法就会觉得很容易了
@的使用
如果需要具体理解装饰器,可以参考廖老师的博客,本文仅介绍@的执行流程
本文参考了 Python @函数装饰器及用法(超级详细),Python中的注解“@”
不带参数的单一使用(一个@修饰)
def spamrun(fn):
def sayspam():
print("spam,spam,spam")
fn()
return sayspam
@spamrun
def useful():
print('useful')
useful()
'''
输出:
spam,spam,spam
useful
'''
修饰效果相当于useful = spamrun(useful)
,具体步骤如下:
- 在初始化时,解释器读到
@spamrun
,此时将下方的useful
作为参数传入到spamrun
中 spamrun(useful)
中,由于是返回函数,直接将sayspam()
的内存地址赋给useful
- 执行
useful()
,此时useful
指向了sayspam
,因此打印spam,spam,spam
。然后执行fn()
,此时的fn
才指向原来的useful()
的地址,开始执行print('useful')
执行流程可以在下图了解一下,可以理解为经过@后,useful
已经不直接指向函数useful()
的地址了,而是sayspam
。再调用useful()
时,执行sayspam()
,由于fn
保存原先useful()
函数的地址,因此可以执行useful()
的功能,即可以打印出'useful'
。如果‘使坏’把fn()
去掉的话,相当于useful()
再也不会执行了
一般情况下,使用@
时不会改变函数原先的执行逻辑,而只是增加功能,因此成为装饰器,如廖老师教程中可以使原函数打印日志
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
@log
def now():
print('2015-3-25')
now()
'''
call now():
2015-3-25
'''
不带参数的多次使用(两个@)
def spamrun(fn):
def sayspam():
print("spam,spam,spam")
fn()
return sayspam
def spamrun1(fn):
def sayspam1():
print("spam1,spam1,spam1")
fn()
return sayspam1
@spamrun
@spamrun1
def useful():
print('useful')
useful()
'''
spam,spam,spam
spam1,spam1,spam1
useful
'''
修饰效果相当于useful = spamrun(spamrun1(useful))
叠加使用时,装饰器的调用顺序和声明顺序是相反的,可以理解成是一个递归的过程。
- 遇到
@spamrun
,开始向下寻找def 函数名
- 结果第二行也是一个@。
@spamrun1
继续向下找 - 遇到了
def useful
,执行useful = spamrun1(useful)
- 回归。
@spamrun1
返回useful
给@spamrun
,执行useful=spamrun(useful)
带参数的单次使用
以廖老师教程中的举例,简化一些,先不考虑*args
,**kw,
因为涉及到返回函数的闭包问题
def log(text):
def decorator(func):
def wrapper():
print('%s %s():' % (text, func.__name__))
return func()
return wrapper
return decorator
@log('execute')
def now():
print('2015-3-25')
now()
修饰效果相当于now=log('execute')(now)
1. 解释器读到@log('execute')
,先执行了log('execute')
,返回函数decorator
2. 将now
作为decorator(func)
的形参,返回warpper
3. 将`warpper`的内存地址赋给变量`now`
此时调用now()
,先执行完print(...)
,然后return func()
。注意此处是带括号的,因此执行了真正的now
函数,最终return
的为None
带参数的多次调用可以将之前的情况组合即可
总结
- @行不带参数
@XXX
def funXXX():
会被解释成funXXX = XXX(funXXX)
- 如果@那行中带参数,则被解释成
funXXX = XXX(@行的参数)(funXXX)
- 要深刻理解返回函数以及
fun
和fun()的
区别 - 函数的内存地址,函数变量,函数的名称的区别。默认情况下,函数变量指向函数的内存地址,但也可以被改变
初学Python,学识短浅,希望多多交流
Python中使用@的理解的更多相关文章
- 转载-对于Python中@property的理解和使用
原文链接:https://blog.csdn.net/u013205877/article/details/77804137 重看狗书,看到对User表定义的时候有下面两行 @property def ...
- Python中yield深入理解
众所周知,python中的yield有这样的用法: def test(alist): for i in alist: yield i 这样,这个test函数就变成了一个生成器,当每次调用的时候,就会自 ...
- python中Metaclass的理解
今天在学习<python3爬虫开发实战>中看到这样一段代码3 class ProxyMetaclass(type): def __new__(cls, name, bases, attrs ...
- python中切片的理解
Python中什么可以切片 l Python中符合序列的有序序列都支持切片(slice) l 如:列表,字符,元祖 Python中切片的格式 l 格式:[start : end : step] ...
- python中*args, **kwargs理解
先来看个例子: def foo(*args, **kwargs): print 'args = ', args print 'kwargs = ', kwargs print '----------- ...
- python 05 关于对python中引用的理解
数据的在内存中的地址就是数据的引用. 如果两个变量为同一个引用,那么这两个变量对应的数据一定相同: 如果两个变量对应的数据相同,引用不一定相同. 通过id(数据)可以查看数据对应的地址,修改变量的值, ...
- 深入理解python(一)python语法总结:基础知识和对python中对象的理解
用python也用了两年了,趁这次疫情想好好整理下. 大概想法是先对python一些知识点进行总结,之后就是根据python内核源码来对python的实现方式进行学习,不会阅读整个源码,,,但是应该会 ...
- Python中生成器的理解
1.生成器的定义 在Python中一边循环一边计算的机制,称为生成器 2.为什么要有生成器 列表所有的数据都存在内存中,如果有海量的数据将非常耗内存 如:仅仅需要访问前面几个元素,那后面绝大多数元素占 ...
- python中HTMLParser简单理解
找一个网页,例如https://www.python.org/events/python-events/,用浏览器查看源码并复制,然后尝试解析一下HTML,输出Python官网发布的会议时间.名称和地 ...
随机推荐
- 大文件断点续传插件webupload插件
java两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路: 1.服:利用ServerSocket搭建服务器,开启相应端口,进行长连接 ...
- 常见WinDbg问题及解决方案
当你调试一个程序时,你最不想处理的是调试器不能正常工作.当你试图集中精力跟踪一个bug时,总是会因为次要的问题而被忽略,尤其是当调试器的问题导致你失去一个重新编程或者浪费了大量的时间等待调试器完成它, ...
- typedi 强大的javascript以及typescript 依赖注入框架
typedi 是typestack团队提供的依赖注入解决方案,对于typescript 我们可以使用注解的开发方式,官方的文档也比较详细 javascript 使用 基于函数的服务注入 var Ser ...
- 常用方法 Excel转换为DataSet
注意一下Excel格式,我平时导入的是xlsx /// <summary> /// Excel 转换为 DataSet /// </summary> /// <param ...
- [RN] React Native Fetch请求设置超时
一.实现思路 根据Promise.race的特性,我们在Promise.race里面添加两个任务,一个是正常的网络请求任务A,另外一个便是网络延时任务B,网络延时可以利用setTimeout方法实现. ...
- Spring中@Bean与@Configuration
一.Spring中Bean及@Bean的理解[1] Bean在Spring和SpringMVC中无所不在,将这个概念内化很重要,下面分享一下我的想法: 一.Bean是啥 1.Java面向对象,对象有方 ...
- Redis简介及其安装
1.Redis NoSQL (Not noly SQL)不仅仅是SQL 属于非关系型数据库:Redis就属于非关系型数据库 传统的Mysql ,oracle ,sql server 等 都是关系型数据 ...
- [Gamma阶段]第四次Scrum Meeting
Scrum Meeting博客目录 [Gamma阶段]第四次Scrum Meeting 基本信息 名称 时间 地点 时长 第四次Scrum Meeting 19/05/30 大运村寝室6楼 35min ...
- 1045-Access denied for user 'root'@'localhost'解决方法
1.出现这个问题的原因之一是权限的问题,也就是说你的电脑可能没有权限访问mysql数据库. 讲道理这种情况其实基本上不该遇到,因为我们在安装mysql之后,root其实是有最高权限的,而且很少会有人去 ...
- 基于cesium的GIS洪水淹没三维模拟系统
简介: “FloodFreeth3D”是一款对Mike11软件计算的洪水演进结果使用cesium进行淹没演进三维模拟的软件产品. 技术参数: 1. B/S架构,支持多Web浏览器(ie.chrom ...