【Python学习笔记】 第9章 元组、文件和其他核心类型
元组Python元组的属性:
[*]任意对象的有序集合
[*]通过偏移量存取
[*]属于“不可变序列”
[*]固定长度、多样性、任意嵌套
[*]对象引用的数组
元组的常见方法:
运算解释()空元组T = (0,)单个元素的元组T = (0, 'Ni', 1.2, 3)四个元素的元组T = 0, 'Ni', 1.2, 3还是四个元素的元组T = ('Bob', ('dev', 'mgr'))嵌套元组T = tuple('spam')可迭代对象的元素组成的元组T索引T索引的索引T分片len(T)长度T1 + T2拼接T * 3重复for x in T: print(x)迭代'spam' in T成员关系测试列表推导(好像不应该出现在这里?)namedtuple('Emp', ['name', 'jobs'])有名元组扩展类型元组的实际应用
元组支持字符串和列表的一般序列操作,如拼接、重复、索引和分片。
>>> (1, 2) + (3, 4)
(1, 2, 3, 4)
>>> (1, 2) * 4
(1, 2, 1, 2, 1, 2, 1, 2)
>>> T = (1, 2, 3, 4)
>>> T
1
>>> T
(2, 3)元组的特殊语法:逗号和圆括号
如果我们要得到元组而不是表达式的值,我们要在圆括号中加,。如果要构造单个元素的元组,就要在元素的后面加上,。
>>> (40) # 数字
40
>>> (40,) # 元组
(40,)在不会引起二义性的情况下,Python允许构造元组时省略圆括号。要用到圆括号的情形:元组出现在一个函数调用中,或嵌套在一个更大的表达式内。
转换、方法和不可变性
注意:上述的对元组的操作(拼接、重复等)会返回一个新的元组。元组不提供字符串、列表和字典中的方法。要对一个元组的元素排序,可以转换成可变对象(如列表),或者使用新的内置函数(如sorted)。
>>> T = ('cc', 'aa', 'dd', 'bb')
>>> T = tuple(tmp)
>>> tmp = list(T)
>>> tmp.sort()
>>> sorted(T)
['aa', 'bb', 'cc', 'dd']
>>> T
('aa', 'bb', 'cc', 'dd')list将元组转换为列表,tuple将列表转换为元组。
列表推导也可以用来转换元组。
>>> T = (1, 2, 3, 4, 5)
>>> L =
>>> L
列表的本质是不可变序列操作,列表推导甚至可以用在某些并非实际存储的序列上,任何可迭代对象都可以。
元组有属于自己的方法——index索引,count寻找元组中元素的数目。
>>> T = (1, 2, 3, 2, 4, 2)
>>> T.index(2) # 第一个2的索引
1
>>> T.index(2, 2) # 从偏移2开始,第一个2的索引
3
>>> T.count(2)
3注意:元组的不可变性只适用于元组本身顶层而非其内容。我们可以修改元组内部的列表。
>>> T = (1, , 4)
>>> T = 'spam'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> T = 'spam'
>>> T
(1, ['spam', 3], 4)为什么有了列表还需要元组
“元组”的概念来自于数学,元组的不可变性提供了某种一致性,确保元组不会被另一个引用修改,类似于“常量”声明。
重访记录:有名元组
我们可以实现一个同时提供序号和键两种询问方式的对象。例:namedtuple工具实现了一个增加了逻辑的元组扩展类型(在模块collections被使用),能同时支持使用序号和属性访问组件。
>>> from collections import namedtuple
>>> Rec = namedtuple('Rec', ['name', 'age', 'jobs'])
>>> bob = Rec('Bob', age=40.5, jobs=['dev', 'mgr'])
>>> bob
Rec(name='Bob', age=40.5, jobs=['dev', 'mgr'])
>>> bob
'Bob'
>>> bob
['dev', 'mgr']
>>> bob.name
'Bob'
>>> bob.jobs
['dev', 'mgr']
>>>可以将其转换成一个字典,或者类似字典的结构:
>>> O = bob._asdict()
>>> O['name']
'Bob'
>>> O['jobs']
['dev', 'mgr']
>>> O
{'name': 'Bob', 'age': 40.5, 'jobs': ['dev', 'mgr']}元组和有名元组都支持解包元组赋值:
>>> bob = Rec('Bob', 40.5, ['dev', 'mgr'])
>>> name, age, jobs = bob
>>> name
'Bob'
>>> jobs
['dev', 'mgr']但是,解包元组赋值有时候不适用于字典,因为字典中的键值是没有序号的。
>>> bob = {'name': 'Bob', 'age': 40.5, 'jobs': ['dev', 'mgr']}
>>> bob.values()
dict_values(['Bob', 40.5, ['dev', 'mgr']])
>>> job, name, age = bob.values()
>>> job
'Bob'
>>> name
40.5文件
通过open,我们能够创建一个Python文件对象作为到计算机上一个文件的链接。在调用open后,我们可以通过返回的文件对象的方法,在程序与相应文件之间来回传递串形式的数据。
文件对象与前面介绍的核心数据类型不同,它只支持与文件处理任务相关的方法。常见的文件操作见下表:
操作解释output = open(r'C:\spam', 'w')创建输出文件('w'代表写入文件)input = open('data', 'r')创建输入文件('r'代表从文件读入)input = open('data')同上('r'是默认值)aString = input.read()把整个文件读入字符串aString = input.read()读取接下啦的N个字符到一个字符串aString = input.readline()读取下一行(包括行末的'\n')到一个字符串aList = input.readlines()读取整个文件到一个字符串列表,列表中的元素是包括行末的'\n'的行output.write(aString)把字符串写入文件output.writelines(aList)把列表内所有的字符串写入文件output.close()手动关闭(文件收集完成时会自动关闭)output.flush()把输出缓冲区刷入硬盘中,但不关闭文件anyFile.seek(N)把文件位置移动到偏移量N处以便进行下一个操作for line in open('data'): use line文件迭代器逐行获取open('f.txt', encoding='latin-1')Python 3.X Unicode文件(str字符串)open('f.bin', 'rb')Python 3.X 字节码文件(bytes字符串)codecs.open('f.txt', encoding='utf-8')Python 2.X Unicode文件(unicode字符串)open('f.bin', 'rb')Python 2.X 字节码文件(str字符串)打开文件
打开文件时,程序调用内置函数open,第一个参数是外部文件名,第二个参数是文件的处理模式,返回文件对象,这个文件对象带有传输数据的方法:
afile = open(filename, mode)
afile.method()第一个参数是外部文件名,它可能有相对路径前缀,如果没有,则默认在脚本所运行的目录下。
第二个参数是打开方式,'r'表示读文件,'w'表示写文件,'a'表示在文件内容追加内容并打开文件,'b'表示可以进行二进制处理,'+'表示同时支持输入输出。
第三个参数是可选的,控制输出缓冲,0表示输出无缓冲。
使用文件
基础用法的提示:
[*]文件迭代器最适合逐行读取
[*]写入文件的内容必须是字符串,Python不会帮你转换
[*]文件是被缓冲的、可定位的,写入的文本不会被立刻从内存转移到硬盘,除非关闭文件或用flush()方法
[*]close是可选的,回收时自动关闭
文件的实际应用
首先为输出打开一个文件,写入两行文本,关闭文件:
>>> afile = open('1.txt', 'w')
>>> myfile = open('myfile.txt', 'w')
>>> myfile.write('hello text file\n')
16
>>> myfile.write('goodbye text file\n')
18
>>> myfile.close()在Python 3.X中,写入字符串会返回字符数,但Python 2.X不会。
然后打开文件,逐行读取。第三个readline()返回空字符串,表示文件已经到达末尾。
>>> myfile = open('myfile.txt')
>>> myfile.readline()
'hello text file\n'
>>> myfile.readline()
'goodbye text file\n'
>>> myfile.readline()
''read方法把整个文件内容读入到一个字符串中:
>>> open('myfile.txt').read()
'hello text file\ngoodbye text file\n'如果要逐行扫描文件并对每一行操作,文件迭代器是最佳选择:
>>> for line in open('myfile.txt'):
... print(line.upper(), end='')
...
HELLO TEXT FILE
GOODBYE TEXT FILE这样,open创建的临时文件对象将自动在每次循环迭代时读入并返回一行。优点:容易编写、更高的内存使用效率、更快。
文本和二进制文件:一个简要的故事
Python总是支持文本和二进制文件,其中:
[*]文本文件把内容表示为常规的str字符串,自动执行Unicode编码和解码,并且默认执行末行转换;
[*]二进制文件把内容表示为一个特殊的bytes字节串类型。
由于文本文件实现了Unicode编码,因此不能以文本模式打开一个二进制文件,否则解码会失败。当我们读取一个二进制文件时,会得到一个bytes对象(字节串)。
>>> open('myfile.txt', 'rb').read()
b'hello text file\r\ngoodbye text file\r\n'此外,二进制文件不会对数据执行任何字符串转换。
在文件中存储Python对象:转换
下面的例子把多种Python对象写入一个文本文件的各行,必须把对象转换成字符串。
>>> open('myfile.txt', 'r').read()
'hello text file\ngoodbye text file\n'
>>> X, Y, Z = 43, 44, 45
>>> S = 'Spam'
>>> D = {'a': 1, 'b': 2}
>>> L =
>>>
>>> F = open('datafile.txt', 'w')
>>> F.write(S + '\n')
5
>>> F.write('%s,%s,%s\n' % (X, Y, Z))
9
>>> F.write(str(L) + '$' + str(D) + '\n')
27
>>> F.close()创建文件后,我们可以读取文件中的内容。注意,交互式(在交互模式下输入对象)显示给出直接的字节内容,而print操作解释内嵌的换行符”:
>>> chars = open('datafile.txt').read()
>>> chars
"Spam\n43,44,45\n${'a': 1, 'b': 2}\n"
>>> print(chars)
Spam
43,44,45
${'a': 1, 'b': 2}但是,问题是如何将文本文件中的字符串转换成Python对象。
把第一行转换为字符串:
>>> F = open('datafile.txt')
>>> line = F.readline()
>>> line
'Spam\n'
>>> line.rstrip()
'Spam'把第二行转换为数字序列。这里int函数可以忽略数字字符串旁边的的空白:
>>> line = F.readline()
>>> parts = line.split(',')
>>> parts
['43', '44', '45\n']
>>> numbers =
>>> numbers
转换第三行时,我们可以运行eval函数,它把字符串参数当作可执行程序代码:
>>> line = F.readline()
>>> line
"${'a': 1, 'b': 2}\n"
>>> parts = line.split('$')
>>> parts
['', "{'a': 1, 'b': 2}\n"]
>>> eval(parts)
>>> eval(parts)
{'a': 1, 'b': 2}存储Python原生对象:pickle
eval的缺陷是:它会执行任何表达式,包括删除计算机中所有文件的表达式。如果要存储Python的原生对象,就应该使用pickle。
pickle是一种能够让我们直接在文件中存储几乎任何Python对象的高级工具,而且不需要字符串转换。
例:在文件中存储字典:
>>> D = {'a': 1, 'b': 2}
>>> F = open('datafile.pkl', 'wb')
>>> import pickle
>>> pickle.dump(D, F)
>>> F.close()想要取回字典时,再次使用pickle重建即可:
>>> F = open('datafile.pkl', 'rb')
>>> E = pickle.load(F)
>>> E
{'a': 1, 'b': 2}实际上,pickle将字典转化为一系列二进制的字节串,也能从字节串转化为对象:
b'\x80\x04\x95\x11\x00\x00\x00\x00\x00\x00\x00}\x94(\x8c\x01a\x94K\x01\x8c\x01b\x94K\x02u.'shelve也可以实现类似功能,但这里不讨论。
用JSON格式存储Python对象
JSON是一种新兴的数据交换格式,它与语言无关,也支持多种系统。它的可移植性在一些场景中会带来巨大优势。此外,由于JSON与Python中的字典和列表在语法上的相似性,使Python的json标准库模块能够很容易地在Python对象与JSON之间来回转换。
比如,Python的字典和JSON数据十分相似。我们创建一个字典,发现可以将字面量几乎原封不动地传给JSON文件:
>>> name = dict(first='Bob', last='Smith')
>>> rec = dict(name=name, job=['dev', 'mgr'], age=40.5)
>>> rec
{'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5}
>>> import json
>>> json.dumps(rec)
'{"name": {"first": "Bob", "last": "Smith"}, "job": ["dev", "mgr"], "age": 40.5}'
>>> S = json.dumps(rec)
>>> O = json.loads(S)
>>> O
{'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5}json模块可以使JSON表达式和Python对象互相转换。从json文件读取对象时,json模块把文件地内容从JSON表示重建成Python对象。这里,dump把对象地字面量读取到json文件中,load把json文件中的字面量加载到变量。注意,实际上的文件扩展名应该是.json而不是.txt。
>>> json.dump(rec, fp=open('testjson.txt', 'w'), indent=4)
>>> print(open('testjson.txt').read())
{
"name": {
"first": "Bob",
"last": "Smith"
},
"job": [
"dev",
"mgr"
],
"age": 40.5
}
>>> P = json.load(open('testjson.txt'))
>>> P
{'name': {'first': 'Bob', 'last': 'Smith'}, 'job': ['dev', 'mgr'], 'age': 40.5}存储打包二进制数据:struct
struct模块能够构造/解析打包二进制数据。
要生成一个打包二进制数据,可以用'wb'(写入二进制)打开它,并将一个格式化字符串和几个Python对象传给struct。
>>> F = open('data.bin', 'wb')
>>> import struct
>>> data = struct.pack('>i4sh', 7, b'spam', 8)
>>> data
b'\x00\x00\x00\x07spam\x00\x08'
>>> F.write(data)
10
>>> F.close()Python会创建一个我们通常写入文件的二进制bytes数据字符串,主要由不可打印字符的十六进制转义组成。
要把二进制文件解析成一般的Python对象,可以直接读取字节串,并使用相同的格式字节串解压即可。
>>> F = open('data.bin', 'rb')
>>> data = F.read()
>>> data
b'\x00\x00\x00\x07spam\x00\x08'
>>> values = struct.unpack('>i4sh', data)
>>> values
(7, b'spam', 8)二进制文件是高级且底层的工具,因此这里不会介绍更多细节。
文件上下文处理器
文件上下文能够让我们把文件处理代码包装到一个逻辑层中,以确保在推出后一定会自动关闭文件,而不是在垃圾回收时自动关闭:
with open(r'C:\code\data.txt') as myfile:
for line in myfile:
...use line here...之后学的try/finally也提供类似的功能。
其他文字工具
更多文件的方法查询:dir()函数、help()函数、第13章。
Python工具集中有其他类似可用的文件工具:
[*]标准流
[*]os模块中的描述文件
[*]套接字、管道和FiFO文件
[*]通过键存取的文件
[*]Shell命令流
核心类型复习总结
[*]按照分类,一些对象拥有共同的操作。如字符串、列表和元组拥有序列操作。
[*]只有可变对象可以在原位置修改。可变对象有:列表、字典和集合。
[*]文件只导出方法,因此可变性不适用于文件类型。
[*]“数字”包含:整数、浮点数、复数、小数和分数。
[*]字符串包含:str、Python 3.X中的bytes和Python 2.X中的unicode。
[*]集合可以视为没有值只有键的字典。
[*]除了类型分类操作,所有类型都有可调用的方法。
请注意:运算符重载
在数值类型中,+表示将两个数的值相加;在序列中,+表示拼接两个序列。如果要设计新的类,我们可以定义加号(以及其他运算符)作用于这个类对象的含义。比如,定义类的加法可以用:def __add__(self, other):。
对象灵活性
一般来说:
[*]列表、字典和元组可以包含任何种类的对象;
[*]集合可包含任意的不可变类型对象;
[*]列表、字典和元组可以任意嵌套;
[*]列表、字典和集合可以动态地扩大和缩小。
Python的复合对象类型支持任意结构,因此它们非常适合表示程序中的复杂数据,可以嵌套任意多层。下面是一个嵌套的例子:
>>> L = ['abc', [(1, 2), (, 4)], 5]
>>> L
[(1, 2), (, 4)]
>>> L
(, 4)
>>> L
>>> L
3用图表示,就是:
引用vs复制
第6章提到,赋值操作总是存储对象的引用,而不是对象的副本。当涉及到较大的对象时,这种现象会变得微妙:
>>> X =
>>> L = ['a', X, 'b']
>>> D = {'x': X, 'y': 2}修改这三个引用的任意一个共享列表对象X,也会改变另外两个引用的对象:
>>> X = 'surprise'
>>> L
['a', , 'b']
>>> D
{'x': , 'y': 2}引用是其他语言中指针的更高级模拟。虽然我们不能获得引用本身,但是我们可以在不止一个地方存储相同的引用。
这意味着,我们可以在程序范围内任何地方传递大型对象而不用复制。如果要复制,需要明确要求:
[*]没有参数的分片表达式L[:]可以复制序列;
[*]字典、集合和列表的copy方法可以复制本身;
[*]内置函数也可以复制,如将list方法应用于列表,dict方法应用于字典等等;
[*]copy标准库模块能够在需要时创建完整副本。
列表和字典复制的例子:
>>> L =
>>> D = {'a': 1, 'b': 2}
>>> # 将副本赋值给其他变量
>>> A = L[:]
>>> B = D.copy()
>>> # 由其他变量引发的改变将修改新产生的副本,而不是原有的对象
>>> A = 'Ni'
>>> B['c'] = 'spam'
>>> L, D
(, {'a': 1, 'b': 2})
>>> A, B
(, {'a': 1, 'b': 2, 'c': 'spam'})但是,copy方法只能进行顶层复制。
>>> L0 =
>>> L1 = ['a', L0, 'b']
>>> L2 = L1.copy()
>>> L2
['a', , 'b']
>>> L2 = 'c'
>>> L1, L2
(['a', , 'b'], ['c', , 'b'])
>>> L2 = 5
>>> L1, L2
(['a', , 'b'], ['c', , 'b'])要进行深层复制(完整、独立的复制),需要copy模块中的deepcopy方法。
比较、等价性和真值
所有的Python对象都支持:测试等价性、相对大小等。比较复合对象时,会检查所有的组件,直到得出结果为止。
这种比较也被称为递归比较——对顶层对象的比较会被应用到每一个嵌套的下一次对象中,直到最底层的对象并得出结果。就核心类型而言,递归功能是默认实现的。
在比较列表对象时将自动比较所有内容,直到找到一个不匹配或完成比较:
>>> L1 =
>>> L2 =
>>> L1 == L2
True
>>> L1 is L2
False这里展示了两种测试等价性的方式,分别是:
[*]==运算符测试值的等价性,Python递归地比较所有内嵌对象。
[*]is表达式测试对象的同一性,Python测试两者是否为同一个对象(在存储器中有相同地址)。
注意对短字符串的运行结果:
>>> S1 = 'spam'
>>> S2 = 'spam'
>>> S1 == S2
True
>>> S1 is S2
True这是因为,Python会通过重复地理利用短字符串进行优化,因此S1和S2共享同一个'spam'。但这不适用于长字符串:
>>> S1 = 'A Longer String'
>>> S2 = 'A Longer String'
>>> S1 == S2
True
>>> S1 is S2
False相对大小比较也能递归地应用于嵌套数据结构:
>>> L1 =
>>> L2 =
>>> L1 < L2, L1 == L2, L1 > L2
(False, False, True)Python比较相对大小地规则是:
[*]数字在转换成必要的公共最高级类型后,比较数值地相对大小。
[*]字符串按照字母字典顺序比较(从左到右比较字符的ASCII码大小)。
[*]列表和元组从左到右对每个组件内容进行比较,直到末尾或发现区别。
[*]集合是相等的,当且仅当它们含有相同的元素。集合的比较大小采取子集和超集的标准。
[*]字典通过比较排序后的(key, value)列表判断是否相同。(仅Python 2.X)
[*]非数字不同类型(混合类型)的比较。(仅Python 2.X)
Python 2.X和3.X混合类型比较和排序
Python 2.X中的混合类型通过任意的顺序的比较,但3.X不允许混合顺序比较。
Python 2.X和3.X中的字典比较
在Python 2.X中,字典支持相对大小比较,就等效于比较排序的键/值列表,但是在Python 3.X中,字典之间的比较大小由于开销太大,故被移除。
Python 3.X的替代方式是:要么编写循环键比较值,要么手动比较排序的键/值列表(利用sorted方法):
>>> list(D1.items())
[('a', 1), ('b', 2)]
>>> list(D2.items())
[('a', 1), ('b', 3)]
>>> sorted(D1.items()) < sorted(D2.items())
True
>>> sorted(D1.items()) > sorted(D2.items())
FalsePython中True和False的含义
在Python中,1表示真,0表示假。实际上,真和假是Python中每个对象的固有属性,规则如下:
[*]数字如果等于0则为假,反之则为真。
[*]其他对象如果为空则为假,反之则为真。
一些是一些例子:
对象值"spam"True""FalseTrue[]False{'a': 1}True{}False1True0.0FalseNoneFalse这样做会使对象的检查更加简单,如检查字符串是否为空可以简化成if X:,也可以if X != '':。
None对象
None总是为假,这是Python中一种特殊数据类型的唯一值,作用是空占位符。
None不意味着“未定义”,而是某些内容。None是一个真正的对象,有一块真实的内存。None是Python给定的一个内置名称,也是函数的默认返回值。
bool类型
Python的布尔类型bool只是扩展了Python中的真假概念。这种设计只是为了让真值更加显式,程序更明确。
Python还提供了一个内置的函数bool,显式地把对象转换为对象的布尔值。
>>> bool(1)
True
>>> bool('spam')
True
>>> bool({})
FalsePython的类型层次
类型和对象
即使是类型本身在Python中也是一种类型的对象:对象的类型本身,也属于type类型的对象。
从Python 2.2开始,每个核心类型都有一个新内置名,用来支持通过面向对象编写子类的类型定制:dict、list、tiple、str、int、float、complex、bytes、type、set等。
Python的其他类型
后面要学到的类型:函数、模块和类。以及扩展:正则表达式对象、DBM文件、GUI组件、网络套接字等等。它们(扩展)与核心类型的区别是:内置类型不需要导入模块,但扩展类型需要用import导入模块。
内置类型陷阱
赋值创建引用,而不是复制
可变对象的共享引用在你的程序中至关重要。
如果不想要这种共享,那么使用复制的手段来避免共享。
重复会增加层次深度
这里,当可变序列进行嵌套时,可能会使列表重复,或者列表中嵌套四个列表:
>>> L =
>>> X = L * 4
>>> X
>>> Y = * 4
>>> Y
[, , , ]Y包含了指向原本L的列表的引用,因此出现了副作用:
>>> L = 0
>>> X
>>> Y
[, , , ]解决方法是:进行复制,这里用list解决:
>>> L =
>>> Y = * 4
>>> Y
[, , , ]
>>> L = 0
>>> Y
[, , , ]但是,Y对应的列表对象所嵌套的引用仍然指向同一个对象。要避免这种现象,必须保证每一个嵌套有一个单独的副本:
>>> Y = 99
>>> Y
[, , , ]
>>> L =
>>> Y =
>>> Y
[, , , ]
>>> Y = 99
>>> Y
[, , , ]注意循环数据结构
如果一个复合对象包含指向自己的引用,就称之为循环对象。Python在检测到循环时,会打印出[...]。
>>> L =
>>> L.append(L)
>>> L
]经验法则:除非真的需要,否则不要使用循环引用。
不可变类型不可以在原位置改变
如果要改变,需要通过分片、拼接等操作创建一份新的对象。
来源:https://www.cnblogs.com/hiu-siu-dou/p/18411402
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]