python变量与参数
命名空间与作用域
命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
一般有三种命名空间:
- 内置名称(built-in names),Python 语言内置的名称,比如函数名
abs
、chr
和异常名称BaseException
、Exception
等等。 - 全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。
- 局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)
命名空间查找顺序:
假设我们要使用变量 LintCode
,则 Python 的查找顺序为:局部的命名空间 -> 全局命名空间 -> 内置命名空间。
如果找不到变量 LintCode
,它将放弃查找并引发一个 NameError
异常:NameError: name 'LintCode' is not defined。
命名空间的生命周期:
命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。
因此,我们无法从外部命名空间访问内部命名空间的对象。
print(chr) # 访问内置名称 chr
chr = 'chr' # chr 是全局名称
print(chr) # 访问全局名称 chr
def outer_function():
chr = 'char'
print(chr) # 访问局部名称 chr
outer_var = 'out_var'
print(outer_var)
# 无法从外部命名空间访问内部命名空间的对象
# print(inner_var)
def inner_function():
inner_var = 'inner_var'
# 可以从内部命名空间访问外部命名空间的对象
print(outer_var)
print(inner_var)
inner_function()
outer_function()
作用域
A scope is a textual region of a Python program where a namespace is directly accessible.
作用域是一个代码区域,是一个命名空间可以直接引用的区域。
在一个 Python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。
Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。
变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python 的作用域一共有4种,分别是:
- L(Local):最内层,包含局部变量,比如一个函数/方法内部。
- E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类)
A
里面又包含了一个函数B
,那么对于B
中的名称来说A
中的作用域就为 non-local。 - G(Global):当前脚本的最外层,比如当前模块的全局变量。
- B(Built-in): 包含了内建的变量/关键字等,最后被搜索。
规则顺序: L –> E –> G –> B。
在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。
global_count = 0 # 全局作用域
def outer():
outer_count = 1 # 闭包函数外的函数中
def inner():
inner_count = 2 # 局部作用域
内置作用域是通过一个名为 builtin
的标准模块来实现的,但是这个变量名自身并没有放入内置作用域内,所以必须导入这个文件才能够使用它。在 Python 3 中,可以使用以下的代码来查看到底预定义了哪些变量:
>>> import builtins
>>> dir(builtins)
Python 中只有模块(module
),类(class
)以及函数(def
、lambda
)才会引入新的作用域,其它的代码块(如 if
/elif
/else
/、try
/except
、for
/while
等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下代码:
>>> if True:
... msg = 'I am from LintCode'
...
>>> msg
'I am from LintCode'
>>>
实例中 msg
变量定义在 if
语句块中,但外部还是可以访问的。
如果将 msg
定义在函数中,则它就是局部变量,外部不能访问:
>>> def test():
... msg_inner = 'I am from LintCode'
...
>>> msg_inner
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'msg_inner' is not defined
>>>
从报错的信息上看,说明了 msg_inner
未定义,无法使用,因为它是局部变量,只有在函数内可以使用。
局部变量与全局变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。如下实例:
#!/usr/bin/python3
total = 0 # 这是一个全局变量
# 可写函数说明
def sum(arg1, arg2):
#返回2个参数的和.
total = arg1 + arg2 # total在这里是局部变量.
print ("函数内是局部变量 : ", total)
return total
# 调用sum函数
sum(10, 20)
print("函数外是全局变量 : ", total)
global
和 nonlocal
关键字
当内部作用域想修改外部作用域的变量时,就要用到 global
和 nonlocal
关键字了。
global
关键字修饰变量后标识该变量是全局变量,对该变量进行修改就是修改全局变量。
global
关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global
修饰后也可以直接使用
num = 1
def function():
global num # 需要使用 global 关键字声明
print(num)
num = 123
print(num)
function()
print(num)
如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal
关键字了。
nonlocal
关键字修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal
位置会发生错误(最上层的函数使用 nonlocal
修饰变量必定会报错)。
def outer():
num = 10
def inner():
nonlocal num # nonlocal关键字声明
num = 100
print(num)
inner()
print(num)
outer()
可变参数传递——在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。
不使用可变参数的传统方式
我们以数学题为例子,给定一组数字 a,b … z ,请计算 sum = a * a + b * b + .....+ z * z
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a, b, …, z作为一个 list 或 tuple 传进来,这样,函数可以定义如下:
def cout(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
但是调用的时候,需要先组装出一个 list 或 tuple
使用列表传递参数:
def cout(numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
print(cout([1, 2, 3]))
使用可变参数
如果利用可变参数,调用函数的方式可以简化成这样:
def cout(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
print(cout(1, 2, 3))
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个 * 号。在函数内部,参数 numbers 接收到的是一个 tuple,因此,函数代码完全不变。
但是,调用该函数时,可以传入任意个参数,包括0个参数。
不可变参数传递
请看示例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函数 person
除了必选参数 name
和 age
外,还接受关键字参数 **kw
。在调用该函数时,可以只传入必选参数:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Michael', 30)
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Bob', 35, city='Beijing')
#name: Bob age: 35 other: {'city': 'Beijing'}
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Adam', 45, gender='M', job='Engineer')
#name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
关键字参数有什么用?它可以扩展函数的功能。比如,在 person
函数里,我们保证能接收到 name
和 age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个 dict
,然后,把该 dict
转换为关键字参数传进去:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, city=extra['city'], job=extra['job'])
#name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
简化版:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra)
#name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
请你设计一个函数 print_avg
,这个函数接收多个关键字参数作为学生的信息,接收多个数字参数作为这个学生多次考试的成绩,请从学生信息中提取出学生的 student_name
,student_age
,然后求出这个学生多次考试的平均成绩 Average
(保留两位小数),返回一个字符串,格式如下:
name: student_name, age: student_age, avg: Average # Please write your code here def print_avg(*args,**kwargs): name=kwargs['student_name'] age=kwargs['student_age'] avg=sum(args)/len(args) return f'name: {name}, age: {age}, avg: {avg:.2f}' #return 'name : {name}, age : {age} ,avg :{avg}'.format(name,age,avg)
默认参数
def 函数名(...,形参名,形参名=默认值): 代码块
注意,在使用此格式定义函数时,指定有默认值的形式参数必须在所有没默认值参数的最后,否则会产生语法错误。
为参数指定默认值是非常有用的方式。调用函数时,可以使用比定义时更少的参数,例如:
def ask_ok(prompt, retries=4, reminder='Please try again!'): while True: ok = input(prompt) if ok in ('y', 'ye', 'yes'): return True if ok in ('n', 'no', 'nop', 'nope'): return False retries = retries - 1 if 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!')
本例还使用了关键字 [object Object],用于确认序列中是否包含某个值。
默认值在 定义 作用域里的函数定义中求值,所以:
重要警告: 默认值只计算一次。默认值为列表、字典或类实例等可变对象时,会产生与该规则不同的结果。例如,下面的函数会累积后续调用时传递的参数:
def f(a, L=[]): L.append(a) return L print(f(1)) #[1] print(f(2)) #[1,2] print(f(3)) #[1,2,3]
关键字参数与特殊参数
关键字参数
kwarg=value
形式的 关键字参数 也可以用于调用函数。函数示例如下:
def parrot(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 positional argument parrot(voltage=1000) # 1 keyword argument parrot(voltage=1000000, action='VOOOOOM') # 2 keyword arguments parrot(action='VOOOOOM', voltage=1000000) # 2 keyword arguments parrot('a million', 'bereft of life', 'jump') # 3 positional arguments parrot('a thousand', state='pushing up the daisies') # 1 positional, 1 keyword
以下调用函数的方式都无效:
parrot() # required argument missing parrot(voltage=5.0, 'dead') # non-keyword argument after a keyword argument parrot(110, voltage=220) # duplicate value for the same argument parrot(actor='John Cleese') # unknown keyword argument
函数调用时,关键字参数必须跟在位置参数后面。所有传递的关键字参数都必须匹配一个函数接受的参数(比如,actor
不是函数 parrot
的有效参数),
关键字参数的顺序并不重要。这也包括必选参数,(比如,parrot(voltage=1000)
也有效)。不能对同一个参数多次赋值。
最后一个形参为 **name
形式时,接收一个字典,该字典包含与函数中已定义形参对应之外的所有关键字参数。
**name
形参可以与 *name
形参(下一小节介绍)组合使用(*name
必须在 **name
前面), *name
形参接收一个元组,该元组包含形参列表之外的位置参数。
例如,可以定义下面这样的函数:
def cheeseshop(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])
def cheeseshop(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]) cheeseshop("Limburger", "It's very runny, sir.", "It's really very, VERY runny, sir.", shopkeeper="Michael Palin", client="John Cleese", sketch="Cheese Shop Sketch")
特殊参数
默认情况下,参数可以按位置或显式关键字传递给 Python 函数。为了让代码易读、高效,最好限制参数的传递方式,这样,开发者只需查看函数定义,即可确定参数项是仅按位置、按位置或关键字,还是仅按关键字传递。
函数定义如下:
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2): ----------- ---------- ---------- | | | | Positional or keyword | | - Keyword only -- Positional only
/
和 *
是可选的。这些符号表明形参如何把参数值传递给函数:位置、位置或关键字、关键字。关键字形参也叫作命名形参。
1. 位置或关键字参数
函数定义中未使用 /
和 *
时,参数可以按位置或关键字传递给函数。
2. 仅位置参数
此处再介绍一些细节,特定形参可以标记为 仅限位置。仅限位置 时,形参的顺序很重要,且这些形参不能用关键字传递。仅限位置形参应放在 /
(正斜杠)前。/
用于在逻辑上分割仅限位置形参与其它形参。如果函数定义中没有 /
,则表示没有仅限位置形参。
/
后可以是 位置或关键字 或 仅限关键字 形参。
3. 仅限关键字参数
把形参标记为 仅限关键字,表明必须以关键字参数形式传递该形参,应在参数列表中第一个 仅限关键字 形参前添加 *
。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南