首页 > 编程语言 >给python入门者的帮助,关于函数和装饰器的理解。

给python入门者的帮助,关于函数和装饰器的理解。

时间:2024-03-11 10:26:45浏览次数:32  
标签:return 函数 python 入门者 func print 装饰 def

有时候学习不能过于较真,至少在合适的时机之前,还是闷头吞知识,等吃饱了,就有精力(足够的能量储备,足够的经验)来理解更深的理解,但是很多时候,包括我自己,都喜欢在吃饱之前就研究自己在吃什么,为什么这个东西能吃这种问题。

最近发现几年前写的一篇关于python函数return的一些理解,又被查看了,而且评论中表示得到了帮助。但是惭愧的是,那篇理解是我刚学python没多久的个人理解,由于储备的认识不足,经验不足,思考不足,许多理解是不恰当的,或者是错的,所以今天重新写一篇关于python函数的理解。
这篇理解主要是帮助刚入门的朋友尽快理解和接收函数,实际上也会有许多不那么准确的比喻和描述,因为一旦涉及到完全准确的底层,就会变得晦涩难懂,就会在解决这个问题之前,遇到更多问题,这显然对学习是不利的。

简单理解函数和return

对初学者来说,首先遇到的一个迷惑的问题就是什么是变量,什么是函数,为什么函数名加一个()就是执行,"函数名()"是什么
首先,变量是一个名字,变量的本质是内存地址,但是为了方便记忆,方便阅读,要用一个阅读者可以快速识别的代号来指代。
函数也算是一个变量,只是通常说的变量指向的内存地址,是保存数值的内存地址,函数名指向的内存地址,是可执行对象的内存地址。

函数,就是一个处理过程,是对某些已知变量或者数据的一系列处理、计算。一般时候,这些过程都是存在内存中的,只有激活这个过程,CPU才会把这一系列处理过程从内存中拿到CPU缓存里一步步执行。
这个激活的信号,就是(),其实这个括号,是一个传参数的动作,可以把定义好的函数理解为一条生产线上的一个机床,程序运行,就是产线通电,机床没有上料之前,机床处于待命状态,物料进来了,机床开始动作。
参数就是物料,有些时候可能()里并没有参数,这是因为大部分数据都是存在系统里的,只有特殊时候,需要人工放参数进去。

def func():
    x = 1
    y = 2
    z = x + y
    return z

func()

这就是一个简答的函数和调用,暂时也叫做函数激活,return就是把函数处理的结果输出出来。
问题来了,我们通常会有下面的疑惑:

def age():
    return 15


A = 12

B = 13

C = age()

A 和 B 赋值可以看懂,通俗理解就是 左边的等于右边的,那C是怎么回事
在程序代码里,func()除了代表激活函数外,这个字符本身可以理解为是一个系统的临时变量(实际上,这涉及到内存模型和堆栈),这个临时变量用于储存函数输出的处理结果。而且这个临时变量只保留一行代码的时间。
也就是在函数激活,处理完毕之后,如果没有一个变量来接收临时变量,这个临时变量就会被系统回收(这涉及到python的垃圾处理机制,这个值所在的内存没有被任何引用,就会被清理)
简单理解为产线机床调出来的东西,没有任何箱子或盒子盛放,直接扔到地上了,搞5S的就把它当成垃圾扫走了。

那什么也不return的函数时咋回事呢

def void():
    pass

empty = void()
print(empty)

void函数什么也没有return,但是执行上面的代码会发现,empty是None

如果执行下面的代码

def void():
    pass

N = None

print(id(N), id(void()))

会发现打印出来的地址是一样的,首先在python里,所有不可变的基础数据都是指向的同一个地址,就是说,不管在哪里定义的变量,A=123, B=123,A和B指向的是同一个地址
None也是如此,None是空,是无(并不是0)

其实python在函数执行上有个默认的行为,就是会在函数执行完之后return None,如果函数中有return语句,那么就不会走默认的return None,如果没有return,就会走默认的return None

理解装饰器

首先什么是装饰器,装饰是什么意思,在主体的外面,加一些功能性的东西。
也就是装饰器,是不触及被装饰目标(对象)的基础上,添加一些其他的额外功能。

def dec(func):
    def inside(*args, **kwargs):
        result = func(*args, **kwargs)
        return result

    return inside

@dec
def func_nothing(x):
    y = x

@dec
def func_return(x):
    return x

上面是装饰器一般形式,那么装饰器做了什么

其实说白了,装饰器就是修改,替换

def dec(func):
    print("01== FUNC IS ==%s"%func)
    def inside(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    print("02== INSIDE ==%s"%inside)
    return inside

@dec
def func_nothing(x):
    y = x

@dec
def func_return(x):
    return x
print("Start========")
print(func_nothing)
print(func_nothing("X"))
print(func_return)
print(func_return("Y"))

运行结果:

01== FUNC IS ==<function func_nothing at 0x0000020127B96550>
02== INSIDE ==<function dec.<locals>.inside at 0x00000201485F7820>
01== FUNC IS ==<function func_return at 0x00000201485F7790>
02== INSIDE ==<function dec.<locals>.inside at 0x00000201487115E0>
Start========
<function dec.<locals>.inside at 0x00000201485F7820>
None
<function dec.<locals>.inside at 0x00000201487115E0>
Y

我们来分析下
在定义完函数之后,打印第一行Start========之前,解释器就已经执行了一堆东西,也就是说@dec实际上就是一个执行操作(这个后面再说)
关键在这里:关注第一个函数 func_nothing,在装饰器里面的打印结果中看到,func_nothing的ID地址和我们后面主动打印 print(func_nothing) 输出的不一样
仔细再看会发现,print(func_nothing)输出的居然和装饰器内部打印的inside地址一样,内存地址不会骗人,也就是说,装饰后的func_nothing实际上就是inside函数

看下面的代码

tasks = []


def talk():
    print("Get out of my way!")


def got_method(method):
    tasks.append(method)
    return talk


@got_method
def speak():
    print("Hello..")


print(tasks)
speak()

执行结果如下:

[<function speak at 0x000001C22C267820>]
Get out of my way!

执行speak,却实际执行了talk,我们没有往tasks中放东西,但是tasks中有了一个元素。
唯一有嫌疑的就是@got_method,我貌似使用了一个装饰器,但是got_method并不是装饰器的格式,这就要涉及python中的@语法了。

@是做什么的

从上面的代码来看@got_method做了这些事:

@got_method
def speak():
    print("Hello..")

实际等于

def got_method(method):
    tasks.append(method)
    return talk

def speak():
    print("Hello..")
    
speak = got_method(speak)

@实际上是执行了got_method,把被@施加的函数作为参数传给got_method。
也就是说

@dec
def func():
    pass

等于

def func():
    pass

func = dec(func)

就是在@dec的地方,执行了装饰器,替换了被装饰的函数。

再回到最开始的装饰器标准格式上

def dec(func):
    def inside(*args, **kwargs):
        result = func(*args, **kwargs)
        return result
    return inside

@dec
def func_return(x):
    return x


print(func_return)
print(func_return("Y"))

现在装饰器应该是解释清楚了,装饰器里让人不好理解的就是@符号,@是一个执行操作,相当于执行了装饰器函数并把被装饰的函数作为参数
装饰器的意义在于利用闭包和变量的作用域来实现函数功能的扩展。装饰器的使用主要就是为了保证函数的修改不影响其他地方的引用,目的和接口一致。

“项目中有五个登记的函数,在项目各处被大量引用,现在公司处于安全考虑,要对这个登记函数加认证,有两个方案,一个是修改这五个函数,在五个函数中分别加入认证,
第二个是定义一个装饰器,然后给五个函数分别装饰上。第一个方案最简单直接,但是后期维护就很麻烦,认证再次变动就要找出所有当时使用认证的地方,全都改一次,漏了任何一个都会出现
生产事故,第二个方案在维护上和可读性上就优秀很多,我只用修改装饰器,那么所有添加认证的地方就都完成了修改。”

这就是装饰器的意义。

装饰器运作逻辑和函数return的逻辑讲完了,接下来就是要解释最后一个问题,装饰器内部为什么要把函数执行结果return出去。

分析上面的代码:

使用dec装饰func_return之后,func_return实际上已经被替换成了inside函数,执行func_return实际就是执行inside,
但是我们要保证被装饰的原func_return正常执行,功能不受影响,所以原func_return的输出必须要做保留,至少在闭包内要保留,
那么为什么inside中 要把func的结果return出来,因为原先func_return就是输出结果的,装饰器内部是否return,要看该函数所在的代码中是否使用了输出结果。
如果函数被调用的地方使用了输出结果,那么就要在装饰器内部把func输出结果接收,然后return出去。

在这里千万不要把return的作用理解错误了,return是传递的操作,函数只能通过return把自己输出的数据传递给调用自己的一级,想象五个人传麻袋,必须是每个人都把前一个人递过来的
东西接过来,然后再递给下一个,你不能接了不递出去,更不能不接,这是return。

python里能够做到不用层层传递的只有raise,可以理解为扔出去,路径上任何人都可以接住,或者不理睬,raise就是扔,或者说是冒泡。而return只能层层传递。

最后,通过上面的实验,可以得出最后一个结论,装饰器是不是一定要return什么东西出来?
不是,要不要return,取决于要不要接收结果,如果需要从内部传递数据出去,那就必须层层递传,层层return,如果不需要,那就不用return。

本文首发于博客园,转载须注明出处

https://www.cnblogs.com/haiton/p/18065460

标签:return,函数,python,入门者,func,print,装饰,def
From: https://www.cnblogs.com/haiton/p/18065460

相关文章

  • 【vscode】vscode配置python
    【vscode】vscode配置python前言‍每次配环境的经历,其实都值得写一篇博客记录一下,以便于自己以后查阅。‍笔者环境:win10‍过程‍step1:python解释器下载‍由于近期edge不知为何,不进行翻译了,所以就只能啃一啃英文了。(别问我为啥不用截屏翻译,因为有那个闲心,不如我直接......
  • LeetCode 128.最长连续序列 Python题解
    leetcode128题最长连续序列分享解题思路,使用哈希表算法......
  • windows上python3开发环境的搭建
    首先进入python官网(当然我们这里所说的python指的是python3,因为如今python2已经停止更新更多的python开发者也会选择python3,这篇博客也是python3环境的搭建)Python官网:https://www.python.org/选择windows版本,我们作为python入门者选择最新版截至目前3.12.2为最新版,点击downlo......
  • python加载2
    #testInstance.py#导入需要的模块importimportlib#导入模块以动态加载库中的类和函数importsys#导入系统模块,用于操作Python解释器的参数和变量importosimportpkgutil#定义TestInstance类classTestInstance:#初始化方法,当创建TestInstance对象时调......
  • vs2019单独重新安装python37_64失败解决办法(bilibili上我最早写的是https://www.bilib
    上个周末的时候,我发现用vs2019编写python的时候。代码高亮出现了奇怪的问题,进入解决方案的时候,print还是蓝色的,但是过了几秒钟后就变为黑色了,因此在最开始的时候我试图通过换一个皮肤和在管理扩展里面找扩展来解决,但是还是有相关问题。于是到vs2019对应的python文件夹找问题,目录是......
  • python加载
    #testInstance.py#导入需要的模块importimportlib#导入模块以动态加载库中的类和函数importsys#导入系统模块,用于操作Python解释器的参数和变量importosimportpkgutil#定义TestInstance类classTestInstance:#初始化方法,当创建TestInstance对象时调......
  • python获取某个包下面的所有子模块
    deflist_submodules(self,package_path):"""递归地列出给定包路径下的所有子模块"""all_submodules=[]#遍历包路径下的所有文件/目录forimporter,modname,ispkginpkgutil.iter_modules([package_path]):full_......
  • Python简单实现查重
    使用Python实现查重这个作业属于哪个课程软件工程这个作业要求在哪里个人项目这个作业的目标初步认识软件开发流程,独立培养开发能力,熟悉PSP记录开发过程你可以在GitHub上找到本项目并下载额外三种算法代码Slave前言作为开发人员,不幸的是此前未曾接触熟悉过......
  • python酒店相似度推荐系统
    importnumpyasnpimportpandasaspdfromnltk.corpusimportstopwordsfromsklearn.metrics.pairwiseimportlinear_kernelfromsklearn.feature_extraction.textimportCountVectorizerfromsklearn.feature_extraction.textimportTfidfVectorizerfromsklear......
  • python实现批量运行命令行
    python实现批量运行命令行背景:对于不同参数设置来调用同一个接口,如果手动一条条修改再运行非常慢且容易出错。尤其是这次参数非常多且长。比如之前都是输入nohuppython-uexe.py>>../log/exp3.log2>&1&来运行一次,在exe中会设置参数并调用接口运行preditction_uni(input_f......