15 函数参数补充 命名空间和作用域详解

函数参数补充 名称空间详解

1 函数参数补充

1.1 命名关键字参数(形参)

函数定义阶段,在 * 后面的形式参数。

def func(x, y, *, kw1, kw2):
    print(kw1, kw2)
    
def func(x, y, *args, kw1, kw2):  # 可以用args接受多余的位置实参
    print(kw1, kw2)

其中形参a和b被称为命名关键字参数。
特点:

  1. 函数调用阶段,必须按照关键字实参的形式(key=value)为命名关键字参数传值。
def func(x, y, *, kw1, kw2):
    print(kw1, kw2)

func(1, 2, kw2=1, kw1=2)  # 2, 1
  1. 可以为命名关键字参数赋予默认值,但不是默认值参数,不遵守默认值参数的限制。
def func(x, y, *, kw1, kw2='default'):
    print(kw1, kw2)

func(1, 2, kw1=2)  # 2, default

def func(x, y, *, kw1='default', kw2):  # 不会报错
    print(kw1, kw2)

func(1, 2, kw2=2)  # default, 2
1.2 函数参数组合使用
1.2.1 形参组合使用

前:位置形参,
中:默认值形参,*args / *,
后:命名关键字形参
最后:**kwargs

def func(x, y='default y', *args, kw1='default kw1', kw2,  **kwargs):
    print(x, y)
    print(args)
    print(kw1, kw2)
    print(kwargs)

func(1, 2, 3, kw2=4, z=5, w=6)
# 1 2
# (3,)
# default kw1 4
# {'z': 5, 'w': 6}

def func(x, *args, y='default y', kw1, kw2='default kw2',  **kwargs):
    print(x, y)
    print(args)
    print(kw1, kw2)
    print(kwargs)

func(1, 2, 3, kw1=4, z=5, w=6)
# 1 default y
# (2, 3)
# 4 default kw2
# {'z': 5, 'w': 6}
1.2.2 实参组合使用

前:位置实参,* + 可迭代对象
后:关键字实参,** + 字典

def func(x, y, z, w, a, b, c, d):
    print(x, y, z, w, a, b, c, d)

func(1, *[2, 3, 4], a=5, b=6, **{'c': 7, 'd': 8})
func(*[2, 3, 4], 1, a=5,  **{'c': 7, 'd': 8}, b=6)

2 名称空间

A namespace is a mapping from names to objects.
Most namespaces are currently implemented as Python dictionaries。

名称空间(Namespace)是从名称到对象的映射,目前大部分名称空间都是通过 Python 的字典来实现的。
名称空间可以简单理解为存放名称的区域,是对栈区的进一步划分。
名称空间提供了可以在栈区中存放相同名字而不互补影响的一种机制。

2.1 名称空间分类

名称空间分为内置名称空间全局名称空间局部名称空间
名称空间提供了一种避免名称冲突的方法。即各个名称空间是独立的,在不同的名称空间中变量是可以重名而没有任何影响的。

2.1.1 内置名称(built-in names)空间

存放了python解释器内置的名字。
存活周期: 内置名称空间在python解释器启动时产生,在关闭时销毁。

2.1.2 全局名称(global names)空间

存放了运行顶级代码时产生的名字。

位于全局名称空间的名字可以理解为
既不是解释器内置的名字,也不是函数内定义的名字。

存活周期: 全局名称空间在python文件运行时产生,在运行完毕时销毁。

2.1.3 局部名称(local names)空间

存放了函数内定义的名称。
在调用函数时,运行函数体代码的过程中产生的名字。
存活周期: 局部名称空间在调用函数时产生,在调用结束时销毁。

2.1.4 对比
  1. 数量
    内置名称空间和全局名称空间只能存在一个,
    局部名称空间可以存在多个。
    调用一个函数就产生一个局部名称空间。
    同一个函数调用多次会产生多个不同的局部名称空间。
  2. 存在
    即使运行一个空文件,内置名称空间和全局名称空间也必须创建,但局部名称空间可以不创建。
  3. 顺序
    加载顺序
    内置名称空间 => 全局名称空间 => 局部名称空间
    销毁顺序
    局部名称空间 => 全局名称空间 => 内置名称空间
2.2 优先级

在当前所在位置逐层往上找。
局部名称空间 => 全局名称空间 => 内置名称空间
注意,名称空间只有优先级之分,并无嵌套关系。

  1. 在python解释器执行语句前,会检查语法并会为变量划分进不同的名称空间;
  2. 对于函数,名称空间划分变量名以及如何查找名字是在函数定义阶段确定好的;
  3. 名称空间只保存变量名,并不关心变量是否定义,即不关心是否保存内存地址;
  4. 名称空间的优先级决定了名字的查找顺序。当调用变量时从当前位置逐层往外找变量定义。
print(input)  # built-in function input
input = 123
print(input)  # 123

例子1

a = 1
def func1():
    print(a)
a = 2
func1()  # 2
a = 3

上面的例子中,解释器在执行前根据函数func1()的定义寻找变量a,结果变量a处于全局名称空间,在执行函数func1()时,变量a已经被赋值为2,因此输出2。

例子2 名称空间的嵌套关系
对于函数,名称空间的嵌套关系是以函数定义阶段为准,与调用阶段无关。

x = 1
def inner():
    print(x)
def outer():
    x = 2
    inner()
outer()  # 1 

例子2中,对于inner()函数,在定义阶段,其内部的变量名x被划分到全局名称空间。因此outer()调用inner()时打印的变量x是全局名称空间中的x。

2.3 函数嵌套定义

再次提醒:对于函数,名称空间的嵌套关系是以函数定义阶段为准,与调用阶段无关。

例子1

def outer():
    x = 1
    def inner():
        x = 2
        print(x)
    inner()

outer()  # 2

例子2

def outer():
    x = 1
    def inner():
        print(x)  # 注意这里与例子1的区别
        x = 2
    inner()

outer()  # 报错

例子2中,在函数inner()定义阶段,变量名x被划分到inner()的局部名称空间中,
调用时,运行到语句print(x)时,inner()的局部名称空间中的变量x还没有定义,因此报错。

例子3

def outer():
    x = 1
    def inner():
        print(x)
    x = 2
    inner()

outer()  # 2

例子3中,在函数inner()定义阶段,变量名x被划分到outer()的局部名称空间中,
调用时,函数outer()调用函数inner()时,outer()的局部名称空间中的变量x=2。

3 作用域

A scope is a textual region of a Python program where a namespace is directly accessible.
“Directly accessible” here means that an unqualified reference to a name attempts to find the name in the namespace.

作用域是一个 Python 程序可以直接访问名称空间的区域。
所谓「直接」,这里指的是只要给出名称就能独立地没有附加条件地找到命名空间中的对应的名称。

作用域可以理解为作用范围,根据可以访问的名称空间来划分区域。
访问一个变量,会从内到外依次访问所有的作用域直到找到,否则报错。

内置名称空间 / 全局名称空间 => 全局作用域
局部名称空间 => 局部作用域

3.1 全局作用域

可以直接访问 内置名称空间 / 全局名称空间 的区域。
特点:

  1. 全局存活;
  2. 全局有效,被所有函数共享。
    例如,input() 函数处于内置名称空间,位于全局作用域中,一般可以被任意函数独立地使用。
3.2 局部作用域
  1. 临时存活;
  2. 局部有效,仅函数内有效。
3.3 四种作用域:LEGB
  1. L(Local)
    最内层,包含局部变量,比如一个函数内部。
  2. E(Enclosing)
    外部嵌套函数的作用域
    包含了非局部(non-local)也非全局(non-global)的变量。
    比如两个嵌套函数,一个函数 outer() 里面又包含了一个函数 inner() ,那么对于 inner() 中的名称来说 outer() 中的作用域就为 nonlocal。
  3. G(Global)
    当前文件的最外层,例如当前模块的全局变量。
  4. B(Built-in)
    包含了内建的变量/关键字等,最后被搜索。

查询顺序: L –> E –> G –> B

# G
func1():
	# E
	func2():
		# E
		func3():
			# E
			func4():
				# L
				pass

在局部L找不到,便会去局部外的局部E找,再找不到就会去全局G找,最后去内置I中找。

3.4 关键字 global 和 nonlocal
3.4.1 关键字 global

关键字 global 使函数中的局部变量升级为全局变量。
如果需要在局部作用域中修改全局作用域中的不可变类型的变量,需要在局部作用域中将待修改的变量用关键字global升级为全局变量。

此时关键字global修饰的变量为不可变数据类型。
对于可变数据类型,可以直接通过修改(不是重新定义)局部作用域中的变量来修改全局作用域中的变量。

x = 1
def func():
	global x
	x = 2	
	
func()
print(x)  # 2
x = [1, 2, 3]
def func():
	x.append(4)	
	
func()
print(x)  # [1, 2, 3, 4]

在变量定义后再将其声明为全局变量会报错。
变量未定义直接声明为全局变量不会报错。

def func1():
    x = 1
    global x  # 报错
    
def func2():
    global x
func2()
3.4.2 关键字 nonlocal

先举个使用关键字global的例子。

x = 0
def func1():
    x = 1
    def func2():
        x = 2
        def func3():
            x = 3
            def func4():
                global x
                x = 4
            func4()
            print(x)  # 3
        func3()
        print(x)  # 2
    func2()
    print(x)  # 1
func1()
print(x)  # 4

关键字 global 通过声明在局部作用域中修改全局作用域中的不可变类型的变量。
但是不会影响外部嵌套函数的作用域中的同名变量。

关键字 nonlocal 修饰变量后标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量定义,就逐层往上找。如果到达最外层函数后依旧没有找到会报错。因此,最外层的函数使用nonlocal修饰变量必定会报错。

x = 0
def func1():
    x = 1
    def func2():
        x = 2
        def func3():
            x = 3  # 最上一级函数找到了变量x定义
            def func4():
                nonlocal x
                x = 4
            func4()
            print(x)  # 4
        func3()
        print(x)  # 2
    func2()
    print(x)  # 1
func1()
print(x)  # 0
x = 0
def func1():
    x = 1  # 最外层函数找到了变量x
    def func2():
        def func3():
            def func4():
                nonlocal x
                x = 4
            func4()
            print(x)  # 4
        func3()
        print(x)  # 4
    func2()
    print(x)  # 4
func1()
print(x)  # 0

使用方法对比:
global 关键字可以用于任何地方,包括最外层函数中和嵌套函数中,即使之前未定义该变量,global 关键字修饰后也可以直接使用;
nonlocal 关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会发生错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值