【Python 初级函数详解】—— 参数沙漠与作用域丛林的求生指南

【Python 初级函数详解】—— 参数沙漠与作用域丛林的求生指南
欢迎来到ZyyOvO的博客✨,一个关于探索技术的角落,记录学习的点滴📖,分享实用的技巧🛠️,偶尔还有一些奇思妙想💡
本文由ZyyOvO原创✍️,感谢支持❤️!请尊重原创📩!欢迎评论区留言交流🌟
个人主页 👉 ZyyOvO
本文专栏➡️Python 算法研究所
请添加图片描述

快速复习👉【Python 速览 】 —— 课前甜点,打开你的味蕾

课前导入

我们知道数学中的函数,我们输入一个数,在通过对应的映射关系得到另一个数,如下图给出了两个简单的数学函数:

在这里插入图片描述

什么是函数

那在Python编程中函数是什么呢?

在编程中,函数(Function) 是一段被命名、可重复使用的代码块,用于执行特定任务,它通过接收输入(参数),处理逻辑,并返回输出(结果),将复杂的程序拆分为模块化的组件,让代码更简洁、高效且易于维护。

函数的优势

在 Python 中,函数是编程的核心工具之一,它通过将代码逻辑封装为可重复使用的模块,显著提升了代码的可维护性、复用性和可读性。

避免代码重复:DRY 原则(Don't Repeat Yourself)

  • 问题场景: 当同一段代码需要多次使用时,复制粘贴会导致代码臃肿,且后续修改需要逐一调整所有副本。
  • 函数解决方案: 将重复代码封装为函数,通过一次定义、多次调用来实现逻辑复用。
# 重复代码示例(计算圆面积) radius1 =5 area1 =3.14* radius1 **2 radius2 =7 area2 =3.14* radius2 **2# 使用函数优化defcalculate_circle_area(radius):return3.14* radius **2 area1 = calculate_circle_area(5) area2 = calculate_circle_area(7)
  • 修改逻辑只需调整函数内部代码(例如将 3.14 改为 math.pi)。
  • 减少代码量,避免冗余。

模块化编程:分解复杂问题

  • 问题场景: 一个大型程序如果写成连续的代码块,会难以理解和维护。
  • 函数解决方案: 将程序拆分为多个函数,每个函数负责单一职责。
# 未使用函数的复杂逻辑 data =[1,2,3,4,5]sum=0for num in data:sum+= num average =sum/len(data)print(f"平均值: {average}")# 使用函数模块化defcalculate_average(data):returnsum(data)/len(data) data =[1,2,3,4,5]print(f"平均值: {calculate_average(data)}")
  • 代码逻辑清晰,每个函数聚焦一个子任务。
  • 便于团队协作开发。

提升代码可读性

  • 问题场景: 长段代码缺乏注释时,阅读者需要逐行理解逻辑。
  • 函数解决方案: 通过函数名称直接表明代码意图。
# 难读的代码 x =5 y =10 result =(x **2+ y **2)**0.5# 使用函数自解释defcalculate_hypotenuse(a, b):return(a**2+ b**2)**0.5 result = calculate_hypotenuse(5,10)
  • 函数名(如 calculate_hypotenuse)直接说明功能。
  • 减少对注释的依赖。

参数化与灵活性

  • 问题场景: 相似逻辑需要处理不同输入数据时,硬编码值会限制灵活性。
  • 函数解决方案: 通过参数动态接收输入,返回处理结果。
# 硬编码的欢迎语print("欢迎,张三!")print("欢迎,李四!")# 参数化函数defgreet(name):print(f"欢迎,{name}!") greet("张三") greet("李四")
  • 同一函数适应不同输入。
  • 支持动态数据处理。

错误隔离与调试效率

  • 问题场景: 代码错误可能出现在任意位置,调试时需全局排查。
  • 函数解决方案: 将代码划分为函数后,错误通常能被隔离到特定函数内。
defload_data(filename):# 假设此处可能抛出文件不存在的错误withopen(filename,'r')as f:return f.read()defprocess_data(data):# 此处可能处理数据时出错return data.upper()try: data = load_data("data.txt") result = process_data(data)except Exception as e:print(f"错误发生在: {e}")
  • 快速定位问题函数。
  • 通过单元测试单独验证每个函数。

支持代码复用与库开发

  • 问题场景: 通用功能(如数据验证、数学计算)需要在多个项目中重复实现。
  • 函数解决方案: 将通用函数封装为模块(.py 文件),供其他程序导入。
# utils.pydefis_prime(n):if n <=1:returnFalsefor i inrange(2,int(n**0.5)+1):if n % i ==0:returnFalsereturnTrue# main.pyfrom utils import is_prime print(is_prime(17))# 输出: True
  • 积累个人代码库,提升开发效率。
  • 促进开源社区协作(如 numpyrequests 等库)。

实现递归算法

  • 问题场景: 某些问题(如阶乘、斐波那契数列)天然适合递归解决。
  • 函数解决方案: 函数可以调用自身,简化递归逻辑。
deffactorial(n):if n ==0:return1else:return n * factorial(n-1)print(factorial(5))# 输出: 120
  • 以简洁方式解决复杂递归问题。
  • 代码更接近数学定义。
应用场景函数的核心作用
代码重复封装重复逻辑,实现 DRY 原则
复杂问题分解为模块化组件,降低认知负担
可读性差通过命名自解释代码意图
动态数据处理参数化输入,提升灵活性
错误调试隔离问题范围,简化排查流程
代码复用构建可共享的代码库
递归问题实现自我调用的算法逻辑

通过函数,Python 代码从“一次性脚本”升级为可维护、可扩展的工程化项目。它是构建大型应用、开源库和高效算法的基石。


定义函数

例如,我们写一段代码来求斐波那契数列:

deffib(n):# 打印小于 n 的斐波那契数列"""Print a Fibonacci series less than n.""" a, b =0,1while a < n:print(a, end=' ') a, b = b, a+b 

定义 函数使用关键字 def,后跟函数名与括号内的形参列表。函数语句从下一行开始,并且必须缩进

函数内的第一条语句是字符串时,该字符串就是文档字符串,也称为 docstring。Python 开发者最好养成在代码中加入文档字符串的好习惯。

函数在 执行 时使用函数局部变量符号表,所有函数变量赋值都存在局部符号表中;引用变量时,首先,在局部符号表里查找变量,然后,是外层函数局部符号表,再是全局符号表,最后是内置名称符号表。因此,尽管可以引用全局变量和外层函数的变量,但最好不要在函数内直接赋值(除非是 global 语句定义的全局变量,或 nonlocal 语句定义的外层函数变量)。


函数对象的概念

在这里插入图片描述

在调用函数时会将实际参数(实参)引入到被调用函数的局部符号表中;因此,实参是使用 按值调用 来传递的(其中的 值 始终是对象的 引用 而不是对象的值)。 当一个函数调用另外一个函数时,会为该调用创建一个新的局部符号表。

在Python中遵循着一切皆对象的原则,无论是变量,函数,亦或是类,在Python看来都是对象,因此,函数定义在当前符号表中把函数名与函数对象关联在一起。解释器把函数名指向的对象作为用户自定义函数。
还可以使用其他名称指向同一个函数对象,并访问访该函数,例如我们上面写的求斐波那契数列的函数:

deffib(n):# 打印小于 n 的斐波那契数列"""Print a Fibonacci series less than n.""" a, b =0,1while a < n:print(a, end=' ') a, b = b, a+b f = fib #将函数对象赋值给f f(100)#调用fprint() fib(100)#调用fib()

输出:

0112358132134558901123581321345589

如果你学过其他语言,比如C语言或者C++,你可能会认为 fib 不是函数而是一个过程语句,因为它没有返回值。 事实上,即使没有 return 语句的函数也有返回值, 这个值被称为 None (是一个内置名称,Python中函数的默认返回值)。 通常解释器会屏蔽单独的返回值 None。 如果你确有需要可以使用 print() 查看它:

print(fib(0))#输出None

编写不直接输出斐波那契数列运算结果,而是返回运算结果列表的函数也非常简单:只需要在函数中定义列表,将斐波那契数使用append接口加入到列表中,最后返回列表对象即可:

deffib2(n):# 返回斐波那契数组直到 n"""Return a list containing the Fibonacci series up to n.""" result =[] a, b =0,1while a < n: result.append(a)# 见下 a, b = b, a+b return result f100 = fib2(100)# 调用它 f100 # 输出结果[0,1,1,2,3,5,8,13,21,34,55,89]

函数定义详解

函数定义支持可变数量的参数。这里列出三种可以组合使用的形式。

默认值参数

为参数指定默认值是非常有用的方式。调用函数时,可以使用比定义时更少的参数,例如:

defask_ok(prompt, retries=4, reminder='Please try again!'):whileTrue: reply =input(prompt)if reply in{'y','ye','yes'}:returnTrueif reply in{'n','no','nop','nope'}:returnFalse retries = retries -1if retries <0:raise ValueError('invalid user response')print(reminder)

该函数有三种调用方式:

  • 只给出必选实参:ask_ok('Do you really want to quit?')
  • 给出一个可选实参:ask_ok('OK to overwrite the file?', 2)
  • 给出所有实参:
ask_ok('OK to overwrite the file?',2, 'Come on, only yes or no!')

retriesreminder默认参数。默认参数在函数定义时指定了默认值,调用函数时如果没有为这些参数提供值,就会使用默认值。而prompt位置参数,调用函数时必须按照顺序传递参数,否则就会导致错误!

使用示例:

try:#异常捕获 result = ask_ok("Do you want to continue? ")if result:print("You agreed!")else:print("You disagreed!")except ValueError as e:print(e)

默认值在 定义 作用域里的函数定义中求值所以:

i =5deff(arg=i):print(arg) i =6 f()

上例输出的是 5。为什么呢???
这里创建了一个全局变量 i,并将其赋值为 5。在定义函数 f 时,默认参数 arg 的值被设定为当前全局变量 i 的值,也就是 5。需要注意的是,Python 函数的默认参数值是在函数定义时就确定下来的,而不是在函数调用时确定。

将全局变量 i 的值修改为 6,但这并不会影响函数 f 中默认参数 arg 的值,因为 arg 的默认值在函数定义时就已经确定为 5 了。

值得注意的是: 默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果。例如,下面的函数会累积后续调用时传递的参数:

deff(a, L=[]): L.append(a)return L print(f(1))print(f(2))print(f(3))

输出结果如下:

[1][1,2][1,2,3]

这里定义了函数 f,其中参数 L 的默认值是一个空列表 []。需要特别注意的是,在 Python 中,函数的默认参数是在函数定义时就被创建的,并且只创建一次。也就是说,无论函数被调用多少次,这个默认的列表 L 始终是同一个对象。

如果不想在后续调用之间共享默认值时,应以如下方式编写函数:

deff(a, L=None):if L isNone: L =[] L.append(a)return L 

这样修改后,每次调用函数时,如果没有传入 L 参数,都会创建一个新的空列表,输出结果将分别为:

[1][2][3]

关键字参数

key=value 形式的 关键字参数 也可以用于调用函数。函数示例如下:

defparrot(voltage, state='a stiff', action='voom',type='Norwegian Blue'):print("-- This parrot wouldn't", action, end=' ')print("if you put", voltage,"volts through it.")print("-- Lovely plumage, the",type)print("-- It's", state,"!")

该函数接受一个必选参数(voltage)和三个可选参数(state, action 和 type)。该函数可用下列方式调用:

parrot(1000)# 1 个位置参数 parrot(voltage=1000)# 1 个关键字参数 parrot(voltage=1000000, action='VOOOOOM')# 2 个关键字参数 parrot(action='VOOOOOM', voltage=1000000)# 2 个关键字参数 parrot('a million','bereft of life','jump')# 3 个位置参数 parrot('a thousand', state='pushing up the daisies')# 1 个位置参数,1 个关键字参数

以下调用函数的方式都无效:

parrot()# 缺失必需的参数 parrot(voltage=5.0,'dead')# 关键字参数后存在非关键字参数 parrot(110, voltage=220)# 同一个参数重复的值 parrot(actor='John Cleese')# 未知的关键字参数

函数调用时,关键字参数必须跟在位置参数后面.

例如,以下调用方式是错误的:

# 错误示例,位置参数应在关键字参数之前 parrot(state='dead',300)

所有传递的关键字参数都必须匹配一个函数接受的参数(比如,actor 不是函数 parrot 的有效参数),关键字参数的顺序并不重要。这也包括必选参数,(比如,parrot(voltage=1000) 也有效)。不能对同一个参数多次赋值,下面就是一个因此限制而失败的例子:

deffunction(a):pass function(0, a=0)

最后一个形参为 **name 形式时,接收一个字典,(映射类型 — dict),该字典包含与函数中已定义形参对应之外的所有关键字参数。**name 形参可以与 *name 形参(下一小节介绍)组合使用(*name 必须在 **name 前面), *name 形参接收一个 元组,该元组包含形参列表之外的位置参数。例如,可以定义下面这样的函数:

defcheeseshop(kind,*arguments,**keywords):print("-- Do you have any", kind,"?")print("-- I'm sorry, we're all out of", kind)for arg in arguments:print(arg)print("-"*40)for kw in keywords:print(kw,":", keywords[kw])

参数解释:

  • kind:这是一个普通的位置参数,调用函数时必须提供该参数的值,用于指定奶酪的种类。
  • *arguments:这是一个可变位置参数,它会将函数调用时传入的除了第一个位置参数(即 kind)之外的所有位置参数收集到一个元组中。在函数内部,可以通过遍历这个元组来处理这些参数。
  • **keywords:这是一个可变关键字参数,它会将函数调用时传入的所有未在函数定义中明确指定的关键字参数收集到一个字典中。字典的键是关键字参数的名称,值是对应的参数值。

该函数可以用如下方式调用:

cheeseshop("Limburger","It's very runny, sir.","It's really very, VERY runny, sir.", shopkeeper="Michael Palin", client="John Cleese", sketch="Cheese Shop Sketch")

第一个参数 "Limburger" 被赋值给 kind
接下来的两个字符串 "It's very runny, sir.""It's really very, VERY runny, sir." 被收集到 arguments 元组中。
最后三个关键字参数 shopkeeper="Michael Palin"、client="John Cleese"sketch="Cheese Shop Sketch" 被收集到 keywords 字典中。

输出结果如下:

-- Do you have any Limburger ? -- I'm sorry, we're all out of Limburger It's very runny, sir. It's really very, VERY runny, sir.---------------------------------------- shopkeeper : Michael Palin client : John Cleese sketch : Cheese Shop Sketch 

在 Python 中,*arguments(可变位置参数)必须放在 **keywords(可变关键字参数)之前,否则会导致语法错误,为什么呢???

Python 函数在调用时,会按照特定的顺序解析传入的参数,具体顺序为:

位置参数 -> 可变位置参数 -> 关键字参数 -> 可变关键字参数。 
  • 位置参数:是按照参数定义的顺序依次接收传入的值。
  • 可变位置参数:会收集所有剩余的位置参数(即除了已被普通位置参数接收的部分)到一个元组中。
  • 关键字参数:通过参数名来指定传入的值。
  • 可变关键字参数:会收集所有未在函数定义中明确指定的关键字参数到一个字典中。

如果 **keywords 放在 *arguments 之前,当函数调用时,Python 解释器在解析参数时就会遇到问题。因为 **keywords 会优先收集所有的关键字参数,而此时如果还有剩余的位置参数,就无法正确地将它们收集到 *arguments 中,因为 *arguments 是用来收集位置参数的,而在 **keywords 之后,解释器已经无法区分哪些是位置参数了。

注意,关键字参数在输出结果中的顺序与调用函数时的顺序一致。


特殊参数

默认情况下,参数可以按位置或显式关键字传递给 Python 函数。为了让代码易读、高效,最好限制参数的传递方式,这样,开发者只需查看函数定义,即可确定参数项是仅按位置、按位置或关键字,还是仅按关键字传递。

函数定义如下:

deff(pos1, pos2,/, pos_or_kwd,*, kwd1, kwd2):-------------------------------|||| 位置或关键字 ||- 仅限关键字 -- 仅限位置 

/ 和 * 是可选的。这些符号表明形参如何把参数值传递给函数:位置、位置或关键字、关键字。关键字形参也叫作命名形参。

位置或关键字参数

函数定义中未使用 /* 时,参数可以按位置或关键字传递给函数。

仅位置参数

此处再介绍一些细节,特定形参可以标记为 仅限位置。仅限位置 时,形参的顺序很重要,且这些形参不能用关键字传递。仅限位置形参应放在 / (正斜杠)前。/ 用于在逻辑上分割仅限位置形参与其它形参。如果函数定义中没有 /,则表示没有仅限位置形参。
/ 后可以是 位置或关键字 或 仅限关键字 形参。

仅限关键字参数

把形参标记为 仅限关键字,表明必须以关键字参数形式传递该形参,应在参数列表中第一个 仅限关键字 形参前添加 *

简单的函数示例

注意 / 和 * 标记:

defstandard_arg(arg):print(arg)defpos_only_arg(arg,/):print(arg)defkwd_only_arg(*, arg):print(arg)defcombined_example(pos_only,/, standard,*, kwd_only):print(pos_only, standard, kwd_only)

第一个函数定义 standard_arg 是最常见的形式,对调用方式没有任何限制,可以按位置也可以按关键字传递参数:

位置参数:

standard_arg(2)#输出2

关键字参数:

standard_arg(arg=2)#输出2

第二个函数 pos_only_arg 的函数定义中有 /,仅限使用位置形参

位置形参:

pos_only_arg(1)#输出1

关键字参数:报错!

pos_only_arg(arg=1) Traceback (most recent call last): File "<stdin>", line 1,in<module> TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments:'arg'

第三个函数 kwd_only_arg 如在函数定义中通过 * 所指明的那样只允许关键字参数。

位置参数:报错

kwd_only_arg(3) Traceback (most recent call last): File "<stdin>", line 1,in<module> TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given 

关键字参数:

kwd_only_arg(arg=3)#输出3

最后一个函数在同一个函数定义中,使用了全部三种调用惯例:

全部位置参数:报错!

combined_example(1,2,3) Traceback (most recent call last): File "<stdin>", line 1,in<module> TypeError: combined_example() takes 2 positional arguments but 3 were given 

位置参数和关键字参数:

combined_example(1,2, kwd_only=3)#输出123 combined_example(1, standard=2, kwd_only=3)#输出123

全部关键字参数:报错!

combined_example(pos_only=1, standard=2, kwd_only=3) Traceback (most recent call last): File "<stdin>", line 1,in<module> TypeError: combined_example() got some positional-only arguments passed as keyword arguments:'pos_only'

下面的函数定义中,kwdsname 当作键,因此,可能与位置参数 name 产生潜在冲突:

deffoo(name,**kwds):return'name'in kwds 

调用该函数不可能返回 True,因为关键字 'name' 总与第一个形参绑定。例如:

foo(1,**{'name':2}) Traceback (most recent call last): File "<stdin>", line 1,in<module> TypeError: foo() got multiple values for argument 'name'

代码报错原因:

当执行 foo(1, **{'name': 2}) 时:
首先,Python 会把位置参数 1 赋值给 name,这是按照位置参数的传递规则来的。
接着,要把字典 {'name': 2} 解包传递给函数。但是这时 Python 发现,name 这个参数已经被位置参数 1 赋值过了,现在又有一个关键字参数 name=2 要进来,就会产生冲突。因为在 Python 里,一个参数不能被多次赋值,所以就会抛出 TypeError: foo() got multiple values for argument 'name' 异常。

正确做法:

  • 加上 / (仅限位置参数)后,就可以了。此时,函数定义把 name 当作位置参数,'name' 也可以作为关键字参数的键:
deffoo(name,/,**kwds):return'name'in kwds foo(1,**{'name':2})#输出True
  • 这里的 / 起到了关键作用,它把 name 变成了仅限位置参数。这就规定了 name
    只能通过位置的方式来传值,不能用关键字参数的方式传值。

调用过程分析
当调用 foo(1, **{'name': 2}) 时:
位置参数 1 会被正常赋值给仅限位置参数 name,这是符合仅限位置参数的传递规则的。
对于字典 {'name': 2},由于 name 是仅限位置参数,字典里的 'name' 键就不会和函数定义里的 name 参数冲突了。**kwds 会把这个 {'name': 2} 字典收集起来,成为一个包含 'name': 2 的字典。
函数内部的 'name' in kwds 语句会去检查 'name' 这个键是否在 kwds 字典里,因为 kwds 字典里确实有 'name' 键,所以函数就会返回 True

可以把这种情况想象成有两个不同的 “区域”,一个区域(仅限位置参数 name)只能通过位置传递的方式放东西进去;另一个区域(**kwds 字典)可以接收关键字参数。这样就避免了参数名的冲突。

换句话说,仅限位置形参的名称可以在 **kwds 中使用,而不产生歧义。

小结

以下用例决定哪些形参可以用于函数定义:

deff(pos1, pos2,/, pos_or_kwd,*, kwd1, kwd2):

说明:

  • 使用仅限位置形参,可以让用户无法使用形参名。形参名没有实际意义时,强制调用函数的实参顺序时,或同时接收位置形参和关键字时,这种方式很有用。
  • 当形参名有实际意义,且显式名称可以让函数定义更易理解时,阻止用户依赖传递实参的位置时,才使用关键字。
  • 对于 API,使用仅限位置形参,可以防止未来修改形参名时造成破坏性的 API 变动。

函数的变量

变量的作用域

在这里插入图片描述

在 Python 中,作用域定义了变量和函数的可见性与生命周期,它决定了在程序的哪些部分可以访问特定的变量或函数。Python 中有四种主要的作用域,按照搜索变量的顺序依次为:局部作用域(Local)闭包作用域(Enclosing)全局作用域(Global)内置作用域(Built-in),通常用 LEGB 规则来描述这个搜索顺序。下面为你详细介绍这四种作用域。

内置作用域(Built-in)

定义:内置作用域是 Python 预先定义好的,包含了所有内置的函数、异常和对象,如 print()len()int 等。这些名称在整个 Python 程序中都是可用的,无需额外导入。
示例:

# 直接使用内置函数 print 和 lenprint(len([1,2,3]))

在这个例子中,printlen 都是内置作用域中的函数,可以直接使用。


全局作用域(Global)

定义:全局作用域是在整个模块(即 .py 文件)中定义的作用域。在模块顶层定义的变量和函数都属于全局作用域,它们可以在模块的任何地方被访问,但在函数内部如果要修改全局变量,需要使用 global 关键字。
示例:

# 定义全局变量 global_variable =10defprint_global_variable():# 可以在函数内部访问全局变量print(global_variable) print_global_variable()# 输出: 10defmodify_global_variable():global global_variable global_variable =20 modify_global_variable()print(global_variable)# 输出: 20

在上述代码中,global_variable 是全局变量,可以在函数 print_global_variable 中直接访问。而在 modify_global_variable 函数中,如果要修改全局变量的值,需要使用 global 关键字声明。

闭包作用域(Enclosing)

定义:闭包作用域也称为嵌套作用域,它出现在嵌套函数中。当一个函数内部定义了另一个函数时,内部函数可以访问外部函数的局部变量,这些外部函数的局部变量就处于闭包作用域中。在 Python 中,闭包是一种特殊的对象,它可以捕获并保存外部函数的局部变量状态。
示例:

defouter_function(): enclosing_variable =5definner_function():# 内部函数可以访问外部函数的局部变量print(enclosing_variable)return inner_function closure = outer_function() closure()# 输出: 5

在这个例子中,inner_function 是嵌套在 outer_function 内部的函数,enclosing_variable 是 outer_function 的局部变量,对于 inner_function 来说,它处于闭包作用域中,因此 inner_function 可以访问这个变量。

局部作用域(Local)

定义:局部作用域是在函数内部定义的作用域。函数内部定义的变量和参数都属于局部作用域,它们只能在函数内部被访问,函数执行结束后,局部变量会被销毁。
示例:

deflocal_scope_example(): local_variable =3print(local_variable) local_scope_example()# 输出: 3# print(local_variable) # 会报错,因为 local_variable 是局部变量,外部无法访问

local_scope_example 函数内部定义的 local_variable 是局部变量,只能在函数内部访问,在函数外部尝试访问会引发 NameError 异常。


函数中变量的查找顺序

在这里插入图片描述


在 Python 中,函数内部的变量查找遵循 LEGB 规则,即按照以下顺序逐层查找变量:
LEGB

Local(局部作用域) → Enclosing(闭包外层) → Global(全局) → Built-in(内置) 
  1. Local(局部作用域)

优先级最高:首先在函数内部查找变量

示例:

deffunc(): x =10# 局部变量print(x)# 输出 10(优先查找局部) func()
  1. Enclosing(闭包外层)

适用于嵌套函数:在外层函数的作用域中查找

示例:

defouter(): y =20definner():print(y)# 查找闭包外层的 y → 20 inner() outer()
  1. Global(全局作用域)

模块级变量:在函数外部定义的变量

示例:

z =30deffunc():print(z)# 查找全局的 z → 30 func()
  1. Built-in(内置作用域)

Python 内置函数/变量:如 len, str, True

示例:

deffunc():print(len)# 找到内置函数 len → <built-in function len> func()

关键注意

  • 赋值操作的影响

若在函数内部对变量赋值,Python 会默认将其视为局部变量(即使全局存在同名变量)

x =100deffunc(): x =200# 创建新的局部变量print(x)# 输出 200 func()print(x)# 输出 100(全局变量未变)
  • global 关键字 强制使用全局变量
x =100deffunc():global x x =200# 修改全局变量print(x)# 输出 200 func()print(x)# 输出 200
  • nonlocal 关键字 在嵌套函数中使用外层非全局变量
defouter(): x =10definner():nonlocal x x =20# 修改闭包外层的 x inner()print(x)# 输出 20 outer()
  • 变量未定义时的报错,若所有作用域均未找到变量:
deffunc():print(undefined_var)# 报错 NameError func()

是否是否是否是否函数内访问变量是局部变量?使用局部变量是闭包外层变量?使用外层变量是全局变量?使用全局变量是内置变量?使用内置变量抛出 NameError


本文小结

本文有关Python中初级函数话题到这里就结束了,后面的文章我们会展开继续 Python函数的更多话题,如内置高级函数,lambda表达式,任意实参列表等等… 感谢您的观看!

如果你觉得这篇文章对你有所帮助,请为我的博客 点赞👍收藏⭐️ 评论💬或 分享🔗 支持一下!你的每一个支持都是我继续创作的动力✨!🙏
如果你有任何问题或想法,也欢迎 留言💬 交流,一起进步📚!❤️ 感谢你的阅读和支持🌟!🎉
祝各位大佬吃得饱🍖,睡得好🛌,日有所得📈,逐梦扬帆⛵!

Read more

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

【OpenClaw从入门到精通】第10篇:OpenClaw生产环境部署全攻略:性能优化+安全加固+监控运维(2026实测版)

摘要:本文聚焦OpenClaw从测试环境走向生产环境的核心痛点,围绕“性能优化、安全加固、监控运维”三大维度展开实操讲解。先明确生产环境硬件/系统选型标准,再通过硬件层资源管控、模型调度策略、缓存优化等手段提升响应速度(实测响应效率提升50%+);接着从网络、权限、数据三层构建安全防护体系,集成火山引擎安全方案拦截高危操作;最后落地TenacitOS可视化监控与Prometheus告警体系,配套完整故障排查清单和虚拟实战案例。全文所有配置、代码均经实测验证,兼顾新手入门实操性和进阶读者的生产级部署需求,帮助开发者真正实现OpenClaw从“能用”到“放心用”的跨越。 优质专栏欢迎订阅! 【DeepSeek深度应用】【Python高阶开发:AI自动化与数据工程实战】【YOLOv11工业级实战】 【机器视觉:C# + HALCON】【大模型微调实战:平民级微调技术全解】 【人工智能之深度学习】【AI 赋能:Python 人工智能应用实战】【数字孪生与仿真技术实战指南】 【AI工程化落地与YOLOv8/v9实战】【C#工业上位机高级应用:高并发通信+性能优化】 【Java生产级避坑指南:

By Ne0inhk
ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(互斥体实现 LED 设备互斥访问)--- Ubuntu20.04互斥体实验

🎬 渡水无言:个人主页渡水无言 ❄专栏传送门: 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》 ❄专栏传送门: 《freertos专栏》《STM32 HAL库专栏》 ⭐️流水不争先,争的是滔滔不绝  📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生 | 省级优秀毕业生获得者 | ZEEKLOG新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生 在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连 目录 前言  一、实验基础说明 1.1、互斥体简介 1.2 本次实验设计思路 二、硬件原理分析(看过之前博客的可以忽略) 三、实验程序编写 3.1 互斥体 LED 驱动代码(mutex.c) 3.2.1、设备结构体定义(28-39

By Ne0inhk
Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

Flutter for OpenHarmony:swagger_dart_code_generator 接口代码自动化生成的救星(OpenAPI/Swagger) 深度解析与鸿蒙适配指南

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 后端工程师扔给你一个 Swagger (OpenAPI) 文档地址,你会怎么做? 1. 对着文档,手写 Dart Model 类(容易写错字段类型)。 2. 手写 Retrofit/Dio 的 API 接口定义(容易拼错 URL)。 3. 当后端修改了字段名,你对着报错修半天。 这是重复劳动的地狱。 swagger_dart_code_generator 可以将 Swagger (JSON/YAML) 文件直接转换为高质量的 Dart 代码,包括: * Model 类:支持 json_serializable,带 fromJson/

By Ne0inhk
Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

Linux 开发别再卡壳!makefile/git/gdb 全流程实操 + 作业解析,新手看完直接用----《Hello Linux!》(5)

文章目录 * 前言 * make/makefile * 文件的三个时间 * Linux第一个小程序-进度条 * 回车和换行 * 缓冲区 * 程序的代码展示 * git指令 * 关于gitee * Linux调试器-gdb使用 * 作业部分 前言 做 Linux 开发时,你是不是也遇到过这些 “卡脖子” 时刻?写 makefile 时,明明语法没错却报错,最后发现是依赖方法行没加 Tab;想提交代码到 gitee,记不清 git add/commit/push 的 “三板斧”,还得反复搜教程;用 gdb 调试程序,输了命令没反应,才想起编译时没加-g生成 debug 版本;甚至连写个进度条,都搞不懂\r和\n的区别,导致进度条乱跳…… 其实这些问题,

By Ne0inhk