Files
2022-07-13 17:54:50 +08:00

261 lines
9.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Python 变量作用域
keywords: Python, 作用域,
desc: Python作用域说明
date: 2022-07-13
tags: Python
---
这里说明一下 Python 变量作用域,帮助大家更好地使用 Python [原文链接](https://www.cnblogs.com/Jolly-hu/p/12228320.html)
<!-- more -->
## 作用域简介
**作用域指的是变量的有效范围**。变量并不是在任何位置都可以访问的,访问权限取决于这个变量是在哪里赋值的,也就是在哪个作用域内的。
通常而言,在编程语言中,变量的作用域从代码结构形式来看,有块级、函数、类、模块、包等由小到大的级别。但是在 Python 中,没有块级作用域,也就是类似 if语句块、for语句块、with上下文管理器 等等是不存在作用域概念的,他们等同于普通的语句
```python
if True: # if语句块没有作用域
x = 1
print(x)
# 1
def func(): # 函数有作用域
a = 8
print(a)
# Traceback (most recent call last):
# File "<pyshell#3>", line 1, in <module>
# a
# NameError: name 'a' is not defined
```
上面的代码可以试着运行一下,然后发现在 if 语句内定义的变量 x可以被外部访问而在函数 func() 中定义的变量 a则无法在外部访问。
通常,函数内部的变量无法被函数外部访问,但内部可以访问;类内部的变量无法被外部访问,但类的内部可以。通俗来讲,就是内部代码可以访问外部变量,而外部代码通常无法访问内部变量。
变量的作用域决定了程序的哪一部分可以访问哪个特定的变量名称。
Python 的作用域一共有4层分别是
- L Local 局部作用域
- E Enclosing 闭包函数外的函数中
- G Global 全局作用域
- B Built-in 内建作用域
```python
global_var = 0 # 全局作用域
def outer():
out_var = 1 # 闭包函数外的函数中
def inner():
inner_var = 2 # 局部作用域
```
前面说的都是变量可以找得到的情况,那如果出现本身作用域没有定义的变量,那该如何寻找呢?
Python 以 L > E > G >B 的规则查找变量,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,最后去内建中找。
如果这样还找不到,那就提示变量不存在的错误。例如下面的代码,函数 func 内部并没有定义变量 a可是 print 函数需要打印 a那怎么办
向外部寻找!按照 L > E > G >B 的规则,层层查询,这个例子很快就从外层查找到了 a并且知道它被赋值为 1于是就打印了 1。
```python
a = 1
def func():
print(a)
```
## 全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,被叫做局部变量,定义在函数外的拥有全局作用域的变量,被称为全局变量。(类、模块等同理)
所谓的局部变量是相对的。局部变量也有可能是更小范围内的变量的外部变量。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
```python
a = 1 # 全局变量
def func():
b = 2 # 局部变量
print(a) # 可访问全局变量a,无法访问它内部的c
def inner():
c = 3 # 更局部的变量
print(a) # 可以访问全局变量a
print(b) # b对于inner函数来说就是外部变量
print(c)
```
## global 和 nonlocal 关键字
我们先看下面的例子:
```python
total = 0 # total是一个全局变量
def plus( arg1, arg2 ):
total = arg1 + arg2 # total在这里是局部变量.
print("函数内局部变量total= ", total)
print("函数内的total的内存地址是: ", id(total))
return total
plus(10, 20)
print("函数外部全局变量total= ", total)
print("函数外的total的内存地址是: ", id(total))
```
很明显,函数 plus 内部通过 total = arg1 + arg2 语句,新建了一个局部变量 total它和外面的全局变量 total 是两码事。而如果我们想要在函数内部修改外面的全局变量 total 呢?使用 global 关键字!
> global指定当前变量使用外部的全局变量
```python
global指定当前变量使用外部的全局变量
total = 0 # total是一个全局变量
def plus( arg1, arg2 ):
global total # 使用global关键字申明此处的total引用外部的total
total = arg1 + arg2
print("函数内局部变量total= ", total)
print("函数内的total的内存地址是: ", id(total))
return total
plus(10, 20)
print("函数外部全局变量total= ", total)
print("函数外的total的内存地址是:
```
所运行结果:
```python
函数内局部变量total= 30
函数内的total的内存地址是: 503494624
函数外部全局变量total= 30
函数外的total的内存地址是: 503494624
```
我们再来看下面的例子:
```python
a = 1
print("函数outer调用之前全局变量a的内存地址 ", id(a))
def outer():
a = 2
print("函数outer调用之时闭包外部的变量a的内存地址 ", id(a))
def inner():
a = 3
print("函数inner调用之后闭包内部变量a的内存地址 ", id(a))
inner()
print("函数inner调用之后闭包外部的变量a的内存地址 ", id(a))
outer()
print("函数outer执行完毕全局变量a的内存地址 ", id(a))
```
如果你将前面的知识点都理解通透了,那么这里应该没什么问题,三个 a 各是各的 a各自有不同的内存地址是三个不同的变量。
打印结果也很好的证明了这点:
```python
函数outer调用之前全局变量a的内存地址 493204544
函数outer调用之时闭包外部的变量a的内存地址 493204576
函数inner调用之后闭包内部变量a的内存地址 493204608
函数inner调用之后闭包外部的变量a的内存地址 493204576
函数outer执行完毕全局变量a的内存地址 493204544
```
那么如果inner 内部想使用 outer 里面的那个 a而不是全局变量的那个 a怎么办用 global 关键字?先试试看吧:
```python
a = 1
print("函数outer调用之前全局变量a的内存地址 ", id(a))
def outer():
a = 2
print("函数outer调用之时闭包外部的变量a的内存地址 ", id(a))
def inner():
global a # 注意这行
a = 3
print("函数inner调用之后闭包内部变量a的内存地址 ", id(a))
inner()
print("函数inner调用之后闭包外部的变量a的内存地址 ", id(a))
outer()
print("函数outer执行完毕全局变量a的内存地址 ", id(a))
```
运行结果如下很明显global使用的是全局变量a。
```python
函数outer调用之前全局变量a的内存地址 494384192
函数outer调用之时闭包外部的变量a的内存地址 494384224
函数inner调用之后闭包内部变量a的内存地址 494384256
函数inner调用之后闭包外部的变量a的内存地址 494384224
函数outer执行完毕全局变量a的内存地址 494384256
```
那怎么办呢?使用 nonlocal 关键字它可以修改嵌套作用域enclosing 作用域,外层非全局作用域)中的变量。将 global a 改成 nonlocal a 后运行,代码这里我就不重复贴了,
运行后查看结果,可以看到我们真的引用了 outer 函数的 a 变量。
```python
函数outer调用之前全局变量a的内存地址 497726528
函数outer调用之时闭包外部的变量a的内存地址 497726560
函数inner调用之后闭包内部变量a的内存地址 497726592
函数inner调用之后闭包外部的变量a的内存地址 497726592
函数outer执行完毕全局变量a的内存地址 497726528
```
## 面试真题
不要上机测试,请说出下面代码的运行结果:
```python
a = 10
def test():
a += 1
print(a)
test()
```
很多同学会说,这太简单了!函数内部没有定义 a那么就去外部找找到 a=10于是加 1打印 11
我会告诉你这段代码有语法错误吗a += 1 相当于 a = a + 1按照赋值运算符的规则是先计算右边的 a+1。但是Python 的规则是,如果在函数内部要修改一个变量,那么这个变量需要是内部变量,除非你用 global 声明了它是外部变量。很明显,我们没有在函数内部定义变量 a所以会弹出局部变量在未定义之前就引用的错误。
## 更多的例子
再来看一些例子(要注意其中的闭包,也就是函数内部封装了函数):
```python
name = 'jack'
def outer():
name='tom'
def inner():
name ='mary'
print(name)
inner()
outer()
```
上面的题目很简单因为inner函数本身有name变量所以打印结果是mary。那么下面这个呢
```python
name ='jack'
def f1():
print(name)
def f2():
name = 'eric'
f1()
f2()
```
这题有点迷惑性想了半天应该是eric因为 f2 函数调用的时候,在内部又调用了 f1 函数f1 自己没有 name 变量,那么就往外找,发现 f2 定义了个 name于是就打印这个 name。错了结果是jack
**Python函数的作用域取决于其函数代码块在整体代码中的位置而不是调用时机的位置**。调用 f1 的时候,会去 f1 函数的定义体查找,对于 f1 函数,它的外部是 name ='jack',而不是 name = 'eric'。
再看下面的例子f2 函数返回了 f1 函数:
```python
name = 'jack'
def f2():
name = 'eric'
return f1
def f1():
print(name)
ret = f2()
ret()
```
仔细回想前面的例子其实这里有异曲同工之妙所以结果还是jack