0x00 前言简述
描述:上一章,我们学习了Python3编程中最基本而得流程控制语句,相信大家在作者的实践下也已经掌握了相关关键字了吧,这一章我们一起学习Python3编程入门中函数定义、函数调用、函数参数(传递、类型),匿名函数、递归函数。内嵌函数和闭包、装饰器函数,以及命名空间作用域的讲解,它也是Python编程中最基础且常用的部分,所以说也是需要我们掌握的。
温馨提示:作者后续实践主要在 Ubuntu 24.04 TLS
Python 3.12
Jupyter Notebook
环境中运行,若要配置为作者的学习环境,可参考《#AIGC学习之路》专栏中的流程,此外便于看友一起学习Python系列笔记,访问《#Python学习之路》专栏从前往后按照时间进行学习。
温馨提示:若各位看友在其他平台看到此篇文章,一定要关注公众号【全栈工程师修炼指南】进行持续学习!我们一同学习,一起进步,关注后回复【加群】哟!
0x01 Python3 函数
描述:几乎所有的高级编程语言都支持函数(function),当然Python也不例外,函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
函数能提高应用的模块性,和代码的重复利用率,所以在Python开发中常常能看到其身影,例如,后续类(class)和模块(module)联合使用;前面我们知道Python中内置而得一些函数,比如print()、type()、id()
等, 除此之外开发者还可以自定义函数,我们称为用户自定义函数。
Python 中函数与过程
描述:过程(procedure)是简单的,特殊的并且没有返回值的,一般的编程语言都把函数和过程分开,但 Python 事实上只有函数没有过程,函数是有返回值的,当函数中无return语句返回的时候,将返回None类型,此外 Python 可以返回多个值、利用列表
(多种类型共存)或者元组
实现;
1.函数定义调用
语法格式:
定义函数使用 def 关键字,一般格式如下:
代码语言:javascript复制def 函数名(参数列表):
'函数文档 :通过 函数名.__doc__调用'
函数体
return [返回值]
# 注:默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
# 注: .__doc__ 是函数的默认且特殊属性。
# 注:return 语句用于退出函数,选择性地向调用方返回一个表达式,不带参数值的 return 语句返回 None。
如何定义一个函数?
代码语言:javascript复制# formal parameters 形参 ,actual parameters 实参
def first(name="default"):
'函数定义过程中 name 是形参' # 因为它只是一个形式,表示占据一个参数位置。
print(name, "这时候name的值是实参,因为他是具体的参数值!")
return
weiyigeek.top-定义一个函数图
如何调用函数?
定义一个函数,给了函数一个名称,指定了函数里包含的参数,和代码块结构,你可以通过另一个函数调用执行,也可以直接从 Python 命令提示符执行。
代码语言:javascript复制# 调用函数
函数名(参数列表)
# 注:参数之间用逗号隔开,调用时括号不能省略。
# 函数中调用函数
def 函数名1(参数列表):
print(参数列表)
函数名(参数列表)
return
示例演示:
- 示例1.Python 交互命令行中调用函数。
# 定义,一个简单的输出函数
>>> def hello():
print("hello world") # 没有return 语句,将返回None
>>> temp = hello() # 调用hello函数,返回 None 给temp
hello world
>>> temp # 无输出
>>> print(temp) # 查看temp变量类型,返回了None类型
None
>>> type(temp)
<class 'NoneType'>
# 定义,一个有返回值的函数
>>> def listBack():
return ["Python","Flash", 3.1415926] # 以 中括号 包裹
>>> listBack()
['Python', 'Flash', 3.1415926] # 返回列表(List)
>> def tupleBack():
return "Python","Flash", 3.1415926 # 以 中括号 包裹
>>> tupleBack()
('Python', 'Flash', 3.1415926) # 返回元组(tuple)
默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。
- 示例2.例如,以下是简单的示例,求一个矩形的面积。
# 定义一个函数,无 retrun 返回 None
def desc(graph)
print("求", graph, "(矩形)面积:")
# 定义一个函数,计算矩形面积,retunr 返回计算值
def area(width, height):
return width * height
desc("rectangle")
width=input("矩形宽:")
height=input("矩形高:")
print(f"矩形面积:{}*{}={}".format(width, height, area(int(width), int(height))))
执行结果:
代码语言:javascript复制求 rectangle (矩形)面积:
矩形宽:5
矩形高:4
矩形面积:5*4=20
2.函数参数
2.1 参数传递
此外,Python 中类型属于对象,对象有不同类型的区分,变量是没有类型的,它仅仅是一个对象的引用(一个指针),可以是指向 List 类型对象,也可以是指向 String 类型对象,这就是我们所说的弱类型
。
知识点复习: Python 类型中的可变和不可变数据类型。
- 不可变对象(Immutable,
变量重赋值等于新建
):Number(数字)、String(字符串)、Tuple(元组) - 可变对象(Changeable,
变量重赋值等于替换
):List(列表)、Dictionary(字典)、Set(集合)、Bytes(字节数组)
类似的,Python 函数的参数传递,也有不可变类型以及可变类型:
- 不可变类型:类似 c 的值传递,整数、字符串、元组等对象。例如,在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。
- 可变类型:类似 C 的引用传递,如 列表,字典、集合、字节等对象。例如, fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响
示例,函数参数传递之不可变类型以及可变类型对象。
代码语言:javascript复制# 1.不可变对象实例
def immutable(num):
print("immutable 函数入口: ",id(num)) # 指向参数传入对象, 形参和实参指向的是同一个对象(对象 id 相同)
num = 1024 # 实际创建一个新对象
print("immutable 函数末尾: ",id(num)) # 此形参指向的是不同的 id
num = 256
print("number 初始:", id(num))
immutable(num)
# 2.可变对象实例
def changeable(mylist):
mylist.append([16, 32, 64, 128])
print ("函数内取值: ", mylist)
return
mylist = [1,2,4,8]
changeable( mylist )
print ("函数外取值: ", mylist) # 传入函数的和在末尾添加新内容的对象用的是同一个引用,所以输出是修改后的
执行结果:
代码语言:javascript复制number 初始: 9779976
immutable 函数入口: 9779976
immutable 函数末尾: 132322308354640
函数内取值: [1, 2, 4, 8, [16, 32, 64, 128]]
函数外取值: [1, 2, 4, 8, [16, 32, 64, 128]]
2.2 参数类型
描述:函数参数是函数定义中括号内的变量,在函数调用时传递给函数的值,在Python中函数的参数可分为以下几种类型:对象必需参数
、关键字参数
、默认参数
、不定长参数
、以及强制位置参数
。
必需(位置)参数:须以正确的顺序传入函数,否则出现语法错误。例如 func(arg1,arg2)
关键字参数:使用关键字参数来确定传入的参数值,并且可以改变参数的顺序,关键字参数必须写在位置参数后面。例如,func(arg1,arg2)
,调用 func(arg1="全栈工程师修炼指南",arg2="Python")
默认参数:调用函数时,如果没有传递参数,则会使用默认设定的参数值。例如,func(arg1,arg2="python")
,
不定长参数:传递参数可变可以是0或任意个,加了一个星号 *
的参数会以元组(tuple)的形式导入,加了两个星号 **
的参数会以字典的形式导入。。例如,func(arg1, *vartuple )
(重点)
# 一个星号 * :tuple
def functionname([formal_args,] *var_args_tuple ):
"函数_文档字符串"
function_suite
return [expression]
# 两个星号 ** : dict
def functionname([formal_args,] **var_args_dict ):
"函数_文档字符串"
function_suite
return [expression]
强制位置参数: Python3.8 新增了一个函数形参语法 / 用来指明之前函数形参必须使用指定位置参数,不能使用关键字参数的形式。
代码语言:javascript复制# 在以下的例子中,形参 a 和 b 必须使用指定位置参数,c 或 d 可以是位置形参或关键字形参,而 e 和 f 要求为关键字形参:
def f(a, b, /, c, d, *, e, f):
print(a, b, c, d, e, f)
# 正确调用方式
f(10, 20, 30, 40, e=50, f=60)
f(10, 20, 30, d=40, e=50, f=60)
示例,四种函数参数类型
代码语言:javascript复制#!/usr/bin/python3
# coding=utf-8
from datetime import date
# 1. 必需参数实例 (一定不要忘记函数后的:)
def printTime(str):
"printTime 帮助: 打印任何传入的字符串及时间" # 通过 函数名.__doc__调用 函数文档
today = date.today()
print(f"{str} :{today}")
return
print(printTime.__doc__)
printTime("当前日期")
# 2. 关键字参数实例
def keyParam(name, age):
print("name: ", name, "age: ", age)
return
keyParam(age=28, name="weiyigeek")
# 3. 默认参数实例
def defaultParam(name, age=28):
print("name: ", name, "age: ", age)
return
defaultParam(name="weiyigeek")
defaultParam("全栈工程师修炼指南",age=28)
# 4. 不定长参数实例
def varTupleParam(arg1,*tpargs):
"desc: 星号 * 的参数会以元组(tuple)的形式导入"
print("arg1:", arg1)
print("*tpargs:", tpargs)
varTupleParam(1024,"全栈工程师修炼指南","https://www.weiyigeek.top")
def varDictionaryParam(arg1,**kwargs):
"desc: 星号 ** 的参数会以字典(directory)的形式导入"
print("arg1:", arg1)
print("*kwargs:", kwargs)
varDictionaryParam(1024,arg2="全栈工程师修炼指南",arg3="https://www.weiyigeek.top")
执行结果:
代码语言:javascript复制"printTime 帮助: 打印任何传入的字符串及时间"
当前日期 :2024-07-22
name: weiyigeek age: 28
name: weiyigeek age: 28
name: 全栈工程师修炼指南 age: 28
arg1: 1024
*tpargs: ('全栈工程师修炼指南', 'https://www.weiyigeek.top') # 元组
arg1: 1024
*kwargs: {'arg2': '全栈工程师修炼指南', 'arg3': 'https://www.weiyigeek.top'} # 字典
特别注意:
- Python一切皆对象,严格意义上我们不能说是值传递还是引用传递,我们应该说不可变对象和可变对象。
- Python中声明函数时,参数中星号 * 可以单独出现,
星号 * 后的参数必须用关键字传入
.
def f(a,b,*,c):
"desc:计算 a b c 的值"
return a b c
# 错误报错
f(1,2,3)
# 正确示例(* 号后参数使用关键字传入!)
f(1,2,c=3)
6
3.匿名函数
描述: 在 Python 除了使用常规的def关键字来声明一个函数外,还可使用 lambda 来创建匿名函数, lambda 函数是一种小型、匿名的、内联函数,它可以具有任意数量的参数,但只能有一个表达式。使用匿名函数的好处是,使得代码更加精简, 增强可读性, 并且不需要考虑函数命名控制。
lambda: 英 / ˈlæmdə
: 希腊字母表的第11个字母 λ , 在微积分中表示拉格朗日乘子,也常用于表示波长。
如何建立一个匿名函数?
lambda 函数语法格式:
代码语言:javascript复制lambda [arg1 [,arg2,.....argn]]:expression
# 格式:
# 没有参数 lambda 函数
f = lambda: "Hello, world! Python lambda."
print(f())
# 有参数 lambda 函数
x = lambda a, b : a * b
print(x(5, 6))
print(x(5, b=6))
# 有默认参数 lambda 函数
x = lambda a=2, b=10 : a * b
print(x(5))
print(x())
示例,简单使用匿名函数
代码语言:javascript复制#!/usr/bin/python3
# coding=utf-8
from functools import reduce
# 示例1.基础用法
# 函数定义
sum = lambda arg1,arg2: arg1 arg2
sum.__doc__ = 'lambda 匿名函数文档,参数:arg1,arg2 ,返回结果:参数之和'
# 调用匿名函数
print(sum.__doc__)
print("1 99 相加后的值 :",sum(1,99))
# 示例2.匿名函数进阶用法和BIF内置函数filter、map联合使用
# 设置过滤函数,返回可以不可以整除2的数值
show = list(filter(lambda x: x % 2,range(10)))
print(show)
# map映射显示被%2整除的结构
keys = list(map(lambda x:x, range(10)))
values = list(map(lambda x:x % 2, range(10)))
# 使用 zip() 和 dict() 将两个列表转换为字典
dictionary = dict(zip(keys, values))
print(dictionary)
# 示例3.将匿名函数封装在 myfunc 函数中,通过传入不同的参数来创建不同的匿名函数
def anonymousfunc(n):
"anonymousfunc :实现参数a的指定参数n幂乘。"
return lambda a : a ** n
anon = anonymousfunc(10)
print("2^10 = ", anon(2))
# 示例4.使用 reduce() 和 lambda 表达式计算一个序列的累积乘积:
numbers = [1, 2, 3, 4, 5]
# reduce() 函数通过遍历 numbers 列表,并使用 lambda 函数将累积的结果不断更新
product = reduce(lambda x, y: x * y, numbers)
print("1 * 2 * 3 * 4 * 5 =",product) # 输出:120
执行结果:
代码语言:javascript复制lambda 匿名函数文档,参数:arg1,arg2 ,返回结果:参数之和
1 99 相加后的值 : 100
[1, 3, 5, 7, 9]
{0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1, 6: 0, 7: 1, 8: 0, 9: 1}
2^10 = 1024
1 * 2 * 3 * 4 * 5 = 120
特别注意:
- lambda 主体是一个表达式,而不是一个代码块, 仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
- lambda 函数虽然看起来只能写一行,却不等同于C或C 的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
4.递归函数
在 Python 中我们可以创建递归函数,来自己调用自己,可以用于解决一些数学计算,例如,《1.Python3编程入门介绍与起步
》文章中实践到的求斐波那契数列
,它是一个典型的递归函数使用示例。
示例,简单递归函数示例
代码语言:javascript复制def Foo(x):
if (x==1):
return 1
else:
return x Foo(x-1)
print(Foo(4))
执行结果过程:
代码语言:javascript复制Foo(1) = 1
Foo(2) = 2 Foo(1) # 3
Foo(3) = 3 Foo(2) # 6
Foo(4) = 4 Foo(3) # 10
5.命名空间作用域
5.1 命名空间
描述: 命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的,提供在项目中避免名字冲突的一种方法,各个命名空间是独立的,没有任何关系的,不同命名空间空间下变量可以重名而没有任何影响。
weiyigeek.top-图
例如,计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
代码语言:javascript复制# 文件夹 A
A/1.txt A/demo1.txt A/demo2.txt
# 文件夹 B
B/2.txt B/demo1.txt B/demo2.txt
Python 有三种命名空间:
- 局部名称(
local names
),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是) - 全局名称(
global names
),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。 - 内置名称(
built-in names
),Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
# var1 全局名称(变量)
var1 = 5
def some_func():
# var2 局部名称(变量)
var2 = 6
def some_inner_func():
# var3 内嵌的局部名称(变量)
var3 = 7
weiyigeek.top-Python 有三种命名空间图
命名空间查找顺序: 局部的命名空间 -> 全局命名空间 -> 内置命名空间
。
例如,如果找不到变量 weiyigeek,它将放弃查找并引发一个 NameError 异常: NameError: name 'weiyigeek' is not defined。
命名空间的生命周期: 取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束, 因此,我们无法从外部命名空间访问内部命名空间的对象。
5.2 作用域
描述:作用域就是一个 Python 程序可以直接访问命名空间的正文区域
,在 Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的,变量的作用域决定了在哪一部分程序
可以访问哪个特定的变量名称
。
在 Python 中只有在模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,在其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问。
在 Python 中对于变量的访问以 L(Local) –> E(Enclosing) –> G(Global) –>B(Built-in)
的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找,否则会报未定义的错误。
weiyigeek.top-作用域查询规则顺序图
例如,观察以下几个例子,均从内部函数输出变量 x:
- 局部作用域
x = 0
def outer():
x = 1
def inner():
x = 2
print(x)
inner()
outer()
执行结果为 2,因为此时直接在函数 inner 内部找到了变量 x。
- 闭包函数外的函数中
x = 0
def outer():
x = 1
def inner():
i = 2
print(x)
inner()
outer()
执行结果为 1,因为在内部函数 inner 中找不到变量 x,继续去局部外的局部——函数 outer 中找,这时找到了,输出 1。
3.全局作用域
代码语言:javascript复制x = 0
def outer():
o = 1
def inner():
i = 2
print(x)
inner()
outer()
执行结果为 0,在局部(inner函数)、局部的局部(outer函数)都没找到变量 x,于是访问全局变量,此时找到了并输出。
- 内建作用域
x = int(3.3)
g = 0
def outer():
o = 1
def inner():
i = 2
print(x)
inner()
outer()
执行结果为 3,在局部(inner函数)、局部的局部(outer函数)以及全局变量中都没有找到变量x,于是访问内建变量,此时找到了并输出。
示例,函数作用域
代码语言:javascript复制#!/usr/bin/python3
# 代码功能:全局与局部变量演示
# 1.msg 变量定义在 if 语句块中,但外部函数还是可调用访问的。
if True:
msg = "I Like Study Python~!"
print("nmsg 变量定义在 if 语句块中,但外部还是可以访问的:", msg)
# 2.验证函数作用域
def discount(price,rate):
final_price = price * rate # 局部变量
# print("这里试图打印全局变量old_price的值(回报错):", old_price)
local_price = 100 # 定义在函数中,则它就是局部变量,外部不能访问
print("局部变量 local_price :",local_price) # 100
old_price = 50
print("discount 函数内 修改后old_price的值是:",old_price) # 50
return final_price # 返回值
old_price = float(input('请输入原价:'))
rate = float(input('请输入则扣率: '))
new_price = discount(old_price,rate)
print('discount 函数外 old_price 的值是 :',old_price)
print('打折后的价格 :',new_price)
执行结果:
代码语言:javascript复制
msg 变量定义在 if 语句块中,但外部还是可以访问的: I Like Study Python~!
请输入原价:66
请输入则扣率: 0.5
局部变量local_price : 100
discount 函数内 修改后old_price的值是: 50
discount 函数外 old_price 的值是: 66.0
打折后的价格 : 33.0
特别注意:
- 如果在函数中修改全局变量便会出现,新建一个与全局变量相同名字的局部变量,并将全局变量的值赋给它,修改得其实是局部变量的值,而全局变量里面的值没有任何更改,所以在开发中慎用全局变量。
- 内置作用域是通过一个名为 builtin 的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它,例如,查看下查看到底预定义了哪些变量
import builtins; dir(builtins)
。
5.3 关键字
描述:Python 为了保护全局变量引入了{ 屏蔽(Shadowing) }
,当内部作用域想修改外部作用域的变量时,就要用到 global
和 nonlocal
关键字了。
global
关键字: 在定义的函数中更改全局变量(global variable)。nonlocal
关键字: 修改嵌套作用域(enclosing 作用域,外层非全局作用域)。
案例: 作用域关键字
代码语言:javascript复制#!/usr/bin/python3
# coding=utf-8
# 功能:全局变量作用域和非全局作用域
# 全局变量作用域
num = 1
def fun1():
global num # 使用 global 关键字声明局域变量 num 为全局变量
print("fun1 函数内,当前全局变量:",num)
num = 123
print("fun1 函数内,对全局变量重赋值",num)
num = 133
fun1()
print("函数外部, 全局变量 num 值 = ",num)
# 非全局作用域
def outer():
num = 10
def inner():
nonlocal num # 使用 nonlocal 关键字 num 变量非全局作用域
num = 100
print("inner 函数中,num = ",num)
num *= 10.24 # 类型自动转换为float
inner()
print("outer 函数中,num = ",num)
outer()
# 此处 num 没有变化由于 outer 函数中没有使用global关键字
print("函数外部, num = ",num)
执行结果:
代码语言:javascript复制fun1 函数内,当前全局变量: 1
fun1 函数内,对全局变量重赋值 123
函数外部, 全局变量 num 值 = 256
inner 函数中,num = 100
outer 函数中,num = 1024.0
函数外部, num = 256
错误示例: 有一种特殊情况,错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。
代码语言:javascript复制#!/usr/bin/python3
a = 10
def test():
a = a 1 # 报错点: UnboundLocalError: local variable 'a' referenced before assignment
print(a)
test()
特别注意:
- 若
没有使用
global 或 nonlocal 关键字对局部变量进行声明,在局部作用域中,可以访问全局命名空间中的变量,但不可对其进行赋值。 - 若
使用了
global 或 nonlocal 关键字对局部变量进行声明,在局部作用域中,可以访问全局命名空间中的变量,也可对其进行赋值, 故在局部作用域中,若想使用外部命名空间中的变量,应使用 global 或 nonlocal 关键字进行声明。 - 关于多层嵌套函数中,用 nonlocal 关键字声明的变量只影响上一层的变量,再外一层的不受影响。
#!/usr/bin/python3
# coding=utf-8
# 功能: 演示在多层嵌套函数中,nonlocal 关键字声明的变量,只影响上一层的变量。
# "全局参数"
x = 0
def outer():
# "外层函数"
x = 3
def mid():
# "中层函数"
x = 2
def inner():
# "内层函数"
nonlocal x
x = 1
print("x_inn=", x) # x_inn = 1
inner()
print("x_mid=", x) # x_inn = 1 ,受到 nonlocal 关键字影响
mid()
print("x_out=", x) # x_inn = 3,不受到 nonlocal 关键字影响
outer()
print("x_glo=", x) # x_inn = 0,因outer函数内没有使用 global 关键字定义 x_inn ,所以此处输出的还是 0
6.内嵌函数、闭包
描述:在 Python 中函数中可以内嵌函数,还能在全局变量不适用的时候可以考虑使用闭包更稳定和安全。
内嵌函数
: 即一个函数中可以定义另一个函数。闭包
: 将函数内部和函数外部连接起来的桥梁,请注意,由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,其次是闭包会在父函数外部,改变父函数内部变量的值,若把闭包当作它的公用方法(Public Method
),把内部变量当作它的私有属性(private value
),此时有可能被黑客利用。
示例,内嵌函数与闭包!
代码语言:javascript复制#!/usr/bin/python3
# coding=utf-8
# 功能:内嵌函数与闭包使用
# 内嵌函数
def fun1():
print("Fun1 主函数被调用")
def fun2():
print("Fun2 内嵌函数正在被调用n")
return fun2() #内部函数(内嵌函数),只能由fun1()调用
fun1()
# 闭包示例1
def funX(x):
def funY(y):
return x * y
return funY
i = funX(8)
print("变量 i 的类型 :",type(i))
print("i(5) =",i(5)) # 返回值 40,由于前面已经赋值给x了,后面得就给了y=5。
"""
# 其他方式:
def funX(x):
def funY(y):
return x * y
return funY(2)
>>> funX(3)
6
"""
# 闭包进阶(采用类似于数数组的方式 -- 列表(传入的是地址))
def funC(arg):
x = [arg]
def funD():
# 采用 nonlocal 关键字也行
x[0] **= x[0] # 采用这样的方式进行取值列表 (**幂运算),不引用局部变量(Local variable), 采用数组的方式进行暗渡成仓.
return x[0]
return funD()
print("2 ** 2 =",funC(2))
print("5 ** 5 =",funC(5))
执行结果:
代码语言:javascript复制Fun1 主函数被调用
Fun2 内嵌函数正在被调用
变量 i 的类型 : <class 'function'>
i(5) = 40
2 ** 2 = 4
5 ** 5 = 3125
示例2,闭包中global 与 nonlocal 关键字区别
代码语言:javascript复制# 闭包示例1.nonlocal 关键字, 其中值保存在内存之中。
def funA():
x = 5 # 函数内部变量,局部变量。
def funB():
nonlocal x # 把x强制表示不是局部变量local variable,所以就不会每次被初始化
x = 13
return x
return funB
a = funA() #当 a 第一次被赋值后,只要没被重新赋值,funA()就没被释放,也就是说局部变量x就没有被重新初始化。
print("闭包中使用 nonlocal 关键字,第一次调用:",a(),"第二次调用:",a(),"第三次调用:",a())
# 闭包示例2.global 关键字, 值还是保存在内存之中。
x=1 # 函数外部的变量,全局变量。
def funC():
x = 5
def funD():
global x # 声明全局变量x,所以每次调用都不会被重新初始化。
x = 13
return x
return funD
b = funC() #当 a 第一次被赋值后,只要没被重新赋值,funC()就没被释放,也就是说全局变量x就没有被重新初始化。
print("闭包中使用 global 关键字,第一次调用:",b(),"第二次调用:",b(),"第三次调用:",b())
执行结果:
代码语言:javascript复制闭包中使用 nonlocal 关键字,第一次调用:18 第二次调用:31 第三次调用:44
闭包中使用 global 关键字,第一次调用:14 第二次调用:27 第三次调用:40
7.装饰器函数
描述:在 Python 中的一种高级功能,它允许你动态地修改函数或类的行为,它接受一个函数作为参数,并返回一个新的函数或修改原来的函数,这就是装饰器(decorators)函数, 简单说:在不改变当前函数的情况下, 给其增加新的功能
。
优点:通过装饰器,开发者可以在保持代码整洁的同时,灵活且高效地扩展程序的功能。
装饰器的语法
使用 @decorator_name
来应用在函数或方法上, 此外还提供了一些内置的装饰器,比如 @staticmethod
和 @classmethod
,用于定义静态方法和类方法。
# 声明装饰器,将被装饰函数传入
def decorator_function(original_function):
def wrapper(*args, **kwargs):
print("Start.在调用 original_function 函数之前的操作")
result = original_function(*args, **kwargs)
print("End.在调用 original_function 函数之后的操作")
return result
# 将装饰完之后的函数返回(返回的是函数名)
return wrapper
# 使用装饰器, 装饰器通过 @ 符号应用在函数定义之前。
@decorator_function
def original_function(arg1, arg2):
print("original_function:",arg1,arg2)
pass
# 调用原函数,触发装饰器
original_function(1024, 2048)
执行结果:
代码语言:javascript复制Start.在调用 original_function 函数之前的操作
original_function: 1024 2048
End.在调用 original_function 函数之后的操作
解析说明:decorator 是一个装饰器函数,它接受一个函数 func 作为参数,并返回一个内部函数 wrapper,在 wrapper 函数内部,你可以执行一些额外的操作,然后调用原始函数 func,并返回其结果。
decorator_function
是装饰器,它接收一个函数original_function
作为参数。wrapper
是内部函数,它是实际会被调用的新函数,它包裹了原始函数的调用,并在其前后增加了额外的行为。- 当我们使用
@decorator_function
前缀在target_function
定义前,Python会自动将target_function
作为参数传递给decorator_function
,然后将返回的wrapper
函数替换掉原来的target_function
。
7.1 带参数的装饰器
描述:repeat 函数是一个带参数的装饰器,它接受一个整数参数 n,然后返回一个装饰器函数,此参数是用来控制装饰器的执行次数。
代码语言:javascript复制# 声明装饰器
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
# 使用装饰器
@repeat(5)
def greet(name):
print(f"Hello, {name}!")
# 调用原函数,触发装饰器
greet("WeiyiGeek")
执行结果:
代码语言:javascript复制Hello, WeiyiGeek!
Hello, WeiyiGeek!
Hello, WeiyiGeek!
Hello, WeiyiGeek!
Hello, WeiyiGeek!
7.2 类装饰器
类装饰器是包含 __call__
方法的类,它接受一个函数作为参数,并返回一个新的函数。
class DecoratorClass:
# 首先,DecoratorClass 的 __init__ 方法应该只接受 count 参数
def __init__(self, count=3):
self.count = count
self.execution_count = 0
def __call__(self, func):
def wrapper(*args, **kwargs):
self.execution_count = 1
print(f"Execution count: {self.execution_count}")
result = None
for _ in range(self.count):
result = func(*args, **kwargs)
return result
return wrapper
# 使用装饰器
@DecoratorClass(count=5)
def my_function(name):
print(f"Hello, {name}!")
# 调用原函数,触发装饰器
my_function("公众号: 全栈工程师修炼指南")
my_function("公众号: 全栈工程师修炼指南")
执行结果:
代码语言:javascript复制Execution count: 1
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Execution count: 2
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
Hello, 公众号: 全栈工程师修炼指南!
代码说明:
__init__
方法:- 接受
count
参数并初始化self.count
。 - 增加
self.execution_count
属性来记录装饰函数被调用的次数。
- 接受
__call__
方法:- 增加
self.execution_count
以记录装饰器的执行次数。 - 打印当前的执行次数。
- 执行
func
函数self.count
次,并返回最后一次执行的结果。 - 接受一个函数对象
func
,返回一个新的wrapper
函数。 - 在
wrapper
函数中:
- 增加
- 使用装饰器:
- 使用
@DecoratorClass(count=3)
装饰my_function
函数,并传递count
参数。
- 使用
这样,my_function
每次被调用时都会打印装饰器的执行次数,并且 func
被执行 count
次。