>  기사  >  백엔드 개발  >  Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.

Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.

WBOY
WBOY앞으로
2022-06-30 16:57:101630검색

이 글은 Python에 대한 관련 지식을 제공합니다. 주로 반환 함수, 클로저, 데코레이터, 부분 함수 등을 포함한 고급 프로그래밍과 관련된 문제를 정리합니다. 모두에게 도움이 되기를 바랍니다.

Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.

【관련 추천: Python3 동영상 튜토리얼

1. 함수 반환

함수를 매개변수로 받는 것 외에도 고차 함수는 함수를 결과 값으로 반환할 수도 있습니다. 함수를 연산할 때 즉시 합계를 계산할 필요가 없으면 다음 코드에서 필요에 따라 계산할 수 있습니다. 예를 들어 다음

# -*- coding: utf-8 -*-# python 全栈# author : a wei # 开发时间: 2022/6/23 22:41def sum_fun_a(*args):
    a = 0
    for n in args:
        a = a + n      
    return a
는 결과를 즉시 계산할 필요가 없는 sum_fun 메서드입니다. 그리고 합계를 반환하지 않고 합계 함수를 반환합니다. 예를 들어 아래에서 sum_fun_b()를 호출하면 반환되는 것은 합계 결과가 아니라 합계 함수 sum_a입니다.

# -*- coding: utf-8 -*-# python 全栈# author : a wei # 开发时间: 2022/6/23 22:41def sum_fun_b(*args):
    def sum_a():
        a = 0
        for n in args:
            a = a + n        return a    return sum_a
이때 직접 얻는 값은 15입니다. 그러면 f = sum_a라고 생각하면 여기서 질문이 생깁니다. 매개변수는 어디로 가나요?

그리고 생성된 두 메소드는 서로 영향을 주지 않고 주소와 값이 다르다는 것을 알 수 있습니다.

sum_a 함수는 sum_fun_b 함수에 정의되어 있으며 내부 함수 sum_a는 해당 메소드의 매개변수와 지역 변수를 참조할 수 있습니다. 외부 함수 sum_fun_b, sum_fun_b가 sum_a 함수를 반환하면 해당 매개변수와 변수가 반환된 함수에 저장되는데, 이를 클로저라고 합니다.

2. 폐쇄


폐쇄란?

먼저 코드를 살펴보겠습니다

f1 = sum_fun_b(1, 2, 3, 4, 5)#  此时f为一个对象实例化,并不会直接生成值print(f1())  # 15f2 = sum_fun_b(1, 2, 3, 4, 5)f3 = sum_fun_b(1, 2, 3, 4, 5)print(f2, f3)<function>.sum_a at 0x0000016E1E1EFD30> <function>.sum_a at 0x0000016E1E1EF700>print(id(f2), id(f3))1899067537152 1899067538880</function></function>

실행 결과:

# 定义一个函数def fun_a(num_a):# 在函数内部再定义⼀个函数# 并且这个内部函数⽤到了外部的变量,这个函数以及⽤到外部函数的变量及参数叫 闭包
    def fun_b(num_b):
        print('内嵌函数fun_b的参数是:%s,外部函数fun_a的参数是:%s' % (num_b, num_a))
        return num_a + num_b    
    # 这里返回的就是闭包的结果
    return fun_b# 给fun_a函数赋值,这个10就是传参给fun_aret = fun_a(10)# 注意这里的10其实是赋值给fun_bprint(ret(10))# 注意这里的90其实是赋值给fun_bprint(ret(90))
이때 내부 함수가 외부 함수의 범위 변수(전역 변수가 아님)를 참조할 때 내부 함수를 클로저라고 합니다. .

여기서 클로저에는 세 가지 조건이 필요합니다.

内嵌函数fun_b的参数是:10,外部函数fun_a的参数是:1020内嵌函数fun_b的参数是:90,外部函数fun_a的参数是:10100
"""
三个条件,缺一不可: 
1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套 
2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
3)外部函数必须返回内嵌函数——必须返回那个内部函数
"""

함수가 로컬 범위에서 변수 선언을 찾을 수 없으면 외부 함수에서 변수 선언을 찾습니다. 이는 함수 클로저에서 매우 일반적이지만 다음과 같은 경우에 사용됩니다. 로컬 범위. 변수를 추가한 후 이 변수에 대한 할당을 변경하려는 경우 오류가 보고됩니다.

"python# python交互环境编辑器 >>> def counter(start=0): 
	count = [start]
	def incr(): 
		count[0] += 1 
		return count[0] 
		return incr 
		
>>> c1 = counter(5)>>> print(c1()) 6>>> print(c1()) 7>>> c2=counter(50) >>> print(c2()) 51>>> print(c2()) >52>>>

오류 메시지:

def test(): 
	 count = 1 
	 def add(): 
		  print(count) 
		  count += 1 
	 return add 
a = test() a()
함수에 비로컬 개수 줄을 추가하면 이 문제가 해결될 수 있습니다.

Code

Traceback (most recent call last): ...... UnboundLocalError: local variable 'count' referenced before assignment


nonlocal로 선언된 변수는 지역 변수가 아니며 전역 변수가 아니라 외부 중첩 함수 내의 변수입니다.

다른 관점에서 보면 이 기능에 기능 상태를 기록하는 기능을 추가했습니다. 물론 이는 함수 상태를 높이기 위해 전역 변수를 선언함으로써 달성될 수도 있습니다. 이런 일이 발생하면 다음과 같은 문제가 발생합니다.

# -*- coding: UTF-8 -*- # def test(): 
	 # count不是局部变量,介于全局变量和局部变量之间的一种变量,nonlocal标识
	 count = 1 
	 def add(): 
		  nonlocal count 
		  print(count) 
		  count += 1 
		  return count 	 return add 

a = test() a() # 1 a() # 2

nonlocal

을 사용하면 함수에 상태를 추가할 때 전역 변수를 추가할 필요가 없으므로 이 함수를 대량으로 호출하여 여러 번 기록할 수 있다는 장점이 있습니다. 동시에 기능 상태. 각 기능은 모두 독립적이고 고유합니다. 실제로 이 함수에는 클래스를 사용하는 또 다른 방법이 있습니다. __call__을 정의하면 인스턴스에서 함수처럼 직접 호출할 수 있습니다.

1. 每次调用函数时,都得在全局作用域申明变量。别人调用函数时还得查看函数内部代码。 
3. 当函数在多个地方被调用并且同时记录着很多状态时,会造成非常地混乱。
실행 결과는
def line_conf(a, b): 
	def line(x): 
		return a * x + b 
		
	return line
	
line1 = line_conf(1, 1) line2 = line_conf(4, 5) print(line1(5)) 
	print(line2(5))
입니다. 이 코드에서 함수 라인은 변수 a와 b를 사용하여 클로저를 형성합니다. 클로저를 생성할 때 line_conf의 매개변수 a와 b를 통해 이 두 변수의 값을 지정합니다. 이러한 방식으로 함수의 최종 형태(y = x + 1 및 y = 4x + 5)를 결정합니다. 다양한

직선 표현 함수를 얻으려면 매개변수 a와 b만 변환하면 됩니다. 이를 통해 클로저도 코드 재사용성을 향상시키는 역할을 한다는 것을 알 수 있습니다. 클로저가 없으면 함수를 생성할 때마다 a, b, x를 지정해야 합니다. 이런 방식으로 더 많은 매개변수를 전달하고 코드의 이식성을 줄여야 합니다.

625

하지만 아직 끝나지 않았습니다. 내부 함수는 외부 함수 매개변수나 값을 참조하고 내부 함수 작업을 수행하지만 함수를 완전히 반환하지는 않지만 외부 함수의 값일 수도 있습니다. 반환된 내용을 알아야 합니다. 함수는 즉시 실행되지 않지만 함수가 호출될 때까지 실행되지 않습니다.

코드를 보세요:

1.闭包似优化了变量,原来需要类对象完成的⼯作,闭包也可以完成 
2.由于闭包引⽤了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存
여기서 fun_a 함수가 생성됩니다. 외부 함수의 fun_list 매개변수는 순회 중에 루프 함수 fun_b가 외부 변수 i를 참조하여 반환 결과를 계산하고 추가합니다. 반복될 때마다 새로운 함수가 생성되고 생성된 함수 3개를 모두 반환합니다


하지만 실제 결과는 우리가 원하는 1,4,9가 아닌 9,9,9입니다. 이것?

반환된 함수가 변수 i를 참조하지만 즉시 실행되지 않기 때문입니다. 세 함수가 모두 반환되면 그들이 참조하는 변수 i는 3이 됩니다. 각 독립 함수가 참조하는 개체는 동일한 변수이지만 값이 반환되면 세 함수가 모두 반환되면 값이 연산이 완료되고 함수가 호출되면 생성된 값이 원하는 결과를 얻지 못합니다. 반환 함수는 루프 변수나 향후 변경될 변수를 참조해서는 안 됩니다. 그러나 필요한 경우 이 함수를 수정하는 방법은 무엇입니까?

여기의 i 값을 _에 할당하면 문제를 해결할 수 있습니다.

def test3():
    func_list = []
    for i in range(1, 4):

        def test4(i_= i):
            return i_**2

        func_list.append(test4)
    return func_list


f1, f2, f3 = test3()print(f1(), f2(), f3())

可以再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变,那我们就可以完成下面的代码

# -*- coding: UTF-8 -*- # def fun_a(): 
	def fun_c(i): 
		def fun_b(): 
			return i * i 
			
		return fun_b 

	fun_list = [] 
	for i in range(1, 4): 
		# f(i)立刻被执行,因此i的当前值被传入f() 
		fun_list.append(fun_c(i)) 
	return fun_list 


f1, f2, f3 = fun_a() print(f1(), f2(), f3()) # 1 4 9

3.装饰器 wraps

什么是装饰器?

看一段代码:

# -*- coding: utf-8 -*-# python 全栈# author : a wei # 开发时间: 2022/6/24 17:03def eat():
    print('吃饭')def test1(func):
    def test2():
        print('做饭')
        
        func()
        
        print('洗碗')
    return test2


eat()  # 调用eat函数# 吃饭test1(eat)()# 做饭# 吃饭# 洗碗

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。也可以将函数赋值变量,做参传入另一个函数。

1>什么是装饰器

"""
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值 也是一个函数对象。 
它经常用于有以下场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝 佳设计
"""

装饰器的作用就是为已经存在的对象添加额外的功能
先看代码:

# -*- coding: utf-8 -*-# python 全栈# author : a wei # 开发时间: 2022/6/24 17:03def test1(func):
    def test2():
        print('做饭')
        func()
        print('洗碗')
    return test2@test1  # 装饰器def eat():
    print('吃饭')eat()# 做饭# 吃饭# 洗碗

我们没有直接将eat函数作为参数传入test1中,只是将test1函数以@方式装饰在eat函数上。
也就是说,被装饰的函数,函数名作为参数,传入到装饰器函数上,不影响eat函数的功能,再此基础上可以根据业务或者功能增加条件或者信息。

(注意:@在装饰器这里是作为Python语法里面的语法糖写法,用来做修饰。)

但是我们这里就存在一个问题这里引入魔术方法 name 这是属于 python 中的内置类属性,就是它会天生就存在与一个 python 程序中,代表对应程序名称,一般一段程序作为主线运行程序时其内置名称就是 main ,当自己作为模块被调用时就是自己的名字

代码:

print(eat.__name__)# test2

这并不是我们想要的!输出应该是" eat"。这里的函数被test2替代了。它重写了我们函数的名字和注释文档,那怎么阻止变化呢,Python提供functools模块里面的wraps函数解决了问题

代码:

 -*- coding: utf-8 -*-
 from functools import wrapsdef test1(func):
    @wraps(func)
    def test2():
        print('做饭')
        func()
        print('洗碗')
    return test2@test1  # 装饰器def eat():
    print('吃饭')eat()# 做饭# 吃饭# 洗碗print(eat.__name__)# eat

我们在装饰器函数内,作用eat的test2函数上也增加了一个装饰器wraps还是带参数的。
这个装饰器的功能就是不改变使用装饰器原有函数的结构。

我们熟悉了操作,拿来熟悉一下具体的功能实现,我们可以写一个打印日志的功能

# -*- coding: utf-8 -*-# python 全栈# author : a wei # 开发时间: 2022/6/24 17:42import timefrom functools import wrapsdef logger(func):
    @wraps(func)
    def write_log():
        print('[info]--时间:%s' % time.strftime('%Y-%m-%d %H:%M:%S'))
        func()
    return write_log@loggerdef work():
    print('我在工作')work()# [info]--时间:2022-06-24 17:52:11# 我在工作print(work.__name__)#work

2>带参装饰器

我们也看到装饰器wraps也是带参数的,那我们是不是也可以定义带参数的装饰器呢,我们可以使用一个函数来包裹装饰器,调入这个参数。

# -*- coding: utf-8 -*-# python 全栈# author : a wei # 开发时间: 2022/6/24 17:42import timefrom functools import wrapsdef logs(func):
    @wraps(func)
    def write_log(*args, **kwargs):
        print('[info]--时间:%s' % time.strftime('%Y-%m-%d %H:%M:%S'))
        func(*args, **kwargs)
    return write_log@logsdef work():
    print('我在工作')@logsdef work2(name1, name2):
    print('%s和%s在工作' % (name1, name2))work2('张三', '李四')# [info]--时间:2022-06-24 18:04:04# 张三和李四在工作

3>函数做装饰器

把日志写入文件

# -*- coding: utf-8 -*-# python 全栈# author : a wei# 开发时间: 2022/6/20 0:06import timefrom functools import wrapsdef logger(file):
    def logs(fun):
        @wraps(fun)
        def write_log(*args, **kwargs):
            log = '[info] 时间是:%s' % time.strftime('%Y-%m-%d %H:%M:%S')
            print(log)
            with open(file, 'a+') as f:
                f.write(log)
            fun(*args, **kwargs)
        return write_log    return logs@logger('work.log')  # 使用装饰器来给 work函数增加记录日志的功能def work(name, name2):  # 1.当前 work可能有多个参数 2.自定义日志文件的名字和位置,记录日志级别
    print(f'{name}和{name2}在工作')work('张三', '李四')

终端输出:
Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.

这里生成里work.log日志文件
Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.
里面记录日志
Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.
这里我们将带参数的带入进去根据代码流程执行生成了文件并将文件打印进去现在我们有了能用于正式环境的logs装饰器,但当我们的应用的某些部分还比较脆弱时,异常也许是需要更紧急关注的事情。

比方说有时你只想打日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留
日志,留个记录。

这是一个使用继承的场景,但目前为止我们只看到过用来构建装饰器的函数。

4>类做装饰器

# -*- coding: utf-8 -*-# python 全栈# author : a wei# 开发时间: 2022/6/20 0:06import timefrom functools import wraps# 不使用函数做装饰器,使用类做装饰器class Logs(object):
    def __init__(self, log_file='out.log', level='info'):
        # 初始化一个默认文件和默认日志级别
        self.log_file = log_file
        self.level = level    def __call__(self, fun):  # 定义装饰器,需要一个接受函数
        @wraps(fun)
        def write_log(name, name2):
            log = '[%s] 时间是:%s' % (self.level, time.strftime('%Y-%m-%d %H:%M:%S'))
            print(log)
            with open(self.log_file, 'a+') as f:
                f.write(log)
            fun(name, name2)
        return write_log@Logs()  # 使用装饰器来给 work函数增加记录日志的功能def work(name, name2):  # 1.当前 work可能有多个参数 2.自定义日志文件的名字和位置,记录日志级别
    print(f'{name}和{name2}在工作')work('张三', '李四')  # 调用work函数

这个实现有一个优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法

4.偏函数 partial

Python的 functools 模块提供了很多有用的功能,其中一个就是偏函(Partial function)。要注意,这里的偏函数和数学意义上的偏函数不一样。

在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点。

例如:int() 函数可以把字符串转换为整数,当仅传入字符串时, int() 函数默认按十进制转换

>>> int('123') 123

但 int() 函数还提供额外的 base 参数,默认值为 10 。如果传入 base 参数,就可以做进制的转换

>>> int('12345', base=8) 5349 >>> int('12345', 16) 74565

如果要转换大量的二进制字符串,每次都传入 int(x, base=2) 非常麻烦,于是,我们想到,可以定义一个int2() 的函数,默认把 base=2 传进去:

代码:

# 定一个转换义函数 >>> def int_1(num, base=2): 
		return int(num, base) 
		>>> int_1('1000000') 64>>> int_1('1010101') 85

把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单
继续优化,functools.partial 就是帮助我们创建一个偏函数的,不需要我们自己定义 int_1() ,可以直接使用下面的代码创 建一个新的函数 int_1

# 导入 >>> import functools 

# 偏函数处理 >>> int_2 = functools.partial(int, base=2) >>> int_2('1000000') 64>>> int_2('1010101') 85

理清了 functools.partial 的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

注意到上面的新的 int_2 函数,仅仅是把 base 参数重新设定默认值为 2 ,但也可以在函数调用时传入其他值实际上固定了int()函数的关键字参数 base

int2('10010')

相当于是:

kw = { base: 2 } int('10010', **kw)

当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单

【相关推荐:Python3视频教程

위 내용은 Python 반환 함수, 클로저, 데코레이터 및 부분 함수를 하나의 문서로 마스터하세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제