Python函数中使用@

稍提一下的基础

funfun()的区别

以一段代码为例:

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 sumsum函数的地址返回给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

按照运算的优先级,可以理解为:

  1. 执行 lazy_sum(),返回sum;
  2. 执行sum(1,3,5,7,9),返回25;
  3. 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),具体步骤如下:

  1. 在初始化时,解释器读到@spamrun,此时将下方的useful作为参数传入到spamrun
  2. spamrun(useful)中,由于是返回函数,直接将sayspam()的内存地址赋给useful
  3. 执行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))

叠加使用时,装饰器的调用顺序和声明顺序是相反的,可以理解成是一个递归的过程。

  1. 遇到@spamrun,开始向下寻找def 函数名
  2. 结果第二行也是一个@。@spamrun1继续向下找
  3. 遇到了def useful,执行useful = spamrun1(useful)
  4. 回归。@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

带参数的多次调用可以将之前的情况组合即可

总结

  1. @行不带参数
@XXX
def funXXX():

会被解释成funXXX = XXX(funXXX)

  1. 如果@那行中带参数,则被解释成funXXX = XXX(@行的参数)(funXXX)
  2. 要深刻理解返回函数以及funfun()的区别
  3. 函数的内存地址,函数变量,函数的名称的区别。默认情况下,函数变量指向函数的内存地址,但也可以被改变

初学Python,学识短浅,希望多多交流

Python中使用@的理解的更多相关文章

  1. 转载-对于Python中@property的理解和使用

    原文链接:https://blog.csdn.net/u013205877/article/details/77804137 重看狗书,看到对User表定义的时候有下面两行 @property def ...

  2. Python中yield深入理解

    众所周知,python中的yield有这样的用法: def test(alist): for i in alist: yield i 这样,这个test函数就变成了一个生成器,当每次调用的时候,就会自 ...

  3. python中Metaclass的理解

    今天在学习<python3爬虫开发实战>中看到这样一段代码3 class ProxyMetaclass(type): def __new__(cls, name, bases, attrs ...

  4. python中切片的理解

    Python中什么可以切片 l  Python中符合序列的有序序列都支持切片(slice) l  如:列表,字符,元祖 Python中切片的格式 l  格式:[start : end : step] ...

  5. python中*args, **kwargs理解

    先来看个例子: def foo(*args, **kwargs): print 'args = ', args print 'kwargs = ', kwargs print '----------- ...

  6. python 05 关于对python中引用的理解

    数据的在内存中的地址就是数据的引用. 如果两个变量为同一个引用,那么这两个变量对应的数据一定相同: 如果两个变量对应的数据相同,引用不一定相同. 通过id(数据)可以查看数据对应的地址,修改变量的值, ...

  7. 深入理解python(一)python语法总结:基础知识和对python中对象的理解

    用python也用了两年了,趁这次疫情想好好整理下. 大概想法是先对python一些知识点进行总结,之后就是根据python内核源码来对python的实现方式进行学习,不会阅读整个源码,,,但是应该会 ...

  8. Python中生成器的理解

    1.生成器的定义 在Python中一边循环一边计算的机制,称为生成器 2.为什么要有生成器 列表所有的数据都存在内存中,如果有海量的数据将非常耗内存 如:仅仅需要访问前面几个元素,那后面绝大多数元素占 ...

  9. python中HTMLParser简单理解

    找一个网页,例如https://www.python.org/events/python-events/,用浏览器查看源码并复制,然后尝试解析一下HTML,输出Python官网发布的会议时间.名称和地 ...

随机推荐

  1. cpu的发现

    system.cpu.discovery 检测到的CPU/CPU内核列表.用于低级发现 返回的cpu从0开始编号,其他关于cpu的监控项就可以使用cpu的id进行单个cpu的资源监控

  2. 【POJ3414】Pots

    本题传送门 本题知识点:宽度优先搜素 + 字符串 题意很简单,如何把用两个杯子,装够到第三个杯子的水. 操作有六种,这样就可以当作是bfs的搜索方向了 // FILL(1) 把第一个杯子装满 // F ...

  3. 【技术博客】 利用Postman和Jmeter进行接口性能测试

    利用Postman和Jmeter进行接口性能测试 作者:ZBW 版本:v1.1 在Phylab的开发过程中,对于生成报告接口的性能考量十分重要.原有的Latex接口虽然生成的报告美观,但编译Latex ...

  4. 解决git 出现 Your account has been blocked问题

    使用git 出现 Your account has been blocked 无法从远程pull代码下来, 解决方案如下: $ git push origin masterGitLab: Your a ...

  5. log配置文件log4j.propeties(配置保存日志文件的相对路径)

    log配置文件log4j.propeties(配置保存日志文件的相对路径) log4j.propeties文件: #日志的4种级别ERROR(错误).WARN(警告潜在的错误).INFO(粗粒度信息) ...

  6. 【Eclipse】Eclipse如何导出java项目为jar包

    1.首先确定要导出的项目 从项目结构可以看出,笔者的项目是一个Dynamic Java Project.com/db下面有一个config的数据库配置文件.WEB-INF/lib文件夹下面有依赖的ja ...

  7. Oracle 11g R2手动配置EM(转)

    转自:http://blog.itpub.net/9034054/viewspace-1973418/ Oracle 11g R2手动配置EM Oracle 作者:luashin 时间:2016-01 ...

  8. xgboost 算法总结

    xgboost有一篇博客写的很清楚,但是现在网址已经失效了,之前转载过,可以搜索XGBoost 与 Boosted Tree. 现在参照这篇,自己对它进行一个总结. xgboost是GBDT的后继算法 ...

  9. Spark无法读取hive 3.x的表数据

    通过Ambari2.7.3安装HDP3.1.0成功之后,通过spark sql去查询hive表的数据发现竟然无法查询 HDP3.0 集成了hive 3.0和 spark 2.3,然而spark却读取不 ...

  10. java url 编码

    public static void main(String[] args) throws UnsupportedEncodingException { String encodeUrl = URLE ...