编程高手之路——闭包函数

目录

  • 一、函数嵌套调用
    • 1.1函数嵌套调用
    • 1.2 函数嵌套定义
  • 二、名称空间
    • 2.1 名称空间说明
    • 2.2 名称空间的分类
  • 三、作用域
    • 3.1 变量作用域
      • 3.1.1 参数名称的另一种定义形式:
    • 3.2全局作用域
    • 3.3 局部作用域
  • 四、函数对象
    • 4.1 函数对象说明
    • 4.2 函数对象应用(重点)
  • 五、闭包函数
    • 5.1 闭包函数说明
    • 5.2 为函数体传值的两种方式
    • 5.3 nonlocal关键字
  • 六、装饰器
    • 6.1 装饰器说明
    • 6.2 无参装饰器
  • 总结

一、函数嵌套调用

1.1函数嵌套调用

函数嵌套指的是在函数内部又调用了其它的函数。

# 求三个数的最大值
def max2(x, y):
    if x > y:
        return x
    else
        return y

def max3(x, y, z):
    res1 = max2(x, y)
    res2 = max2(res1, z)
    return res2

print(max3(11, 199, 2))

1.2 函数嵌套定义

函数的嵌套定义指的是在函数内又定义了其它函数。

def func1():
    print('from func1')
    def func2():
        print('from func2')
    print(func2)  # 输出函数func2的地址
    func2()

二、名称空间

2.1 名称空间说明

名称空间从字面意思理解就是存放名字的地方,在学习变量的时候知道:定义一个变量就是开辟一块内存空间,这个内存空间存放的就是变量的值,除了变量值之外,还有变量名。

变量名和变量值的绑定关系这个数据要在内存中存储。变量名是名字,函数名也是名字,名称空间就是存放名字与值的绑定关系的地方。

2.2 名称空间的分类

名称空间分为三类

  1. 内置名称空间:存放python解释器自带的名字,在解释器启动时就生效,解释器关闭就失败。
  2. 全局名称空间:文件级别的名字,在执行文件的时候生效,在文件结束或者在文件执行时被删除则失效。有些全局名称空间可能一眼看上去不像全局名称空间。我们只需要记住,只要不是内置名称空间和局部名称空间,那么就是全局名称空间。
  3. 局部名称空间:存放函数定义的名称(函数的参数以及函数内的名字都存放于局部名称空间),在函数调用时临时生效,函数结束则失效。
# 内置
print(print)
print(len)

#全局
x = 1  # 全局

def func():  # func是全局名称空间
    name = 'Albert'  # name是局部名称空间

if 10>3:
    y = 2  # 全局

while True:
    z = 5  # 全局
    break

内置名称空间与全局名称空间的结束的生命周期基本上是一致的,程序执行结束或者文件关闭(手动强制关闭写python代码的这个文件),内置或者全局名称空间生命周期结束。局部名称空间生命周期是从函数调用开始到函数结束,即函数生命周期终止。

加载顺序:内置名称空间->全局名称空间->局部名称空间
查找名字:局部名称空间->全局名称空间->内置名称空间

# 加载顺序很好理解,查找顺序是以当前位置为起始点

len = 0

def f1():
    len = 1
    def f2():
        len = 2
        print(len)

    len = 3
    f2()

f1()  # 输出为2

三、作用域

3.1 变量作用域

变量作用域是指一个变量可以使用的范围,例如在函数中定义的变量只允许本函数使用,称之为局部变量;而在代码非函数中定义的变量则称为全局变量,允许在多个函数或者代码中共同访问。

Python为了进行全局变量的标注,提供了global关键字,只需要在变量使用前利用global进行声明就可以自动将函数中操作的变量设置为全局变量。

关于变量名称解析的LEGB原则

  • L(Local):函数内部变量名称;
  • E(Enclosing Function Locals):外部嵌套函数变量名称;
  • G(Global):函数所在模块或程序文件的变量名称;
  • B(Builtin):内置模块的变量名称;

如果按照以上顺序都无法找到指定的变量名称,那么在程序执行时就会报错。

3.1.1 参数名称的另一种定义形式:

在代码开发中常常会出现同一变量名被重复使用的情况,为避免使用时出现问题,项目开发定义变量时往往使用一些标记结合变量名称一起进行定义,例如:

  1. 函数局部变量(本地变量,使用local_var_作为前缀):local_var_name;
  2. 函数参数(使用function_parameter_作为前缀):function_parameter_name;
  3. 全局变量(字母大写,使用GLOBAL_VAR_作为前缀):GLOBAL_VAR_NAME;

采用此类方法可以从根本上杜绝变量名重名的影响。而是否采用这种命名方式,还需要取决于开发者所处的开发公司的命名要求来决定。

3.2全局作用域

全局作用域包含的是内置名称空间与全局名称空间的名字,它的特点是:

  1. 在任何位置都能够访问到;
  2. 该范围的名字会伴随程序整个生命周期;

如果在局部使用全部作用域的变量是没有问题的,但是如果在局部修改全局作用域的变量则不能直接修改,而要使用global关键字才能修改。

global_count = 0

def global_check():
    print(global_count)  # 直接使用全局变量

def global_modify():
    global global_count  # 修改前需要先使用global
    global_count += 1
    print(global_count)

global_check()
global_modify()

3.3 局部作用域

局部作用域包含的是局部名称空间的名字,它的特点是

  1. 只能在函数中使用;
  2. 调用函数时生效,调用结束失效;

如果在局部使用的是嵌套在函数内部的局部变量,可以直接使用,而修改需要使用nonlocal关键字。

 def make_counter():
    count = 0

    def check_counter():
        print(count)

    check_counter()

    def modify_counter():
        nonlocal count
        count += 1
        print(count)

    modify_counter()

make_counter()

四、函数对象

4.1 函数对象说明

函数在python中是第一类对象,这个话可以通俗理解为函数也是一个对象,就是int,字符串,列表和字典一样都是对象,等学到面向对象就能有进一步的理解,现在可以暂时理解为函数对象可以像int或者字符串一样使用。

# 1、函数可以被引用

# int示例
x = 1
y = x

# 函数示例
def bar():
    print('from bar')
f = bar
f()

# 2、可以当参数传入

# int示例
x = 1

def func(a):
    print(a)

func(x)

# 函数示例
def bar():
    print('from bar')

def wrapper(func):
    func()

wrapper(bar)

# 3、可以当函数的返回值

# int示例
x = 1

def foo()
    return x

res = foo()
print(res)

# 函数示例
def bar()
    print('from bar')

def foo(func)
    return func

f = foo(bar)
f()

# 4、可以当容器类型的元素

# int示例
z = 1
l = [z, ]

print(l)

# 函数示例

def get():
    print('from get')

def put():
    print('from put')

l1 = [get, put]

l1[0]{}

4.2 函数对象应用(重点)

利用这一特性,可以优雅的取代原来的if多分支(elif这种多分支是我们写代码要尽可能避免的)。

def auth():
    print('登录。。。。')

def register():
	print('注册。。。。')

def check():
	print('查看。。。。')

def transfer():
	print('转账。。。。')

def pay():
	print('支付。。。。')

fun_dict = {
	'1':auth,
	'2':register,
	'3':check,
	'4':transfer,
	'5':pay}

def interactive():
	while True:
		print(
		"""
		1 登录
		2 注册
		3 查看
		4 转账
		5 支付
		""")
		choice = input('>>').strip()
		if choice in fun_dict:
			fun_dict[choice]()
		else:
			print('非法操作')

interactive()

五、闭包函数

5.1 闭包函数说明

闭包函数就是定义在函数内部的函数,也就是函数的嵌套定义。根据字面意思理解,闭包函数有两个关键字,分别是封闭和包裹。需要注意的重点是:闭包函数的作用域关系在函数定义阶段就固定死了,与调用位置无关。

def outer():
    x = 1

    def inner():
    	# 注意和nonlocal的区别
    	# nonlocal x
    	# x += 1
        x = 2
        print('from inner',x)

    return inner  # outer函数返回inner函数对象

f = outer()  # 现在f是一个全局变量,同时是inner函数对象

print(f)

x = 3  # 这个x = 3并不能改变inner函数外层的x
f()

def foo():
    x = 4  # 这个x = 4同样也不能改变
    f()  # 全局作用域在任意位置都可以调用

foo()

闭包函数可以用外层函数来调用内部的函数,打破了函数的层级限制,与此同时该函数包含对外部函数作用域中名字的引用。

def outer():
	name = 'Albert'

	def inner():
		print('my name is %s' % name)
	return inner

f = outer()
f()

5.2 为函数体传值的两种方式

(1)以参数的形式传入

import requests  # requests模块就是模拟浏览器向目标站点发请求

def get(url)
	response = requests.get(url)  # get方法获取请求返回对象
	print(response)
	if response.status_code == 200:  # 200是一个状态码,代表请求成功
		print(response.text)  # text方法是获取返回对象的内容

get('https://www.baidu.com')

(2)以闭包函数的形式

闭包函数就是在函数外再包裹一层作用域,由于这个作用域在外层函数内部,所以只作用在内层函数上。

def outer(url)  # 给外层参数传参相当于 url='https://www.baidu.com'

    def get():
        response = request.get(url)
        if response.status_code == 200:
            print(response.text)
    return get

baidu = outer('https://www.baidu.com')
python = outer('https://www.python.org')

baidu()
python()

5.3 nonlocal关键字

使用闭包结构的最大特点是可以保持外部函数操作的状态,但是如果要想在内部函数中修改外部函数中定义的局部变量或者参数的内容,则必须使用nonlocal关键字。

def print_data(count):
    def out(data):
        nonlocal count
        count += 1
        print("count:{},\ndata:{}".format(count,data))
    return out
    
oa = print_data(1)
oa("www.baidu")

六、装饰器

6.1 装饰器说明

器指的工具(只要是工具,就应该想到函数),装饰指的是为被装饰对象添加新功能。需要注意的是:项目一旦上线之后,就应该遵循开发封闭的原则。开放封闭指的是对修改函数内的源代码和调用方式是封闭的,对功能的扩展是开放的。

看起来有点矛盾,但这就是我们要做的。在这样的要求下,我们必须找到一种解决方案,能够在不修改一个功能源代码以及调用方式的前提下,为其添加新功能。这就用到了装饰器,它能够在不修改被装饰对象源代码与调用方式的前提下,为被装饰器对象添加新功能。

6.2 无参装饰器

(1)无参装饰器实现过程

无参修饰器指的是装饰器本身没有参数

# 要求:为index函数添加一个统计时间的功能
import time  # 这是一个与时间相关的模块

def index():
    time.sleep(3)  # 睡3秒
    print('welcome to index page')

index()

# 版本一(只有index函数可以使用)
import time  # 这是一个与时间相关的模块

def index():
    time.sleep(3)  # 睡3秒
    print('welcome to index page')

start_time = time.time()  # 从1970年开始计时的时间戳
index()
end_time = time.time()
print('run time is %s' % (end_time - start_time))

% 版本二(两个函数都可以使用,但是有大量重复代码)
def index():
    time.sleep(3)  # 睡3秒
    print('welcome to index page')

def home(name):
    time.sleep(5)
    print('welcome %s to home page' % name)

start_time = time.time()
index()
stop_time = time.time()
print('run time is %s' % (end_time - start_time))

start_time = time.time()
home('Albert')
stop_time = time.time()
print('run time is %s' % (end_time - start_time))

# 版本三(修改了源函数的调用方式)
import time

def index():
    time.sleep(3)  # 睡3秒
    print('welcome to index page')

def home(name):
    time.sleep(5)
    print('welcome %s to home page' % name)

def wrapper(func):
	start_time = time.time()
	func()
	stop_time = time.time()
	print('run time is %s' % (stop_time - start_time))

wrapper(index)

# 版本四(使用闭包函数,不修改源函数调用方式)
import time

def index():
    time.sleep(3)  # 睡3秒
    print('welcome to index page')

def outer(func):
	def wrapper():
		start_time = time.time()
		func()
		stop_time = time.time()
		print(stop_time - start_time)
    return wrapper

index = outer(index)  # 赋值给index覆盖原来的index,index = wrapper

index()  # wrapper()

# 版本五(解决原函数返回值无效)
import time

def index():
	time.sleep(1)
	print('welcome to index page')
	return 1  # 假设源函数有一个返回值

def outer(func):
	def wrapper():
		start_time = time.time()
		res = func()
		stop_time = time.time()
		print(stop_time - start_time)
		return res
    return wrapper

index = outer(index)
res = index()
print(res)

# 版本六(终极版,解决有参函数和无参函数通用的问题)
import time

def index():
	time.sleep(1)
	print('welcome to index page')
	return 1  # 假设源函数有一个返回值

def home(name):
    time.sleep(5)
    print('welcome %s to home page' % name)

def timer(func):  # 装饰器也是一个函数,起一个好听的名字
	def wrapper(*args, **kwargs):
		start_time = time.time()
		res = func(*args, **kwargs)
		stop_time = time.time()
		print(stop_time - start_time)
		return res
    return wrapper

index = timer(index)  # 新的index=wrapper
home  = timer(home)  # 新的home=wrapper

home(name='Albert')  # wrapper(name='Albert')
home('Albert')  # wrapper('Albert')
index()  # wrapper()

#  无参装饰器模板
def outer(func):
	def inner(*args, **kwargs):
		"""
		这里写装饰器逻辑
		:param args:任意位置参数
		:param kwargs:任意关键字参数
		:return:一个函数对象
		"""
		res = func(*args, **kwargs)
		return res
	return inner

(2)装饰器语法糖

import time

# 装饰器也是一个函数,使用函数必须先定义,所以装饰器放在最上方
def timer(func):
	def wrapper(*args, **kwargs):
		start_time = time.time()
		res = func(*args, **kwargs)
		stop_time = time.time()
		print(stop_time - start_time)
		return res

@timer  # 在被装饰对象正上方单独一行添加,相当于执行index=timer(index)
def index():
	time.sleep(1):
	print('welcome to index page')
	return 1

(3)用户认证装饰器

import time

current_user = {'username': None}


def auth(func):
    def wrapper(*args, **kwargs):
        if current_user['username']:
            print('已经登陆过了')
            res = func(*args, **kwargs)
            return res

        name = input('用户名>>').strip()
        pwd = input('密码>>').strip()
        if name == 'Albert' and pwd == '1':
            print('登录成功')
            current_user['username'] = name
            res = func(*args, **kwargs)
            return res

        else:
            print('用户名或密码错误')

    return  wrapper

@auth

def index():
    time.sleep(1)
    print('welcome to index page')

@auth
def home(name):
    time.sleep(2)
    print('welcome %s to home page' % name)

index()
home('Albert')

总结

  1. 知道在局部修改全局作用域的变量需要使用关键字global;
  2. 知道在局部中修改嵌套在函数内部的局部变量需要使用关键字nonlocal;
  3. 知道在嵌套函数中对局部变量重新赋值和修改的区别(是否使用nonlocal);
  4. 知道函数对象的应用,这可以有效减少if-else的使用;
  5. 知道闭包函数的使用;
  6. 知道为函数体传值的两种方式(以参数形式和以闭包形式);
  7. 使用@表示对该函数执行装饰函数;

你可能感兴趣的:(python编程高手之路)