Python-函数作用域、chr、ord、iter、next、reversed、enumerate函数、global、nonlocal、闭包、迭代器、可迭代对象

1、作用域

1.1、什么是作用域

一个标识符的可见范围,这就是标识符的作用域。一般常说的是变量的作用域

1.2、示例

1.2.1、代码

def foo():
    x = 100
print(x) # 可以访问到吗

1.2.2、分析

上例中x不可以访问到,会抛出异常(NameError: name 'x' is not defined),原因在于函数是一个封装,它会开辟一个作用域,x变量被限制在这个作用域中,所以在函数外部x变量不可见。
注意:每一个函数都会开辟一个作用域

2、作用域分类

2.1、全局作用域

2.1.1、简介

在整个程序运行环境中都可见
全局作用域中的变量称为全局变量global

2.1.2、示例

x = 5 # 全局变量,也在函数外定义
def foo():
    print(x)
foo()

2.2、局部作用域

2.2.1、简介

在函数、类等内部可见
局部作用域中的变量称为局部变量,其使用范围不能超过其所在局部作用域
也称为本地作用域local

2.2.2、示例

def fn1():
    x = 1 # 局部作用域,x为局部变量,使用范围在fn1内
def fn2():
    print(x)  # 报错
print(x) # 报错
fn1()
fn2()

3、函数嵌套

3.1、什么是函数嵌套

在一个函数中定义了另外一个函数

3.2、示例

3.2.1、代码

def outer():
    def inner():
        print("inner")
    inner()
    print("outer")
outer() # 可以运行
inner() # 不可以,非全局函数

3.2.2、分析

内部函数inner不能在外部直接使用,会抛NameError异常,因为它在函数外部不可见。
其实,inner不过就是一个标识符,就是一个函数outer内部定义的变量而已。

3.3、嵌套结构的作用域

3.3.1、代码

def outer1():
    o = 65
    def inner():
        print('inner', o, chr(o))
    inner()
    print('outer', o, chr(o))
outer1() # 执行后,打印
# inner 65 A
# outer 65 A

def outer2():
    o = 65
    def inner():
        o = 97
        print('inner', o, chr(o))
    inner()
    print('outer', o, chr(o))
outer2() # 执行后,打印
# inner 97 a
# outer 65 A

3.3.2、分析

外层变量在内部作用域可见
内层作用域inner中,如果定义了 o = 97 ,相当于在当前函数inner作用域中重新定义了一个新的
变量o,但是,这个o并不能覆盖掉外部作用域outer2中的变量o。只不过对于inner函数来说,其
只能可见自己作用域中定义的变量o了

3.3.3、chr、ord函数介绍

chr chr(i) 通过unicode编码返回对应字符
ord ord(c) 获得字符对应的unicode

print(ord(''), hex(ord('')), ''.encode(), ''.encode('gbk'))
chr(20013) # '中'
chr(97)

4、一个赋值语句的问题

4.1、示例1:正常执行,函数外部的变量在函数内部可见

x=5
def foo():
    print(x)
foo()

4.2、示例2:全局变量赋值

4.2.1、代码

x=5
def foo():
    y=x+1 # 不会报错
    x+=1 # 报错 cannot access local variable 'x' where it is not associated with a value
    print(x)
foo()

4.2.2、原因分析

x += 1 其实是 x = x + 1
只要有"x="出现,这就是赋值语句。相当于在foo内部定义一个局部变量x,那么foo内部所有x都是这个局部变量x了
x = x + 1 相当于使用了局部变量x,但是这个x还没有完成赋值,就被右边拿来做加1操作了5、

5、global语句

5.1、global使用原则

外部作用域变量会在内部作用域可见,但也不要在这个内部的局部作用域中直接使用,因为函数的
目的就是为了封装,尽量与外界隔离
如果函数需要使用外部全局变量,请尽量使用函数的形参定义,并在调用传实参解决
一句话:不用global。学习它就是为了深入理解变量作用域

5.2、代码示例

5.2.1、代码

x = 5
def foo():
   global x # 全局变量
   x += 1
   print(x)
foo()

5.2.2、分析

使用global关键字的变量,将foo内的x声明为使用外部的全局作用域中定义的x
全局作用域中必须有x的定义

5.3、如果全局作用域中没有x定义会怎样?

5.3.1、报错

def foo():
    global x
    x += 1
    print(x)
foo()
# NameError: name 'x' is not defined

5.3.2、正常

def foo():
    global x
    x = 10
    x += 1
    print(x)
foo()
print(x) # 正常打印

5.4、总结

x+=1 这种是特殊形式产生的错误的原因?
先引用后赋值,而python动态语言是赋值才算定义,才能被引用。

解决办法:在这条语句前增加x
=0之类的赋值语句,或者使用global 告诉内部作用域,去全局作用域查找变量定义内部作用域使用 x = 10 之类的赋值语句会重新定义局部作用域使用的变量x,
但是,一旦这个作用域中使用
global 声明x为全局的,那么x=5相当于在为全局作用域的变量x赋值

6、闭包

6.1、什么是闭包

自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是JavaScript

6.2、闭包示例

6.2.1、代码

def counter():
    c = [0]
    def inc():
        c[0] += 1 # 报错吗? 为什么 # line 4
        return c[0]
    return inc
foo = counter() # line 8
print(foo(), foo()) # line 9
c = 100
print(foo()) # line 11

6.2.2、分析

第8行会执行counter函数并返回inc对应的函数对象,注意这个函数对象并不释放,因为有foo记着
第4行会报错吗?为什么
不会报错,c已经在counter函数中定义过了。而且inc中的使用方式是为c的元素修改值,而不
是重新定义c变量
第9行打印什么结果?
打印 1 2
第11行打印什么结果?
打印 3
第9行的c和counter中的c不一样,而inc引用的是自由变量正是counter中的变量c

6.3、闭包作用域问题

6.3.1、报错的示例

def counter():
    count = 0
    def inc():
        count += 1 # 引处会报错,因为作用域名没有提前定义好count变量
        return count
    return inc
foo = counter()
print(foo(), foo())

6.3.2、使用global解决

def counter():
    global count
    count = 0
    def inc():
        global count
        count += 1
        return count
    return inc
foo = counter()
print(foo(), foo())
print(foo())

6.3.3、Python3中可以使用nonlocal关键字

def counter():
    count = 0
    def inc():
        nonlocal count
        count += 1
        return count
    return inc
foo = counter()
print(foo(), foo())
print(foo())

7、nonlocal语句

7.1、作用

Python3中可以使用nonlocal关键字。nonlocal:将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中定义。

7.2、示例

内部函数使用nonlocal关键字声明count变量在上级作用域而非本地作用域中定义。
代码中内层函数引用外部局部作用域中的自由变量,形成闭包。

# 可以参考:6.3.3

8、函数的销毁

定义一个函数就是生成一个函数对象,函数名指向的就是函数对象。
可以使用del语句删除函数,使其引用计数减1。
可以使用同名标识符覆盖原有定义,本质上也是使其引用计数减1。
Python程序结束时,所有对象销毁。
函数也是对象,也不例外,是否销毁,还是看引用计数是否减为0。

9、变量名解析原则LEGB

9.1、什么是LEGB

Local,本地作用域、局部作用域的local命名空间。函数调用时创建,调用结束消亡
Enclosing,Python2.2时引入了嵌套函数,实现了闭包,这个就是嵌套函数的外部函数的命名空间
Global,全局作用域,即一个模块的命名空间。模块被import时创建,解释器退出时消亡
Build-in,内置模块的命名空间,生命周期从python解释器启动时创建到解释器退出时消亡。例如 print(open),print和open都是内置的变量,所以一个名词的查找顺序就是LEGB

9.2、内置、全局、上层函数的本地作用域名、本地函数关系图

10、iter、next、reversed、enumerate函数

10.1、iter

iter(iterable) 把一个可迭代对象包装成迭代器

10.2、next

next(iterable[, default]) 取迭代器下一个元素,如果已经取完,继续取抛StopIteration异常

10.3、reversed

reversed(seq) 返回一个翻转元素的迭代器

10.4、enumerate

enumerate(seq, start=0) 迭代一个可迭代对象,返回一个迭代器,每一个元素都是数字和元素构成的二元组

10.5、迭代器

特殊的对象,一定是可迭代对象,具备可迭代对象的特征
通过iter方法把一个可迭代对象封装成迭代器
通过next方法,获取 迭代器对象的一个元素
生成器对象,就是迭代器对象。但是迭代器对象未必是生成器对象

10.6、可迭代对象

能够通过迭代一次次返回不同的元素的对象
所谓相同,不是指值是否相同,而是元素在容器中是否是同一个,例如列表中值可以重复的,
['a', 'a'],虽然这个列表有2个元素,值一样,但是两个'a'是不同的元素
可以迭代,但是未必有序,未必可索引
可迭代对象有:list、tuple、string、bytes、bytearray、range、set、dict、生成器、迭代器等
可以使用成员操作符in、not in
对于线性数据结构,in本质上是在遍历对象,时间复杂度为O(n)

10.7、示例

lst = [1, 3, 5, 7, 9]
it = iter(lst) # 返回一个迭代器对象
print(next(it))
print(next(it))
for i, x in enumerate(it, 2):
    print(i, x)
#print(next(it)) # StopIteration
print()
for x in reversed(lst):
    print(x)
# 比较下面的区别,说明原因?
it = iter(lst)
print(1 in it)
print(1 in it) # 第二次比较,已经是空列表
print(1 in lst)
print(1 in lst)
posted @ 2023-06-28 15:33  小粉优化大师  阅读(56)  评论(0)    收藏  举报