Python3 | 练气期,函数创建、参数传递、作用域!

2024-07-29 10:01:04 浏览数 (3)

[ 知识是人生的灯塔,只有不断学习,才能照亮前行的道路 ]

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 交互命令行中调用函数。
代码语言:javascript复制
# 定义,一个简单的输出函数
>>> 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.例如,以下是简单的示例,求一个矩形的面积。
代码语言:javascript复制
# 定义一个函数,无 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 ) (重点)

代码语言:javascript复制
# 一个星号 * :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中声明函数时,参数中星号 * 可以单独出现,星号 * 后的参数必须用关键字传入.
代码语言:javascript复制
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

特别注意:

  1. lambda 主体是一个表达式,而不是一个代码块, 仅仅能在lambda表达式中封装有限的逻辑进去。
  2. lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
  3. 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 有三种命名空间

  1. 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
  2. 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
  3. 内置名称(built-in names),Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。
代码语言:javascript复制
# 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:

  1. 局部作用域
代码语言:javascript复制
x = 0
def outer():
    x = 1
    def inner():
        x = 2
        print(x)
    inner()

outer()

执行结果为 2,因为此时直接在函数 inner 内部找到了变量 x。

  1. 闭包函数外的函数中
代码语言:javascript复制
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,于是访问全局变量,此时找到了并输出。

  1. 内建作用域
代码语言:javascript复制
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) },当内部作用域想修改外部作用域的变量时,就要用到 globalnonlocal关键字了。

  • 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 关键字声明的变量只影响上一层的变量,再外一层的不受影响。
代码语言:javascript复制
#!/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,用于定义静态方法和类方法。

代码语言:javascript复制
# 声明装饰器,将被装饰函数传入
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__ 方法的类,它接受一个函数作为参数,并返回一个新的函数。

代码语言:javascript复制
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, 公众号: 全栈工程师修炼指南!

代码说明:

  1. __init__ 方法:
    • 接受 count 参数并初始化 self.count
    • 增加 self.execution_count 属性来记录装饰函数被调用的次数。
  2. __call__ 方法:
    • 增加 self.execution_count 以记录装饰器的执行次数。
    • 打印当前的执行次数。
    • 执行 func 函数 self.count 次,并返回最后一次执行的结果。
    • 接受一个函数对象 func,返回一个新的 wrapper 函数。
    • wrapper 函数中:
  3. 使用装饰器:
    • 使用 @DecoratorClass(count=3) 装饰 my_function 函数,并传递 count 参数。

这样,my_function 每次被调用时都会打印装饰器的执行次数,并且 func 被执行 count 次。

0 人点赞