Python系列(7)- Python 命名空间与作用域、输入与输出
1. 命名空间与作用域
Python 中的 命名空间 (Namespace) 和作用域是密切相关的概念。Python 命名空间 (Namespace) 可以视为一个字典,其中键是变量名,值是与之关联的对象。各个命名空间是独立的,同一个命名空间中不能有重名(重名的以后一个为准),不同的命名空间是可以重名。
命名空间类似于电脑的文件系统,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。
一般有三种命名空间:
内置名称 (built-in names): Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等;
全局名称 (global names): 模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量;
局部名称 (local names): 函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。
命名空间查找顺序: 局部的命名空间 -> 全局命名空间 -> 内置命名空间。
命名空间的生命周期:命名空间的生命周期取决于对象的作用域,对象执行完成,则该命名空间的生命周期就结束。
1) 作用域
作用域就是 Python 程序可以直接访问命名空间的范围,或者说变量的有效范围。Python 的作用域有 4 种:
L(Local):最内层,包含局部变量,比如一个函数/方法内部。
E(Enclosing):包含了非局部 (non-local) 也非全局 (non-global) 的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。
G(Global):当前脚本的最外层,比如当前模块的全局变量。
B(Built-in): 包含了内置的变量/关键字等。
作用域查找遵循 LEGB 规则:
Local -> Enclosing -> Global -> Built-in
内置作用域是通过一个名为 builtins 的标准模块来实现的,查看内置作用域里的变量和关键字,可以运行如下代码:
import builtins
print(dir(builtins))
注:可是使用 dir() 函数查看模块、类、对象等结构内部所包含的属性列表和方法列表。
Python 中只有模块(module)、类(class)和函数(def、lambda)才会引入新的作用域,其它的代码块(比如 if/elif/else/、try/except、for/while 等)不会引入新的作用域。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # 全局变量 s = 'Global' def func(): # nonlocal 变量 s = 'Enclosing' def inner_func(): # 局部变量 s = 'Local' print(s) # 输出局部变量 s inner_func() print(s) # 输出 nonlocal 变量 s if __name__ == "__main__": func() print(s) # 输出全局变量 s print(x) # 输出未定义变量 x
输出结果如下:
Local Enclosing Global Traceback (most recent call last): File "d:/pythonDemo/func3.py", line 22, in <module> print(x) # 输出未定义变量 x NameError: name 'x' is not defined
注:在各命名空间都找不到变量 x, 抛出了一个 NameError 异常。
在 fun() 作用域内,给 s 赋值 'Enclosing',没有改变全局变量 s 的值。在 inner_func() 作用域内,给 s 赋值 'Local',没有改变nonlocal 变量 s 的值。在作用域内,想要修改作用域外的变量,需要用到 global 或 nonlocal 关键字。
2) global 和 nonlocal 关键字
在 Python 中,global 关键字用于标识变量是全局的。如果在函数内部要修改全局变量,需要使用 global 关键字。
nonlocal 关键字是用于标识变量是外部的(非全局)。如果在函数内部要修改外部函数的变量,需要使用 nonlocal 关键字。
示例1:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # 全局变量 s = 'Global' def func(): # nonlocal 变量 s = 'Enclosing' def inner_func(): # 标识 s 是全局变量 global s s = 'Local' print(s) # 输出全局变量 s inner_func() print(s) # 输出 nonlocal 变量 s if __name__ == "__main__": func() print(s) # 输出全局变量 s
输出结果如下:
Local
Enclosing
Local
示例2:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # 全局变量 s = 'Global' def func(): # nonlocal 变量 s = 'Enclosing' def inner_func(): # 标识 s 是 nonlocal 变量 nonlocal s s = 'Local' print(s) # 输出 nonlocal 变量 s inner_func() print(s) # 输出 nonlocal 变量 s if __name__ == "__main__": func() print(s) # 输出全局变量 s
输出结果如下:
Local
Local
Global
3) 获取作用域范围中的变量
(1) globals() 函数
globals() 函数是 Python 的内置函数,在函数内外调用(包括全局作用域),都返回一个全局变量字典。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # 全局变量 a = 1 b = 2 c = 3 def func(): # nonlocal 变量 i = 11 j = 12 def inner_func(): # 局部变量 x = 101 y = 102 inner_func() if __name__ == "__main__": print(globals())
输出结果如下:
{... , 'a': 1, 'b': 2, 'c': 3, ...}
注: globals() 函数返回的字典中,会默认包含有很多下划线变量,它们是 Python 主程序内置的属性和方法,不用管它们。
(2) locals() 函数
locals() 函数也是 Python 的内置函数,在函数内外调用,返回一个当前作用域内所有变量的字典。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # 全局变量 a = 1 b = 2 c = 3 def func(): # nonlocal 变量 i = 11 j = 12 print(locals()) def inner_func(): # 局部变量 x = 101 y = 102 print(locals()) inner_func() if __name__ == "__main__": func() print(locals())
输出结果如下:
{'i': 11, 'j': 12}
{'x': 101, 'y': 102}
{... , 'a': 1, 'b': 2, 'c': 3, ...}
注: 在函数内部调用 locals() 函数,会获得包含所有局部变量的字典;在全局作用域调用,locals() 的功能和 globals() 函数相同。
(3) vars(object) 函数
vars(object) 函数也是 Python 内置函数,其功能是返回一个指定 object 对象范围内所有变量组成的字典。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # 全局变量 a = 1 b = 2 c = 3 class Person: name = 'Python' age = 20 if __name__ == "__main__": print(vars(Person)) print(vars())
输出结果如下:
{..., 'name': 'Python', 'age': 20, ...}
{ ... 'a': 1, 'b': 2, 'c': 3, ...}
注:如果不传入 object 参数,vars() 和 locals() 的作用完全相同。
2. 输入与输出
1) 控制台输入
(1) input() 函数
input() 函数的主要功能是从标准输入(通常是键盘)读取一行文本。语法格式如下:
变量 = input(prompt=None)
函数说明:
prompt:可选参数,是一个字符串,用于显示提示信息,告诉用户应该输入什么类型的数据。
变量: 函数返回一个字符串,即用户输入的文本。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- a = input('Please enter a number:') print(type(a), int(a)) b = input('Please enter 2 numbers seprate with spaces:').split() print(type(b), b) c,d = input('Please enter 2 numbers seprate with comma:').split(',') print(type(c), int(c)) print(type(d), int(d))
输出结果如下:
Please enter a number:2 <class 'str'> 2 Please enter 2 numbers seprate with spaces:3 5 <class 'list'> ['3', '5'] Please enter 2 numbers seprate with comma:7,8 <class 'str'> 7 <class 'str'> 8
(2) sys.stdin.read() 函数
sys.stdin.read() 函数用于从标准输入读取数据,直到遇到 EOF(End of File)字符。这个函数会阻塞当前程序的执行,直到用户输入数据和回车键,按下 Ctrl+D(在 Unix/Linux 系统中)或 Ctrl+Z(在 Windows 系统中)键并按下回车键,结束输入。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import sys s = sys.stdin.read().split('\n') print(type(s), sa)
输出结果如下:
3
4
5
^Z
<class 'list'> ['3', '4', '5', '']
注:输入 3 后回车,再输入 4 后回车,再输入 5 后回车,同时按下 Ctrl+Z (Windows下) 键后回车。
2) 控制台输出
(1) print() 函数
在 Python 3.x 中 print 是一个函数,但在 Python2.x 版本不是一个函数,是一个关键字。语法格式如下:
print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
函数说明:
objects -- 复数,表示可以一次输出多个对象。输出多个对象时,需要用 , 分隔。
sep -- 用来间隔多个对象,默认值是一个空格。
end -- 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符串。
file -- 要写入的文件对象。
flush -- 输出是否被缓存通常决定于 file,但如果 flush 关键字参数为 True,流会被强制刷新。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import time import math print(1) print('1') print(1, '2') print(1, 2) print("python", "3.x") print("python", "3.x", sep="_") print("python""2.x") print("python", end="") # 以空格替代换行符, print("3.x") print("--- Loading 效果 ---") print("Loading",end = " ") for i in range(10): print(".", end = "",flush = True) time.sleep(0.5) print('') # 使用 f-string 语法格式化输出 s1 = 'python' s2 = '2.x' print(f'{s1} {s2}') print(F'{s2} {s1}') # F 和 f 等效 print(f'2*2={2*2}, pi={math.pi}') print(f'{{s1}} {{{s2}}}') # 字符串中插入大括号 # 使用 format() 函数 f-string 语法格式化输出 print("{0} {1}".format('Python', '3.x')) print("{1} {0}".format('Python', '3.x')) print("{name} {version}".format(name='Python', version='3.x')) print("{name} {0}".format('3.x', name='Python')) print('整型值:{:5d}'.format(3)) print('PI 值:{}'.format(math.pi)) print('PI 值:{0:.2f}'.format(math.pi)) print("{0:10} {1:.2f}".format('Python', math.pi)) # 在 : 后的 10, 表示要输出 10 个字符,不够用空格来补 obj = {'name': 'python', 'age': 20} print('{0[age]:d}'.format(obj)) # 使用 [] 访问字典键值,:d 表示整型 print('{0[age]:x}'.format(obj)) # 使用 [] 访问字典键值,:d 表示十六进制整数 print('{0[name]:s}'.format(obj)) # 使用 [] 访问字典键值,:s 表示字符串 # 使用 % 格式化输出 print('PI 值:%4.2f' % math.pi)
输出结果如下:
1 1 1 2 1 2 python 3.x python_3.x python2.x python3.x --- Loading 效果 --- Loading .......... python 2.x 2.x python 2*2=4, pi=3.141592653589793 {s1} {2.x} Python 3.x 3.x Python Python 3.x Python 3.x 整型值: 3 PI 值:3.141592653589793 PI 值:3.14 Python 3.14 20 14 python PI 值:3.14
(2) sys.stdout.write() 函数
sys.stdout.write() 函数用于从标准输出流将信息输出到控制台,不会自动输出换行符(\n),参数是字符串.
调用 print() 函数时,默认情况下实际是调用了 sys.stdout.write() 函数。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import sys sys.stdout.write('Python ') sys.stdout.write('3.x\n')
输出结果如下:
Python 3.x
(3) pprint() 模块
pprint (Data pretty printer),顾名思义就是让显示结果更漂亮。
pprint() 模块打印出来的数据结构更加完整,每行为一个数据结构,更加方便阅读打印输出结果。
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import pprint data = [(1,{'a':'A','b':'B','c':'C','d':'D'}),(2,{'e':'E','f':'F','g':'G','h':'H'})] from pprint import pprint print('depth 1:') pprint(data, depth=1) print('depth 2:') pprint(data, depth=2) print('depth 3:') pprint(data, depth=3)
输出结果如下:
depth 1: [(...), (...)] depth 2: [(1, {...}), (2, {...})] depth 3: [(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}), (2, {'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H'})]
3) 读写文件
open() 方法用于打开一个文件,并返回文件对象。语法格式如下:
open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)
参数说明:
file: 必需,文件路径(相对或者绝对路径)。
mode: 可选,文件打开模式,默认为文本模式,如果要以二进制模式打开,加上 b 。
buffering: 设置缓冲
encoding: 一般使用utf8
errors: 报错级别
newline: 区分换行符
closefd: 传入的file参数类型
opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。
mode 参数:
模式 | 描述 |
t | 文本模式 (默认)。 |
x | 写模式,新建一个文件,如果该文件已存在则会报错。 |
b | 二进制模式。 |
+ | 打开一个文件进行更新(可读可写)。 |
U | 通用换行模式(Python 3 不支持)。 |
r | 以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。 |
rb | 以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。 |
r+ | 打开一个文件用于读写。文件指针将会放在文件的开头。 |
rb+ | 以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。 |
w | 打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb | 以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
w+ | 打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。 |
wb+ | 以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。 |
a | 打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
ab | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。 |
a+ | 打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。 |
ab+ | 以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。 |
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- # Create/write a file with open('foo.txt', 'w', encoding='UTF-8', newline='') as f: num = f.write("open() 方法用于打开一个文件,并返回文件对象。\n语法格式如下:\nopen(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)\n") print('f.write(): ', num) f.close() # Read() with open('foo.txt', 'r', encoding='UTF-8', newline='') as f: str = f.read() print('f.read(): ') print(str) # cursor position print('f.tell():', f.tell()) # Seek to start f.seek(0, 0) print('f.seek(0, 0):', f.tell()) str = f.readline() print('f.readline(): ') print(str) str = f.readlines() print('f.readlines(): ') print(str) f.close()
输出结果如下:
f.write(): 139 f.read(): open() 方法用于打开一个文件,并返回文件对象。 语法格式如下: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None) f.tell(): 191 f.seek(0, 0): 0 f.readline(): open() 方法用于打开一个文件,并返回文件对象。 f.readlines(): ['语法格式如下:\n', "open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)\n"]
4) pickle 模块
pickle 模块用于实现基本的数据序列和反序列化。
(1) 数据序列化
序列化操作就是将程序中运行的对象信息保存到文件中去,永久存储,函数格式如下:
pickle.dump(obj, file, [,protocol])
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import pickle class A: def display(self, s): print(s) data = [(1,{'a':'A','b':'B','c':'C','d':'D'}),(2,{'e':'E','f':'F','g':'G','h':'H'})] a = A() with open('data.pkl', 'wb') as f: # Pickle dictionary using protocol 0. pickle.dump(data, f) # Pickle the list using the highest protocol available. pickle.dump(a, f, -1) print('pickle.dump() -> OK') f.close()
输出结果如下:
pickle.dump() -> OK
(2) 数据反序列化
反序列化操作就是从文件中读取序列化数据,转换成数据对象,函数格式如下:
obj = pickle.load(file)
示例:
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import pickle import pprint with open('data.pkl', 'rb') as f: obj1 = pickle.load(f) pprint.pprint(obj1) obj2 = pickle.load(f) pprint.pprint(obj2) obj2.display('pickle.load() -> OK') f.close()
输出结果如下:
[(1, {'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D'}),
(2, {'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H'})]
<__main__.A object at 0x000001D5489B7130>
pickle.load() -> OK