函数(三)作用域之变量作用域、函数嵌套中局部函数作用域、默认值参数作用域

一、非嵌套函数下全局变量与局部变量的使用

x=200
>>> def f2():
...     print(x)
...
>>> f2()
200
x=200
>>> def f2():
...     x=100
...     print(x)
...
>>> f2()
100
在函数体外定义的变量,一定是全局变量,例如:
add = "http://c.biancheng.net/shell/"
def text():
    print("函数体内访问:",add)
text()
print('函数体外访问:',add)
运行结果为:
函数体内访问: http://c.biancheng.net/shell/
函数体外访问: http://c.biancheng.net/shell/

在函数体内定义全局变量。即使用 global 关键字对变量进行修饰后,该变量就会变为全局变量。例如:
def text():
    global add
    add= "http://c.biancheng.net/java/"
    print("函数体内访问:",add)
text()
print('函数体外访问:',add)
运行结果为:
函数体内访问: http://c.biancheng.net/java/
函数体外访问: http://c.biancheng.net/java/
>>> x=200
>>> def f2():
...     print(x)
...     x=100
...
>>> f2()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f2
UnboundLocalError: local variable 'x' referenced before assignment

x = 200
def fn():
    print(x)  报错!该步执行不了!
    x += 1    只要在该作用域内赋值定义('='),该作用域内的所有该变量都为局部变量!不管位置在哪
    print(x)
fn()
>>> x=200
>>> def f():
...     x           
...     x=x+1    x没定义
...     print(x)
...
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment


x=200
>>> def f():
...     x
...     print(x)
...
>>> f()
200

>>> x=200
>>> def f():
...     x=1
...     x=x+1
...     print(x)
...
>>> f()
2
>>> x
200

x = 200
def f():
    x = x + 1     报错! 赋值即定义,即 x = x + 1 (局部变量 = 局部变量 + 1)print(x)
fn()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment

>>> x=200
>>> def f():
...     t=x+1
...     print(t)
...
>>> f()
201

综上只要在函数内部在该作用域内赋值定义(’=’),该作用域内的所有该变量都是局部变量!不管赋值的位置在哪,因此在赋值之前必须对该变量进行声明,即使该变量和全局变量同名也需要再次声明
声明和全局变量同名的变量时若没有使用global关键字 该变量是局部变量 对该变量的修改不影响与其同名的全局变量的值
声明和全局变量同名的变量时若使用global关键字声明,则函数内部对该变量的修改就是对同名全局变量的修改

在使用 global 关键字修饰变量名时,不能直接给变量赋初值,否则会引发语法错误。


x = 100
def fn():
    global x  # 声明全局变量
    print(x)  # 100
    x += 1
    print(x)  # 101
fn()
print(x)      # 101

global 使用原则:
1> 外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的目的就是为了封装,尽量与外界隔离。
2> 如果函数需要使用外部全局变量,使用函数的形参定义,并在调用传实参解决。
3> 一句话:不用 global,学习它就是为了深入理解变量作用域,全局变量一般情况不推荐修改,一旦在作用域中使用 global 声明全局变量,那么相当于在对全局变量赋值定义。

二、函数嵌套下的作用域

理解函数也是对象:函数做参数

def bar():
	print('i am bar')
bar
bar()

运行结果
<function bar at 0x02F18DF8>
i am bar

不带括号得到函数对象bar的内存地址
带括号表示执行该函数对象



def foo(f):
	f()       函数体表示参数f需要能够执行()
foo(bar)      因此参数f可以是一个函数对象  
foo(str)      也可以是任何可以执行()的对象类型 如字符串str  注意str是关键字 表示字符串
foo(strs)     不可以是strs

运行结果
>>> foo(bar)
i am bar

>>> foo(str)   不报错因为str类型可以执行()
>>> str()
''

>>>foo(strs)
NameError: name 'strs' is not defined




def opt_seq(func,seq):         作为参数的函数func在参数列表只写函数名 不写该函数的参数
	r=[func(i) for i in seq]   调用时func的参数要有
	return r
opt_seq(abs,range(-5,5))       可以是任何内置函数或者自定义函数

运行结果
[5,4,3,2,1,0,1,2,3,4]

函数做参数的另一种写法就是嵌套函数

(1)局部函数的作用域

函数内部定义的函数叫做局部函数,和局部变量一样默认情况下局部函数只能在其所在函数的作用域内使用

>>> def outdef():
...     def indef():
...             print("indef")
...     indef()  在局部函数所在函数内调用该局部函数
...
>>> outdef()     外部调用局部函数所在的函数 达到间接调用局部函数的效果
indef
>>> indef()      不能在局部函数所在函数外直接调用该局部函数
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'indef' is not defined

要想在外部直接调用局部函数需要
将局部函数作为所在函数的返回值,外部调用局部函数所在的函数来接收作为返回值的局部函数,然后再通过()调用该返回值函数即可

闭包,又称闭包函数或者闭合函数,其实和前面讲的嵌套函数类似,不同之处在于,闭包中外部函数返回的不是一个具体的值,而是一个函数。一般情况下,返回的函数会赋值给一个变量,这个变量可以在后面被继续执行调用。

就如同全局函数返回其局部变量,就可以扩大该变量的作用域一样,
通过将局部函数作为所在函数的返回值,也可以扩大局部函数的使用范围。例如,修改上面程序为:
#全局函数
def outdef ():
    #局部函数
    def indef():
        print("调用局部函数")
    #调用局部函数
    return indef
#调用全局函数
new_indef = outdef()
调用全局函数中的局部函数
new_indef()


程序执行结果为:
调用局部函数
#闭包函数,其中 exponent 称为自由变量
def nth_power(exponent):
    def exponent_of(base):
        return base ** exponent
    return exponent_of # 返回值是 exponent_of 函数
    
square = nth_power(2) # 计算一个数的平方(幂次数为2)
cube = nth_power(3) # 计算一个数的立方(幂次数为3)


print(square(2))  # 计算 2 的平方 2**2
print(square(5))  # 计算 5 的平方 5**2

这样多次调用不用每次都传两个参数 至于要传一个底数base就行

print(cube(2)) # 计算 2 的立方    2**3



程序运行结果
4
25
8

局部函数的作用域可总结为:
如果所在函数没有返回局部函数,则局部函数的可用范围仅限于所在函数内部
如果所在函数将局部函数作为返回值,则局部函数的作用域就会扩大,既可以在所在函数内部使用,也可以在所在函数的作用域中使用

(2)变量的作用域

外层变量在内部作用域可见,内层作用域中如果定义了和外层相同的变量,相当于在当前函数作用域中重新定义了一个新的变量,该内层变量不能覆盖掉外部作用域中的变量

def outer():
    o = 65    # 局部变量、本地 local 变量、临时变量
    def inner():
        o = 97
        print('inner', o)   
    print('outer 1 ', o)    外层变量在内部作用域可见
    inner()
    print('outer 2 ', o)    内层变量并不能覆盖掉外部作用域中的变量
outer()   

运行结果    
outer 1  65    
inner 97    
outer 2  65

如果局部函数中定义有和所在函数中变量同名的变量,会发生“遮蔽”的问题

#全局函数
def outdef ():
    name = "所在函数中定义的 name 变量"
    #局部函数
    def indef():
        print(name)
        name = "局部函数中定义的 name 变量"
    indef()
#调用全局函数
outdef()

UnboundLocalError: local variable 'name' referenced before assignment
局部函数 indef() 中定义的 name 变量遮蔽了所在函数 outdef() 中定义的 name 变量。
再加上,indef() 函数中 name 变量的定义位于 print() 输出语句之后,导致 print(name) 语句在执行时找不到定义的 name 变量,因此程序报错。


>>> def outdef():
...     name="所在函数中定义的 name 变量"
...     def indef():
...             name="局部函数中定义的 name 变量"
...             print(name)
...     indef()
...
>>> outdef()
"局部函数中定义的 name 变量"



>>> def outdef():
...     name="out"
...     def indef():
...             print(name)    外层变量在内部作用域可见
...     indef()
...
>>> outdef()
out

>>> def outdef():
...     name="out"
...     def indef():
...             name+="123"   一旦有赋值就是局部变量
...     indef()
...
>>> outdef()
UnboundLocalError: local variable 'name' referenced before assignment

其次python中name也不可修改
>>> def outdef():
...     name="out"
...     def indef():
...             name.append("123")
...     indef()
...
>>> outdef()
AttributeError: 'str' object has no attribute 'append'


>>> def outdef():
...     x=10
...     def indef():
...             x+=1
...     indef()
...
>>> outdef()
UnboundLocalError: local variable 'x' referenced before assignment

由于这里的 name 变量也是局部变量(在outdef函数中定义的),因此globals关键字并不适用于解决此问题。这里可以使用 Python3 提供的 nonlocal 关键字
nonlocal将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中
引入两个概念:
自由变量:未在本地作用域中定义的变量,如定义在内层函数外的外层函数的作用域中的变量。
闭包:出现在嵌套函数中,指内层函数引用到了外层函数的自由变量,就形成了闭包

闭包:indef通过nonlocal引用到了outdef中的name
#全局函数
def outdef ():
    name = "所在函数中定义的 name 变量"
    #局部函数
    def indef():
        nonlocal name
        print(name)
        #修改name变量的值
        name = "局部函数中定义的 name 变量"
    indef()
#调用全局函数
outdef()


程序执行结果为:
所在函数中定义的 name 变量
python 2 实现闭包  使用可变类型列表等
def counter():
    c = [0]
    def inc():
        c[0] += 1  # 是赋值即定义嘛?不是!是修改值
        return c[0]
    return inc     # 返回标识符,即函数对象

m = counter()
m()                # 调用函数 inc(),但是 c 消亡了嘛?没有,内层函数没有消亡,c 不消亡(闭包)
m()
m()
print(m())

运行结果 4
 推荐使用 nonlocal,python 3 实现闭包
def counter():
    c = 0
    def inc():
        nonlocal c    # 非当前函数的本地变量,当前函数之外的任意层函数的变量,绝非 global
        c += 1        # 是闭包吗?是!
        return c
    return inc

m = counter()
m()
m()
m()
print(m())

运行结果 4

三、默认值参数的作用域

函数也是对象,每个函数定义被执行后就生成了一个函数对象和函数名这个标识符关联。python 把函数默认值放在函数对象的属性中,该属性伴随着该函数对象的整个生命周期,查看 _defaults _ 属性它是个元组, _kwdefaults _是一个字典

不可变默认值参数类型(int string等)在函数内修改该参数 下次再调用该函数 默认值仍然不变
可变默认值参数类型(list等)在函数内修改该参数 下次再调用该函数 默认值是修改后的值

def foo(x=1):
    x += 1
    print(x)
foo()   
foo() 
  
运行结果
2
2
第二次执行x仍然等于2说明第二次进入函数时x值为1 

def bar(x=[]):    # x = [],引用类型
    x.append(1)   # [1]
    print(x)
bar()   
bar()    

运行结果
[1]
[1,1]


def foo(x, m=123, n='abc'):
    m=456
    n='def'
    print(x)
print(foo.__defaults__)    
foo('yang')
print(foo.__defaults__)   

运行结果
(123, 'abc')
'yang'
(123, 'abc')



def foo(x, m=123, *, n='abc', t=[1,2]):
    m=456
    n='def'
    t.append(12)
    #t[:].append(12)    # t[:],全新复制一个列表,避免引用计数
    print(x, m, n, t)

print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2]}
foo('yang')
print(foo.__defaults__, foo.__kwdefaults__) #(123,) {'n': 'abc', 't': [1, 2, 12]}

列表的 + 和 += 的区别:
+表示两个列表合并并返回一个全新的列表。
+= 表示,就地修改前一个列表,在其后追加后一个列表,就是 extend 方法

def x(a=[]):
    a = a + [5]          加法的本质:返回新列表、新地址;赋值即定义
print(x.__defaults__)    ([],)
x()
x()
print(x.__defaults__)    ([],)

def y(a=[]):
    a += [5]             += 即 extend => a.extend([5])
print(y.__defaults__)    ([],)
y()
y()
print(y.__defaults__)    ([5, 5],)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值