如何制作Python Cookbook学习笔记?
- 内容介绍
- 文章标签
- 相关推荐
本文共计19281个文字,预计阅读时间需要78分钟。
目录:+ 数据结构和算法 + 1. 数据结构:4 + 1.1 解压序列列给多个变量赋值 + 2. 解压可迭代对象给多个变量赋值 + 3. 保留最后一个元素:collections.deque
目录
一、数据结构和算法: 4
1、解压序列赋值给多个变量 4
2、解压可迭代对象赋值给多个变量 5
3、保留最后N个元素collections.deque 5
4、查找最大或最小的N个元素heapq.nlargest|nsmallest 5
5、实现一个优先级队列heapq.heappush()|heapq.heappop() 6
6、字典中的键映射多个值(multidict)collections.defaultdict 8
7、字典排序(有序字典)collections.OrderedDict 8
8、字典运算 9
9、查找2字典的相同点 10
10、删除序列中相同元素并保持顺序 11
11、命名切片slice(start,stop,step) 12
12、序列中出现次数最多的元素collections.Counter 13
13、通过某个关键字排序一个字典列表operator.itemgetter 13
14、排序不支持原生比较的对象operator.attrgetter 14
15、通过某个字段将记录分组itertools.groupby 15
16、过滤序列元素 16
17、从字典中提取子集: 18
18、映射名称到序列元素collections.namedtuple 19
19、转换并同时计算数据,生成器表达式 20
20、合并多个字典或映射collections.ChainMap 21
二、字符串和文本 23
1、使用多个界定符分割字符串re.split() 23
2、字符串开头或结尾匹配str.startswith()|str.endswith() 24
3、用shell通配符匹配字符串fnmatch.fnmatch 24
4、字符串匹配和搜索re.match()|re.findall() 25
5、字符串搜索和替换re.sub() 26
6、字符串忽略大小写的搜索替换 27
7、最短匹配模式.*?|.+? 28
8、多行匹配模式 28
9、将Unicode文本标准化 28
10、正则表达式使用Unicode 29
11、删除字符串中不需要的字符str.strip()|re.sub('\s+','',s1) 29
12、审查清理文本字符串 29
13、字符串对齐format() 29
14、合并拼接字符串join() 30
15、字符串中插入变量format()|format_map() 31
16、以指定列宽格式化字符串textwrap.fill() 32
17、在字符串中处理html|xml 33
18、字符串令牌解析 34
19、实现一个简单的递归下降分析器 34
20、字节字符串上的字符串操作 34
三、数字|日期|时间 35
1、数字的四舍五入round(value, ndigits) 35
2、执行精确的浮点数运算 36
3、数字的格式化输出format() 37
4、二|八|十六进制整数 38
5、字节到大整数的打包与解包 38
6、复数的数学运算cmath|numpy 39
7、无穷大与NaN 40
8、分数运算fractions.Fraction 41
9、大型数组运算numpy 41
10、矩阵np.matrix()与线性代数运算 42
11、随机选择random 42
12、基本的日期与时间转换datetime|dateutil 43
13、计算最后一个周五的日期 45
14、计算当月份的日期范围 45
15、字符串转换为日期datetime.strptime() 45
16、结合时区的日期操作pytz 46
四、迭代器与生成器 46
1、手动遍历迭代器 47
2、代理迭代__iter__() 48
3、使用生成器创建新的迭代模式 49
4、实现迭代器协议 50
5、反向迭代reversed(lst)|__reversed__() 51
6、带有外部状态的生成器函数 52
7、迭代器切片itertools.islice() 53
8、跳过可迭代对象的开始部分itertools.dropwhile()|itertools.islice() 54
9、排列组合的迭代itertools.permutations()|itertools.combinations()|itertools.combinations_with_replacement() 55
10、序列上索引值迭代enumerate() 55
11、同时迭代多个序列zip()|itertools.zip_longest() 57
12、不同集合上元素的迭代itertools.chain() 58
***13、创建数据处理管道 59
14、展开嵌套的序列collections.Iterable 60
15、顺序迭代合并后的排序迭代对象heapq.merge() 61
16、迭代器替代while无限循环 62
七、函数 63
1、可接受任意数量参数的函数 64
2、只接受关键字参数的函数 64
3、给函数参数增加元信息 65
4、返回多个值的函数 66
5、定义有默认参数的函数 66
6、定义匿名函数或内联函数 68
7、匿名函数捕获变量值 68
8、减少可调用对象的参数个数functools.partial() 69
9、将单方法的类转换为函数(闭包) 70
10、带额外状态信息的回调函数 70
11、内联回调函数 72
12、访问闭包中定义的变量 72
十三、脚本编程与系统管理 73
1、通过重定向|管道|文件接受输入fileinput.input() 73
2、终止程序并给出错误信息raise 74
3、解析命令行选项argparse 74
4、运行时弹出密码输入提示getpass 76
5、获取终端的大小os.get_terminal_size() 76
6、执行外部命令并获取它的输出subprocess.check_output() 77
7、复制或移动文件或目录shutil 78
8、创建和解压归档文件shutil.make_archive()|shutil.unpack_archive() 79
9、通过文件名查找文件os.walk() 80
10、读取.ini配置文件configparser.ConfigParser 81
11、给简单脚本增加日志功能logging 83
12、给函数库增加日志功能logging 85
13、实现一个计时器time.perf_counter() 86
14、限制内存和cpu的使用量resource.getrlimit()|resource.setrlimit() 87
15、启动一个web浏览器webbrowser 87
八、类与对象 88
1、改变对象的字符串显示__str__()|__repr__() 88
2、自定义字符串的格式化__format__() 89
3、让对象支持上下文管理协议__enter__()|__exit__()|contextmanager 89
4、创建大量对象时节省内存的方法__slots__ 91
5、在类中封装属性名_ 92
6、创建可管理的属性@property 93
7、调用父类的方法super() 94
8、子类中扩展property 95
9、创建新的类或实例属性__get__()|__set__()|__delete__() 96
10、使用延迟计算特性_描述器类 97
11、简化数据结构的初始化 98
12、定义接口或抽象基类abc 98
13、实现数据模型的类型约束 99
五、文件与IO 99
1、读写文本数据open() 99
2、打印输出至文件中print('content', file=f) 100
3、使用其它分隔符或行终止符打印print('content', sep=',', end='!!\n') 100
4、读写字节数据open() 100
5、文件不存在才能写入open() 102
6、字符串的I/O操作io.StringIO()|io.BytesIO() 102
7、读写压缩文件gzip.open()|bz2.open() 103
8、固定大小记录的文件迭代functools.partial()|iter() 103
9、读取二进制数据到可变缓冲区中_f.readinto() 103
10、内存映射的二进制文件mmap 104
11、文件路径名的操作os.path中的函数 104
12、测试文件是否存在os.path.exists() 105
13、获取目录中的文件列表os.listdir() 105
14、忽略文件名编码 106
15、打印不合法的文件名 107
16、增加或改变已打开文件的编码io.TextIOWrapper() 107
17、将字节写入文本文件 107
18、将文件描述符包装成文件对象 107
19、创建临时文件和文件夹tempfile.TemporaryFile|tempfile.NamedTemporaryFile|tempfile.TemporaryDirectory 108
20、与串行端口的数据通信serial 109
21、序列化py对象pickle 109
六、数据编码和处理 110
1、读写csv数据csv.reader()|csv.DictReader()|csv.writer()|csv.DictWriter() 110
2、读写json数据json.dumps()|json.loads() 113
3、解析简单的xml数据xml.etree.ElementTree.parse() 115
4、增量式解析大型xml文件xml.etree.ElementTruee.iterparse() 115
5、将字典转为xmlxml.etree.ElementTruee.Element 115
6、解析和修改xmlxml.etree.ElementTree.parse()|xml.etree.ElementTree.Element 115
7、利用命名空间解析xml文档 115
8、与关系型数据库的交互 116
9、编码解码16进制数binascii.b2a_hex()|binascii.a2b_hex()|base64.b16encode()|base64.b16decode() 116
10、编码解码base64数据base64.b64encode()|base64.b64decode() 117
11、读写二进制数组数据struct 117
12、读取嵌套和可变长二进制数据struct 117
13、数据的累加与统计操作pandas 118
一、数据结构和算法:
1、解压序列赋值给多个变量
p = (4,5)
x, y = p
_, x = p # _或ign为废弃名称
record = ('ACME', 50, 123.45, (12,18,2012))
name, *_, (*_, year) = record
2、解压可迭代对象赋值给多个变量
record = ('Dave', 'dave@example.com', '4524510', '4527926')
name, email, *phone_numbers = record #phone_numbers为list
*trailing, current = [10,8.7,1,9,5.10,1] # current为1
3、保留最后N个元素collections.deque
from collections import deque
deque(maxlen=N) # 新建一个固定大小的队列,当新的元素加入且这个队列已满时,最老的元素会自动被移除掉;虽列表也能实现,但这个方案更优雅运行更快,在队列两端插入或删除时间复杂度都是O(1),而在列表的开头插入或删除时间复杂度是O(N)
In [8]: from collections import deque
In [9]: q = deque(maxlen=3)
In [10]: q.append(1)
In [11]: q.append(2)
In [12]: q.append(3)
In [13]: q
Out[13]: deque([1, 2, 3])
In [14]: q.append(4)
In [15]: q
Out[15]: deque([2, 3, 4])
In [16]: q.appendleft(5)
In [17]: q
Out[17]: deque([5, 2, 3])
4、查找最大或最小的N个元素heapq.nlargest|nsmallest
from heapq import nlargest, nsmallest # 底层使用堆排序,适合于要查找的元素相对较少时;若仅想查找唯一的最大或最小元素,使用min()和max()更快;如果要查找的元素和集合中的元素相当,用sorted(items)[:N]更快
In [18]: from heapq import nsmallest,nlargest
In [19]: lst = [1,2,3,4,5,6,7,8]
In [20]: nlargest(3,lst)
Out[20]: [8, 7, 6]
In [21]: nsmallest(3,lst)
Out[21]: [1, 2, 3]
5、实现一个优先级队列heapq.heappush()|heapq.heappop()
实现一个按优先级排序的队列,且在这个队列上每次pop()总是返回优先级最高的那个元素;
heapq.heappush(heap, item) -> None. Push item onto heap, maintaining the heap invariant.
heapq.heappop().
push和pop操作时间复杂度为O(logN),N是堆的大小,因此即使N很大运行速度仍然很快;
结合下例,这2函数分别在队列_queue上插入和删除第一个元素,且队列_queue保证第一个元素拥有最小优先级,因为heapq.heappop()返回的结果是最高优先级;
若要在多线程中使用同一个队列,需要增加适当的锁和信号量机制;
In [178]: import heapq
In [179]: class PriorityQueue:
...: def __init__(self):
...: self._queue = []
变量的作用是保证同等优先级元素的正确排序,通过保存一个不断增加的下标变量,可确保元素按照它们插入的顺序排序,且_index变量也在相同优先级元素比较时起到重要作用
...: def push(self, item, priority):
元组中,优先级为负数的目的是使得元素按照优先级从高到低排序,这跟普通的按优先级从低到高排序的堆排序恰巧相反;
...: self._index += 1
...: def pop(self):
...: return heapq.heappop(self._queue)[-1]
...:
In [180]: class Item: #该实例不可排序,若Item('foo') < Item('bar')会出错;但(1, Item('foo'))< (5, Item('bar'))则可以,如果(1, Item('foo')) < (1, Item('bar'))会报错,通过再引入index则避免这个报错,(1, 0, Item('foo')) < (1,1,Item('bar'))
...: def __init__(self, name):
...: self.name = name
...: def __repr__(self):
...: return 'Item({!r})'.format(self.name)
...:
In [181]: q = PriorityQueue()
In [182]: q.push(Item('foo'), 1)
In [183]: q.push(Item('bar'), 5)
In [184]: q.push(Item('span'), 4)
In [185]: q.push(Item('grok'), 1)
In [186]: q.pop()
Out[186]: Item('bar')
In [187]: q.pop()
Out[187]: Item('span')
In [188]: q.pop()
Out[188]: Item('foo')
In [189]: q.pop()
Out[189]: Item('grok')
6、字典中的键映射多个值(multidict)collections.defaultdict
用途:数据处理中的记录归类;
from collections import defaultdict
In [26]: from collections import defaultdict
In [27]: d = defaultdict(list)
In [28]: d['a'].append(1)
In [29]: d['a'].append(2)
In [30]: d['b'].append(4)
In [31]: d
Out[31]: defaultdict(list, {'a': [1, 2], 'b': [4]})
In [32]: d2 = defaultdict(set)
In [33]: d2['a'].add(1)
In [34]: d2['a'].add(2)
In [35]: d2['b'].add(4)
In [36]: d2
Out[36]: defaultdict(set, {'a': {1, 2}, 'b': {4}})
In [40]: d
Out[40]: {}
In [41]: d.setdefault('a',[]).append(1)
In [42]: d.setdefault('a',[]).append(2)
In [43]: d.setdefault('b',[]).append(4)
In [44]: d
Out[44]: {'a': [1, 2], 'b': [4]}
7、字典排序(有序字典)collections.OrderedDict
用途:精确控制以josn编码后字段的顺序
from collections import OrderedDict # 内部维护着一个双向链表,且该大小是普通字典的2倍,要考虑消耗内存情况
In [46]: d = OrderedDict()
In [47]: d['foo']=1
In [48]: d['bar']=2
In [49]: d['span']=3
In [50]: d['grok']=4
In [51]: d
Out[51]: OrderedDict([('foo', 1), ('bar', 2), ('span', 3), ('grok', 4)])
8、字典运算
在字典中执行计算(最小值|最大值|排序等)
在一个字典上执行普通的数学运算,仅能作用于键,而不能作用于值,对于如下场景使用zip()将值和键反转;
当通过zip()将字典的key和value反转后,在使用min()或max()计算时,若有多个value相同,再去比较key;
In [53]: prices = {
...: 'ACME': 45.23,
...: 'AAPL': 612.78,
...: 'IBM': 205.55,
...: 'HPQ': 37.20,
...: 'FB': 10.75
...: }
In [54]: min_price = min(zip(prices.values(), prices.keys()))
InIn [55]: min_price
Out[55]: (10.75, 'FB')
In [56]: max_price = max(zip(prices.values(), prices.keys()))
In [57]: max_price
Out[57]: (612.78, 'AAPL')
In [59]: zip(prices.values(), prices.keys())
Out[59]: <zip at 0x256bc2eda88>
In [60]: iter(zip(prices.values(), prices.keys()))
Out[60]: <zip at 0x256bc3741c8>
In [61]: for i in zip(prices.values(), prices.keys()): # zip()创建的是一个只能访问一次的迭代器
...: print(i)
...:
(45.23, 'ACME')
(612.78, 'AAPL')
(205.55, 'IBM')
(37.2, 'HPQ')
(10.75, 'FB')
In [64]: min(prices.values())
Out[64]: 10.75
In [65]: max(prices.values())
Out[65]: 612.78
In [66]: min(prices)
Out[66]: 'AAPL'
In [67]: max(prices)
Out[67]: 'IBM'
In [68]: min(prices, key=lambda k: prices[k]) # 通过key参数可对字典的值进行计算,而返回字典的key信息
Out[68]: 'FB'
In [69]: max(prices, key=lambda k: prices[k])
Out[69]: 'AAPL'
In [70]: prices = { # 当value相同时,才比较key
...: 'AAA': 45.23,
...: 'ZZZ': 45.23
...: }
In [71]: min(zip(prices.values(), prices.keys()))
Out[71]: (45.23, 'AAA')
In [72]: max(zip(prices.values(), prices.keys()))
Out[72]: (45.23, 'ZZZ')
9、查找2字典的相同点
在2字典的keys()和items()结果上执行集合操作;
字典的key,有一特性是也支持集合操作,不用先转换成一个set,用.keys()直接进行操作;
字典的.items()方法返回(k,v),这个对象也支持集合操作;
字典的.values()不支持集合操作,原因不能保证所有值是不相同的,要先转为set再进行集合操作;
In [74]: a = {
...: 'x': 1,
...: 'y': 2,
...: 'z': 3
...: }
In [75]: b = {
...: 'w': 10,
...: 'x': 11,
...: 'y': 2
...: }
In [76]: a.keys()
Out[76]: dict_keys(['x', 'y', 'z'])
In [77]: b.keys()
Out[77]: dict_keys(['w', 'x', 'y'])
In [78]: a.keys() & b.keys()
Out[78]: {'x', 'y'}
In [79]: a.keys() - b.keys()
Out[79]: {'z'}
In [80]: a.items() & b.items()
Out[80]: {('y', 2)}
In [82]: {k:a[k] for k in a.keys() - {'z', 'w'}} #字典生成式
Out[82]: {'x': 1, 'y': 2}
10、删除序列中相同元素并保持顺序
对于可hash的值:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item #使用生成器函数,使得更加通用,不仅仅局限于列表处理,也可用于读取文件消除重复行
seen.add(item)
In [88]: def dedupe(items):
...: seen = set()
...: for item in items:
...: if item not in seen:
...: yield item
...: seen.add(item)
...:
In [89]: a = [1,5,2,1,9,1,2,5,10]
In [90]: list(dedupe(a))
Out[90]: [1, 5, 2, 9, 10]
对于不可hash的值:
In [91]: def dedupe(items, key=None):
...: seen = set()
...: for item in items:
...: val = item if key is None else key(item)
...: if val not in seen:
...: yield item
...: seen.add(val)
...:
In [92]: a = [{'x':1, 'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
In [93]: list(dedupe(a,key=lambda d: (d['x'],d['y'])))
Out[93]: [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
In [94]: list(dedupe(a, key=lambda d: d['x'])) # 适合场景:基于某个字段|属性,或更大的数据结构来消除重复元素
Out[94]: [{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
In [96]: a = [1,5,2,1,9,1,2,5,10]
In [97]: set(a) # 若仅是消除重复元素,用集合,这种不能维护元素的顺序
Out[97]: {1, 2, 5, 9, 10}
11、命名切片slice(start,stop,step)
避免大量无法理解的硬编码下标,使代码更加清晰可读;
slice(start, stop[, step])内置函数,创建一个切片对象,可被用作任何切片允许使用的地方,通过.start|.stop|.step访问其属性;
In [101]: record='....................100 .......513.25 ..........'
In [102]: SHARES = slice(20,23)
In [103]: PRICE = slice(31,37)
In [104]: cost = int(record[SHARES]) * float(record[PRICE])
In [105]: cost
Out[105]: 51325.0
In [106]: SHARES
Out[106]: slice(20, 23, None)
In [107]: PRICE
Out[107]: slice(31, 37, None)
In [108]: record[SHARES]
Out[108]: '100'
In [110]: a = slice(5,50,2)
In [111]: a.start
Out[111]: 5
In [112]: a.stop
Out[112]: 50
In [113]: a.step
Out[113]: 2
12、序列中出现次数最多的元素collections.Counter
from collections import Counter # Counter对象可接受任意的hashable序列对象,底层实现上一个Counter对象就是一个字典,将元素映射到它出现的次数上;
用途:Counter对象几乎所有需要制表或计数数据的场合都可用,且优先选择使用Counter,而不是手动的利用字典实现;
In [116]: words = [
...: 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
...: 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
...: 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
...: 'my', 'eyes', "you're", 'under'
...: ]
In [117]: from collections import Counter
In [118]: word_counts = Counter(words)
In [119]: word_counts #若要手动增加计数,用word_counts['no']+=1或update()方法
Out[119]:
Counter({'look': 4,
'into': 3,
'my': 3,
'eyes': 8,
'the': 5,
'not': 1,
'around': 2,
"don't": 1,
"you're": 1,
'under': 1})
In [120]: top_three = word_counts.most_common(3)
In [121]: top_three
Out[121]: [('eyes', 8), ('the', 5), ('look', 4)]
13、通过某个关键字排序一个字典列表operator.itemgetter
from operator import itemgetter
In [126]: rows = [
...: {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
...: {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
...: {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
...: {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
...: ]
In [127]: rows_by_fname = sorted(rows, key=itemgetter('fname')) #同lambda表达式rows_by_fname = sorted(rows, key=lambda r: r['fname']),itemgetter()效率更高且支持多个key比较;sorted()或min()或max()
In [128]: rows_by_fname
Out[128]:
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
In [129]: rows_by_uid = sorted(rows, key=itemgetter('uid'))
In [130]: rows_by_uid
Out[130]:
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
In [131]: rows_by_lfname = sorted(rows, key=itemgetter('lname','fname')) # itemgetter()支持多个key
In [132]: rows_by_lfname
Out[132]:
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
14、排序不支持原生比较的对象operator.attrgetter
from operator import attrgetter
In [135]: class User:
...: def __init__(self, user_id):
...: self.user_id = user_id
...: def __repr__(self):
...: return 'User({})'.format(self.user_id)
...:
In [136]: users = [User(23),User(3),User(99)]
In [137]: sorted(users, key=lambda u: u.user_id)
Out[137]: [User(3), User(23), User(99)]
In [138]: from operator import attrgetter
In [139]: sorted(users, key=attrgetter('user_id'))
Out[139]: [User(3), User(23), User(99)]
15、通过某个字段将记录分组itertools.groupby
from operator import itemgetter
from itertools import groupby
groupby()扫描整个序列并且查找连续相同值的元素序列,在每次迭代时,它会返回一个值和一个迭代器对象;
一个重要准备是先要根据指定的字段将数据排序,因为groupby()仅检查连续的元素;
In [140]: rows = [
...: {'address': '5412 N CLARK', 'date': '07/01/2012'},
...: {'address': '5148 N CLARK', 'date': '07/04/2012'},
...: {'address': '5800 E 58TH', 'date': '07/02/2012'},
...: {'address': '2122 N CLARK', 'date': '07/03/2012'},
...: {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
...: {'address': '1060 W ADDISON', 'date': '07/02/2012'},
...: {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
...: {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
...: ]
In [142]: rows.sort(key=itemgetter('date')) # 先按指定的字段排序,再调用groupby()
In [145]: for date, items in groupby(rows, key=itemgetter('date')):
...: print(date)
...: for i in items:
...: print(' ', i)
...:
07/01/2012
{'address': '5412 N CLARK', 'date': '07/01/2012'}
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
{'address': '5800 E 58TH', 'date': '07/02/2012'}
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
{'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
{'address': '5148 N CLARK', 'date': '07/04/2012'}
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
In [146]: from collections import defaultdict #若仅仅是想根据date字段将数据分组到一个大的数据结构中去,且允许随机访问,用defaultdict()来构建一个多值字典,有用先对rows进行排序
In [147]: rows_by_date = defaultdict(list)
In [148]: rows_by_date
Out[148]: defaultdict(list, {})
In [149]: rows
Out[149]:
[{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}]
In [150]: for row in rows:
...: rows_by_date[row['date']].append(row)
...:
In [151]: rows_by_date
Out[151]:
defaultdict(list,
{'07/01/2012': [{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}],
'07/02/2012': [{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'}],
'07/03/2012': [{'address': '2122 N CLARK', 'date': '07/03/2012'}],
'07/04/2012': [{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}]})
In [152]: for r in rows_by_date['07/02/2012']:
...: print(r)
...:
{'address': '5800 E 58TH', 'date': '07/02/2012'}
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
16、过滤序列元素
列表推导(列表生成式),缺陷长度很长时占内存;
生成器表达式;
列表推导和生成器表达式是过滤数据最简单的方式,还可在过滤时转换数据;过滤操作的一个变种就是将不符合条件的值用新的值代替而不是丢弃它们,如在一列数据中不仅想要正数,且还想将不是正数的数替换成指定的数;
过滤规则复杂时(如过滤时需要处理一些异常或其它复杂情况),将过滤代码放到一个函数中,使用内建filter()函数;
In [153]: lst = [1,4,-5,10,-7,2,3,-1]
In [154]: [n for n in lst if n>0]
Out[154]: [1, 4, 10, 2, 3]
In [155]: [n for n in lst if n<0]
Out[155]: [-5, -7, -1]
In [156]: pos = (n for n in lst if n>0)
In [157]: pos
Out[157]: <generator object <genexpr> at 0x00000256BC2A8888>
In [158]: for x in pos:
...: print(x)
...:
1
4
10
2
3
In [159]: values = ['1','2','-3','-','4','N/A','5']
In [160]: def is_int(val):
...: try:
...: x = int(val)
...: return True
...: except ValueError:
...: return False
...:
In [161]: ivals = list(filter(is_int, values)) #filter()创建一个迭代器
In [162]: ivals
Out[162]: ['1', '2', '-3', '4', '5']
In [166]: lst = [1,4,-5,10,-7,2,3,-1]
In [167]: import math
In [168]: [math.sqrt(n) for n in lst if n>0]
Out[168]: [1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
In [169]: clip_neg = [n if n>0 else 0 for n in lst]
In [170]: clip_neg
Out[170]: [1, 4, 0, 10, 0, 2, 3, 0]
In [171]: addresses = [
...: '5412 N CLARK',
...: '5148 N CLARK',
...: '5800 E 58TH',
...: '2122 N CLARK'
...: '5645 N RAVENSWOOD',
...: '1060 W ADDISON',
...: '4801 N BROADWAY',
...: '1039 W GRANVILLE',
...: ]
In [172]: counts = [ 0, 3, 10, 4, 1, 7, 6, 1]
In [173]: from itertools import compress
In [174]: more5 = [n>5 for n in counts]
In [175]: more5
Out[175]: [False, False, True, False, False, True, True, False]
In [176]: list(compress(addresses, more5)) # compress()也是返回一个迭代器
Out[176]: ['5800 E 58TH', '4801 N BROADWAY', '1039 W GRANVILLE']
17、从字典中提取子集:
字典推导,或通过创建一个元组序列然后把它传给dict(),字典推导要比dict()运行快一倍;
In [179]: prices = {
...: 'ACME': 45.23,
...: 'AAPL': 612.78,
...: 'IBM': 205.55,
...: 'HPQ': 37.20,
...: 'FB': 10.75
...: }
In [180]: p1 = {k:v for k,v in prices.items() if v>200}
In [181]: p1
Out[181]: {'AAPL': 612.78, 'IBM': 205.55}
In [182]: tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
In [183]: p2 = {k:v for k,v in prices.items() if k in tech_names}
In [184]: p2
Out[184]: {'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}
In [185]: p2 = {k:prices[k] for k in prices.keys() & tech_names}
In [186]: p2
Out[186]: {'AAPL': 612.78, 'HPQ': 37.2, 'IBM': 205.55}
18、映射名称到序列元素collections.namedtuple
from collections import namedtuple
用途:
将代码从下标操作中解脱出来;
作为字典的替代,字典存储需要更多的内存空间,如果需要构建一个非常大的包含字典的数据结构,使用命名元组更加高效;
如果是要定义一个需要更新很多实例属性的高效数据结构,命名元组并不是最佳选择,可考虑定义一个包含__slots__方法的类;
注意:
命名元组不可更改,如要更改要用实例的_replace(),它会创建一个全新的命名元组并将对应的字段用新的值取代;
_replace()有用的特性,当你的命名元组拥有可选或缺失字段时,它是一个非常方便的填充数据的方法,可先创建一个包含缺少值的原型元组,然后使用_replace()创建新的值被更新过的实例;
In [187]: def compute_cost(records):
...: total = 0.0
...: for rec in records:
...: total += rec[1] * rec[2]
...: return total
In [195]: compute_cost(((8,9,10),)) #普通元组代码
Out[195]: 90.0
Stock = namedtuple('Stock', ['name', 'shares', 'price'])
In [198]: def compute_cost(records):
...: total = 0.0
...: for rec in records:
...: s = Stock(*rec)
...: total += s.shares * s.price
...: return total
In [200]: compute_cost((('jowin',88,10),)) # 使用命名元组版本
Out[200]: 880.0
In [201]: s = Stock('jowin',100,123.45)
In [202]: s
Out[202]: Stock(name='jowin', shares=100, price=123.45)
In [203]: s.shares # 不可更改,如果更改要用实例的_replace()方法
Out[203]: 100
In [204]: s = s._replace(shares=75)
In [205]: s
Out[205]: Stock(name='jowin', shares=75, price=123.45)
In [206]: Stock = namedtuple('Stock',['name','shares','price','date','time'])
In [207]: stock_prototype = Stock('', 0, 0.0, None, None)
In [208]: def dict_to_stock(s):
...: return stock_prototype._replace(**s)
...:
In [209]: a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
In [210]: dict_to_stock(a)
Out[210]: Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
19、转换并同时计算数据,生成器表达式
用优雅的方式,即生成器表达式,比先创建一个临时列表更加高效和优雅;
s = sum(x*x for x in nums) #这种方式更优雅;而不必sum((x*x for x in nums))多加一对括号,显式地传递一个生成器表达式对象
s = sum([x*x for x in nums]) #这种会多一步骤,即先创建一个额外的列表,当列表非常大时仅被使用一次就被丢弃,而生成器方案会以迭代的方式转换数据,因此更省内存
In [212]: nums = [1,2,3,4,5]
In [213]: s = sum(x*x for x in nums) #这种方式更优雅;而不必sum((x*x for x in nums))多加一对括号,显式地传递一个生成器表达式对象
In [214]: s
Out[214]: 55
In [217]: files = os.listdir(r'E:/')
In [221]: if any(name.endswith('.py') for name in files):
...: print('there be python')
...: else:
...: print('no python')
...:
no python
In [222]: if any(name.endswith('end') for name in files):
...: print('there be python')
...: else:
...: print('no python')
...:
there be python
In [223]: s = ('ACME',50,123,45)
In [224]: print(','.join(str(x) for x in s)) # csv
ACME,50,123,45
In [226]: portfolio = [
...: {'name':'GOOG', 'shares': 50},
...: {'name':'YHOO', 'shares': 75},
...: {'name':'AOL', 'shares': 20},
...: {'name':'SCOX', 'shares': 65}
...: ]
In [227]: min_shares = min(s['shares'] for s in portfolio)
In [228]: min_shares
Out[228]: 20
In [229]: min_shares = min(portfolio, key=lambda s: s['shares']) #min()|max()|sorted(),使用key参数更佳
In [230]: min_shares
Out[230]: {'name': 'AOL', 'shares': 20}
20、合并多个字典或映射collections.ChainMap
from collection import ChainMap # 一个ChainMap接受多个字典并将它们在逻辑上变为一个字典,然后这些字典并不是真的合并在一起了,ChainMap类只是在内部创建了一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表,大部分字典操作可用;如果有重复key,返回第一次出现的映射值;对于字典的更新或删除,始终影响的是列表中第一个字典;
若使用update()将2字典合并,要创建一个完全不同的字典对象,如果源字典做了更新,这种改变不会反应到新的合并字典中去,merged = dict(b);
ChainMap使用原来的字典,它自己不创建新的字典,源字典a中的数据更新会影响merged结果,merged = ChainMap(a, b),
用途:
对于编程中的作用域,globals|locals是非常有用的,values = ChainMap(),values['x']=1,values = values.new_child(),values= values.parents;
In [1]: from collections import ChainMap
In [2]: a = {'x':1, 'z':3}
In [3]: b = {'y':2, 'z':4}
In [4]: c = ChainMap(a,b)
In [5]: c['x']
Out[5]: 1
In [6]: c['y']
Out[6]: 2
In [7]: c['z']
Out[7]: 3
In [9]: len(c)
Out[9]: 3
In [10]: list(c.keys())
Out[10]: ['x', 'z', 'y']
In [11]: list(c.values())
Out[11]: [1, 3, 2]
In [12]: c['z'] = 10
In [13]: c['w'] = 40
In [14]: c
Out[14]: ChainMap({'x': 1, 'z': 10, 'w': 40}, {'y': 2, 'z': 4})
In [15]: del c['x']
In [16]: a
Out[16]: {'z': 10, 'w': 40}
In [17]: b
Out[17]: {'y': 2, 'z': 4}
In [18]: del c['y']
……
KeyError: "Key not found in the first mapping: 'y'"
In [19]: a
Out[19]: {'z': 10, 'w': 40}
In [20]: b
Out[20]: {'y': 2, 'z': 4}
In [22]: merged = dict(b) #创建一个新的字典对象
In [23]: merged.update(a)
In [24]: merged['z']
Out[24]: 10
In [25]: merged['y']
Out[25]: 2
In [26]: merged['w']
Out[26]: 40
In [27]: a['z'] = 8
In [28]: merged['z']
Out[28]: 10
In [29]: a
Out[29]: {'z': 8, 'w': 40}
In [30]: b
Out[30]: {'y': 2, 'z': 4}
In [31]: merged = ChainMap(a, b) #ChainMap引用原来的字典对象
In [32]: merged['z']
Out[32]: 8
In [33]: a['z'] = 88
In [34]: merged['z']
Out[34]: 88
二、字符串和文本
1、使用多个界定符分割字符串re.split()
re.split() #返回字段列表同str.split()
如果想保留分割字符,使用捕获分组;
In [35]: line = 'test test1; test2, test3,test4, test5'
In [36]: import re
In [37]: re.split(r'[;,\s]\s*', line)
Out[37]: ['test', 'test1', 'test2', 'test3', 'test4', 'test5']
In [38]: fields = re.split(r'(;|,|\s)\s*', line) #捕获分组,保留分割字符串
In [39]: fields
Out[39]: ['test', ' ', 'test1', ';', 'test2', ',', 'test3', ',', 'test4', ',', 'test5']
In [40]: values = fields[::2]
In [41]: values
Out[41]: ['test', 'test1', 'test2', 'test3', 'test4', 'test5']
In [42]: delimiters = fields[1::2] + ['']
In [43]: ''.join(v+d for v,d in zip(values, delimiters))
Out[43]: 'test test1;test2,test3,test4,test5'
In [44]: re.split(r'(?:,|;|\s)\s*', line) #获取非捕获分组,(?:)
Out[44]: ['test', 'test1', 'test2', 'test3', 'test4', 'test5']
2、字符串开头或结尾匹配str.startswith()|str.endswith()
str.startswith()
str.endswith()
可用切片实现,没上面优雅;
可用re.match('www.python.org'
In [49]: url.startswith('www.python.org'
In [49]: url.startswith('www.python.org') #启动一个浏览器,使用默认浏览器打开指定网页,与平台无关
webbrowser.open_new() #对网页打开方式做更多控制
webbrowser.open_new_tab()
c = webbrowser.get('firefox') #指定浏览器
c.open()
c.open_new_tab()
八、类与对象
1、改变对象的字符串显示__str__()|__repr__()
__repr__() #返回一个实例的代码表示形式,通常用来重新构造这个实例,内置的repr()返回这个字符串,跟用交互式解释器显示的值是一样的
__str__() #将实例转换为一个字符串,使用str()或print()会输出这个字符串,如果该方法未定义就会使用__repr__()来代替输出
自定义__repr__()和__str__()通常是好的习惯,因为它能简化调试和实例输出,会看到实例更加详细与有用的信息;
'{!r}'.format() #格式化时的!r表示指明输出使用__repr__()来代替默认的__str__()
In [327]: class Pair:
...: def __init__(self, x, y):
...: self.x = x
...: self.y = y
...: def __repr__(self):
指代self
...: def __str__(self):
...: return '({0.x!s}, {0.y!s})'.format(self)
...:
In [328]: p = Pair(3,4)
In [329]: p
Out[329]: Pair(3, 4)
In [330]: print(p)
(3, 4)
2、自定义字符串的格式化__format__()
__format__() #此方法给py的字符串格式化功能提供了一个钩子,着重强调的是格式化代码的解析工作完全由类自己决定,因此格式化代码可以是任何值,参照datetime.date
In [334]: _formats = {'ymd': '{d.year}-{d.month}-{d.day}','mdy': '{d.month}/{d.day}/{d.
...: year}','dmy': '{d.day}/{d.month}/{d.year}'}
In [341]: class Date:
...: def __init__(self, year, month, day):
...: self.year = year
...: self.month = month
...: self.day = day
...: def __format__(self, code):
...: if code == '':
...: code = 'ymd'
...: fmt = _formats[code]
...: return fmt.format(d=self)
...:
In [342]: d = Date(2019, 11, 16)
In [343]: format(d)
Out[343]: '2019-11-16'
In [344]: 'The date is {:mdy}'.format(d)
Out[344]: 'The date is 11/16/2019'
In [345]: 'The date is {:dmy}'.format(d)
Out[345]: 'The date is 16/11/2019'
In [346]: from datetime import date
In [347]: d = date(2019, 11, 16)
In [348]: format(d)
Out[348]: '2019-11-16'
In [349]: format(d, '%A, %B, %d, %Y')
Out[349]: 'Saturday, November, 16, 2019'
In [350]: 'The end is {:%d %b %Y}. Goodbye'.format(d)
Out[350]: 'The end is 16 Nov 2019. Goodbye'
3、让对象支持上下文管理协议__enter__()|__exit__()|contextmanager
为兼容with语句,需实现__enter__()和__exit__();
当出现with语句时,对象的__enter__()被触发,它返回的值被赋值给as声明的变量,然后with语句块里的代码开始执行,最后__exit__()被触发进行清理工作;
不管with代码块中发生什么,LazyConnection中的控制流都会执行完,就算代码块中发生异常也一样,__exit__()中第三个参数包含了异常类型|异常值|追溯信息,__exit__()方法能自己决定怎样利用这个异常信息,或忽略它返回None,如果__exit__()返回True那么异常会被清空,好像什么都没发生一样,with语句后的程序继续正常执行;
在需要管理一些资源,如文件|网络连接|锁的编程环境中,使用上下文管理器很普遍,这些资源的一个重要特征是它们必须被手动的关闭或释放来确保程序的正确运行,如请求了一个锁那就必须确保之后要释放它,否则可能会产生死锁;
在contextmanager模块中有一个标准的上下文管理方案模板可参考;
In [351]: from socket import socket, AF_INET, SOCK_STREAM
In [352]: class LazyConnection: #此类表示了一个网络连接,初始化时并没建立连接,连接的建立和关闭是用with语句自动完成的;此例只能允许一个socket连接,如果正在使用一个socket时又重复使用with语句就会产生异常
...: def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
...: self.address = address
...: self.family = family
...: self.type = type
...: self.sock = None
...: def __enter__(self):
...: if self.sock is not None:
...: raise RuntimeError('Already connected')
...: self.sock = socket(self.family, self.type)
...: self.sock.connect(self.address)
...: return self.sock
...: def __exit__(self, exc_ty, exc_val, tb):
...: self.sock.close()
...: self.sock = None
...:
In [3]: class LazyConnection: #支持多个嵌套with语句,LazyConnection被看作是连接工厂,在内部列表被用来构造一个栈,每次__enter__()执行时它复制创建一个新的连接,并将其加入到栈里面,__exit__()简单的从栈中弹出最后一个连接并关闭它
...: def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
...: self.address = address
...: self.family = family
...: self.type = type
...: self.connections = []
...: def __enter__(self):
...: sock = socket(self.family, self.type)
...: sock.connect(self.address)
...: self.connections.append(sock)
...: return sock
...: def __exit__(self, exc_ty, exc_val, tb):
...: self.connections.pop().close()
...:
In [4]: from functools import partial
In [5]: conn = LazyConnection(('www.python.org', 80))
In [6]: with conn as s1:
...: pass
...: with conn as s2:
...: pass
...:
4、创建大量对象时节省内存的方法__slots__
__slots__ #定义该属性后,py就会为实例使用一种更加紧凑的内部表示,实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟tuple|list类似,在__slots__中列出的属性名在内部被映射到这个数组的指定小标上;使用__slots__不好处是,不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名
In [7]: class Date:
...: __slots__ = ['year', 'month', 'day']
...: def __init__(self, year, month, day):
...: self.year = year
...: self.month = month
...: self.day = day
...:
5、在类中封装属性名_
_开头的命名 #任何以_开头的名字都应该是内部实现,同样也适用于模块名和模块级别函数(模块级别函数在使用时比sys._getframe()得加倍小心),py并不会真的阻止访问这些属性,但如果这么做不好可能会导致脆弱的代码
__开头的命名 #使用__开始会导致名称变成其它形式,如类B和类C中,_B__private|_B__private_method和_C__private|_C__private_method是不一样的,目的是继承用,这种属性通过继承是无法被覆盖的
使用注意:
大多数情况下,非公共名称以_开头方案;
如果代码涉及到子类,且有些内部属性应在子类中隐藏,才考虑使用__方案;
如果定义的一个变量和某个保留关键字冲突,使用_作为后缀,如lambda_ = 2.0,这里不使用_前缀的原因是避免误解它的使用初衷(使用_前缀的目的是为了防止命名冲突而不是指明这个属性是私有的)
In [8]: class A:
...: def __init__(self):
...: self._internal = 0
...: self.public = 1
...: def public_method(self):
...: pass
...: def _internal_method(self):
...: pass
...:
In [9]: class B:
...: def __init__(self):
...: self.__private = 0
...: def __private_method(self):
...: pass
...: def public_method(self):
...: pass
...: self.__private_method()
...:
In [11]: class C(B):
...: def __init__(self):
...: self.__private = 0
...: def __private_method(self):
...: pass
...: def public_method(self):
...: pass
...: self.__private_method()
...:
6、创建可管理的属性@property
@property #的一个关键特征是它看上去跟普通的attribute没区别,但访问它时会自动触发getter|setter|deleter方法;
一个property属性其实就是一系列相关方法的集合,查看拥有property的类,发现property本身的fget|fset|fdel属性就是类里面的普通方法,通常不会直接调用fget|fset,它会在访问property时自动被触发;
只有当需要对attribute执行其它额外操作时才应使用property,不要写没做任何其它额外操作的property,1代码臃肿易出错丑陋且会迷惑阅读者,2程序运行会变慢,3 P241;
property还是一种定义动态计算attribute的方法,这种类型的attribute并不会被实际的存储,而是在需要时计算出来,对半径|直径|周长|面积的访问都通过属性访问,跟访问简单的attribute一样,如:
@property
def area(self):
return math.pi * self.radius ** 2
In [12]: class Person:
...: def __init__(self, first_name):
...: self.first_name注意此处为self.first_name不是self._first_name,是想在初始化时也进行类型检查,初始化时会自动调用setter方法进行参数检查
...: @property
...: def first_name(self): #这3个方法的名字必须一样,依次为getter|setter|deleter函数,getter函数使得first_name成为一个属性,只有在getter被创建后setter和deleter才能被定义
...: return self._first_name
...: @first_name.setter
...: def first_name(self, value):
...: if not isinstance(value, str):
...: raise TypeError('Expected a string')
...: self._first_name = value
...: @first_name.deleter
...: def first_name(self):
...: raise AttributeError("Can't delete attribute")
...:
In [13]: a = Person('Guido')
In [14]: a.first_name
Out[14]: 'Guido'
In [15]: a.first_name = 42
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-15-6341bdece797> in <module>
----> 1 a.first_name = 42
<ipython-input-12-d496c776c69d> in first_name(self, value)
8 def first_name(self, value):
9 if not isinstance(value, str):
---> 10 raise TypeError('Expected a string')
11 self._first_name = value
12 @first_name.deleter
TypeError: Expected a string
In [16]: del a.first_name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-50ce9077a584> in <module>
----> 1 del a.first_name
<ipython-input-12-d496c776c69d> in first_name(self)
12 @first_name.deleter
13 def first_name(self):
---> 14 raise AttributeError("Can't delete attribute")
15
AttributeError: Can't delete attribute
In [23]: Person.first_name.fget
Out[23]: <function __main__.Person.first_name(self)>
In [24]: Person.first_name.fset
Out[24]: <function __main__.Person.first_name(self, value)>
In [25]: Person.first_name.fdel
Out[25]: <function __main__.Person.first_name(self)>
7、调用父类的方法super()
要正确使用super(),super().__init__(),不要直接用Base.__init__(self),在复杂情况下如多继承代码中就可能导致问题发生,比如父类Base.__init__()会被调用多次;
原因是py实现多继承的原理:
对于定义的每一个类,py会计算出一个所谓的方法解析顺序MRO列表(C.__mro__是一个简单的所有基类的线性顺序表),为实现继承py会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止;
这个MRO列表的构造是通过一个C3线性算法来实现的,实际上是合并所有父类的MRO列表并遵循3条准则(子类会先于父类被检查|多个父类会根据它们在列表中的顺序被检查|如果对下一个类存在两个合法的选择会选择第一个父类);
需要知道的是MRO列表中的类顺序会让我们定义的任意类层次级关系变得有意义;
使用super()时,py会在MRO列表上继续搜索下一个类,只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用1次;
P246
用法1,在__init__()确保父类被正确的初始化了;
用法2,出现在覆盖py特殊方法的代码中;
8、子类中扩展property
P250
9、创建新的类或实例属性__get__()|__set__()|__delete__()
创建一个新的拥有一些额外功能的实例属性类型,如类型检查;
一个描述器就是一个实现了3个核心的属性访问操作(get|set|delete)的类,分别为__get__()|__set__()|__delete__()这三个特殊的方法,这些方法接受一个实例作为输入,之后相应的操作实例底层的__dict__;
描述器可实现大部分py类特性中的底层魔法,包括@classmethod|@staticmethod|@property甚至是__slots__;
通过定义一个描述器,可在底层捕获核心的实例操作(get|set|delete),且可完全自定义它们的行为,这是一个强大的工具,有了它就可实现很多高级功能,并且它也是很多高级库和框架中的重要工具之一;
描述器的一个比较困惑的地方是它只能在类级别被定义,而不能为每个实例单独定义;
描述器通常是那些使用到装饰器或元类的大型框架中的一个组件,同时它们的使用也被隐藏在后面;
如果一个描述器仅定义了一个__get__(),它比通常的具有更弱的绑定,只有当被访问属性不在实例底层的__dict__时__get__()方法才会被触发;
P252
10、使用延迟计算特性_描述器类
P254
如果一个描述器仅定义了一个__get__(),它比通常的具有更弱的绑定,只有当被访问属性不在实例底层的__dict__时__get__()方法才会被触发;
11、简化数据结构的初始化
P258
12、定义接口或抽象基类abc
定义一个接口或抽象类,通过执行类型检查来确保子类实现了某些特定的方法;
from abc import ABCMeta, abstractmethod
抽象类的1个特点是,它不能直接被实例化;
抽象类的目的是,让别的类继承它并实现特定的抽象方法;
抽象类的主要用途,在代码中检查某些类是否为特定类型,实现了特定接口;
13、实现数据模型的类型约束
五、文件与IO
1、读写文本数据open()
rt #读取文本文件
wt #写入一个文本文件
at #在已存在文件中添加内容
encoding='utf-8' #如果确定读写的文本是其它编码使用encoding指定,py支持的有ascii|latin-1|utf-8|utf-16,web应用程序中通常使用的都是utf-8
In [37]: sys.getdefaultencoding() #文件的读写操作使用的系统编码
Out[37]: 'utf-8'
注意:
1、在没用with语句时,要记得手动关闭文件,f = open('somefile.txt', 'rt'),data = f.read(),f.close();
2、换行符的识别,unix中是\n,win中是\r\n,默认py会以统一模式处理换行符,这种模式下,在读取文本时,py可识别所用的普通换行符并将其转换为单个\n字符,在输出时会将换行符\n转换为系统默认的换行符,如果不希望这种默认的处理方式,可给open()传入参数newline='';
3、文本文件中可能出现的编码错误,最好确认该文件的编码是正确的;
2、打印输出至文件中print('content', file=f)
In [39]: with open(r'c:/test.txt', 'wt') as f:
中指定file关键字参数
...:
3、使用其它分隔符或行终止符打印print('content', sep=',', end='!!\n')
在print()中使用sep和end关键字参数,end参数也可在输出中禁止换行;
In [40]: print('ACME', 50, 91.5)
ACME 50 91.5
In [41]: print('ACME', 50, 91.5, sep=',')
ACME,50,91.5
In [42]: print('ACME', 50, 91.5, sep=',', end='!!\n')
ACME,50,91.5!!
In [43]: for i in range(3):
...: print(i, end=' ')
...:
0 1 2
In [47]: row = ('ACME', 50, 91.5)
In [48]: print(*row, sep=',') #使用非空格分隔符输出数据,这种方式最简单;如果用str.join(lst),lst中的元素必须都为字符串,如果不是得转换,这样麻烦
ACME,50,91.5
In [49]: print(','.join(str(x) for x in row))
ACME,50,91.5
4、读写字节数据open()
rb #open()使用的模式,以二进制方式读取数据
wb
在读取二进制数据时:
字节字符串和文本字符串的语义差异可能会导致一个潜在的陷阱,需要注意,索引和迭代动作返回的是字节的值而不是字节字符串;
所有返回的数据都是字节字符串格式,而不是文本字符串,类似在写入时,必须保证参数是以字节形式对外暴露数据的对象,如字节字符串|字节数组对象等;
如果想从二进制模式的文件中读取或写入文本数据,必须确保要进行解码和编码操作;
二进制i/o还有一个鲜为人知的特性是数组和C结构体类型能直接被写入,而不需要中间转换为自己对象,这个适用于任何实现了“缓冲接口”的对象,这种对象会直接暴露其底层的内存缓冲区给能处理它的操作,二进制数据的写入就是这类操作之一;
很多对象还允许通过使用文件对象的readinto()方法直接读取二进制数据到其底层的内存中去,使用这种技术时需格外小心,因为它具有平台相关性,可能会依赖字长和字节顺序(高位优先|低位优先);
with open('somefile.bin', 'rb') as f:
data = f.read()
In [50]: b = b'Hello World'
In [52]: for c in b:
...: print(c, end='\t')
...:
...:
72 101 108 108 111 32 87 111 114 108 100
In [53]: import array
In [55]: nums = array.array('i', [1,2,3,4])
In [56]: with open(r'c:/data.bin', 'wb') as f:
...: f.write(nums)
...:
In [57]: a = array.array('i', [0,0,0,0,0,0,0,0])
In [59]: with open(r'c:/data.bin', 'rb') as f:
...: f.readinto(a)
...:
In [60]:
In [60]: a
Out[60]: array('i', [1, 2, 3, 4, 0, 0, 0, 0])
5、文件不存在才能写入open()
x #open()中使用x模式代替w模式,xt|xb,仅py3对open()特有的扩展
解决不小心覆盖一个已存在的文件;
In [64]: if not os.path.exists('somefile'): #这种方式没有open('somefile', 'xt')好
...: with open('somefile', 'wt') as f:
...: f.write('Hello\n')
...: else:
...: print('File alredy exists!')
...:
6、字符串的I/O操作io.StringIO()|io.BytesIO()
使用操作类文件对象的程序来操作文本或二进制字符串;
io.StringIO() #用于文本
io.BytesIO() #用于二进制数据
注意,StringIO|BytesIO实例并没有正确的整数类型的文件描述符,因此它们不能在那些需要使用真实的系统级文件如文件|管道|套接字的程序中使用;
用途:
在单元测试中,可用StringIO来创建一个包含测试数据的类文件对象,该对象可被传给某个参数为普通文件对象的函数;
In [66]: import io
In [67]: s = io.StringIO()
In [68]: s.write('Hello World\n')
Out[68]: 12
In [69]: print('This is a test', file=s)
In [70]: s.getvalue()
Out[70]: 'Hello World\nThis is a test\n'
In [71]: s = io.StringIO('Hello\nWorld\n')
In [72]: s.read(4)
Out[72]: 'Hell'
In [73]: s.read()
Out[73]: 'o\nWorld\n'
7、读写压缩文件gzip.open()|bz2.open()
gzip.open() #默认二进制模式,.open()接受的参数同内置open(),当写入数据时compresslevel=5来指定压缩级别,默认9最高压缩级别,0-9,等级越低性能越好数据压缩程度越低;rt|wt,所有的I/O操作都使用文本模式并执行Unicode编码/解码,如果操作二进制数据,用rb|wb
bz2.open()
gzip.open()|bz2.open()还有一特性是,可作用在一个已存在并以二进制模式打开的文件上,这样就允许gzip和bz2模块可工作在许多类文件对象上,如套接字|管道|内存中文件;
with gzip.open('somefile.gz', 'wt', compresslevel=5) as f:
f.write(text)
import gzip
f = open('somefile.gz', 'rb')
with gzip.open(f, 'rt') as g:
text = g.read()
8、固定大小记录的文件迭代functools.partial()|iter()
from functools import partial
RECORD_SIZE = 32
with open('somefile.data', 'rb') as f: #以二进制模式打开的文件,通常是读取固定大小的记录;对于文本文件,通常是一行一行地读取
对象是一个可迭代对象,它会不断的产生固定大小的数据块,直到文件末尾,注意如果总记录大小不是块大小的整数倍的话,最后一个返回的元素的字节数会比期望值少
for r in records:
......
P145
9、读取二进制数据到可变缓冲区中_f.readinto()
P147
def read_into_buffer(filename):
buf = bytearray(os.path.getsize(filename))
with open(filename, 'rb') as f:
f.readinto(buf)
return buf
>with open('sample.bin', 'wb') as f:
f.write(b'Hello World')
>buf = read_into(buffer('sample.bin')
>buf[0:5] = b'Hallo'
>with open('newsample.bin', 'wb') as f:
f.write(buf)
10、内存映射的二进制文件mmap
P149
11、文件路径名的操作os.path中的函数
os.path #对于任何的文件名的操作,都应使用os.path模块,而不是使用标准字符串操作来构造自己的代码(不应浪费时间重复造轮子),特别是为了可移植性考虑时更应如此
In [74]: path = '/Users/beazley/Data/data.csv'
In [75]: os.path.basename(path)
Out[75]: 'data.csv'
In [76]: os.path.dirname(path)
Out[76]: '/Users/beazley/Data'
In [77]: os.path.join('tmp', 'data', os.path.basename(path))
Out[77]: 'tmp\\data\\data.csv'
In [78]: path = '~/Data/data.csv'
In [79]: os.path.expanduser(path)
Out[79]: 'C:\\Users\\Administrator.SC-201908130831/Data/data.csv'
In [81]: os.path.splitext(path)
Out[81]: ('~/Data/data', '.csv')
12、测试文件是否存在os.path.exists()
操作时注意文件权限问题,尤其是获取元数据时;
In [82]: os.path.exists(r'c:/test.txt')
Out[82]: True
In [83]: os.path.isfile(r'c:/test.txt')
Out[83]: True
In [84]: os.path.isdir(r'c:/test.txt')
Out[84]: False
In [85]: os.path.islink(r'c:/test.txt')
Out[85]: False
In [86]: os.path.realpath('c:/test.txt')
Out[86]: 'c:\\test.txt'
In [87]: os.path.getsize('c:/test.txt')
Out[87]: 14
In [88]: os.path.getmtime('c:/test.txt')
Out[88]: 1574055034.3199937
In [89]: import time
In [90]: time.ctime(os.path.getmtime(r'c:/test.txt'))
Out[90]: 'Mon Nov 18 13:30:34 2019'
13、获取目录中的文件列表os.listdir()
os.listdir() #返回目录中所有文件列表,包括文件|子目录|符号链接等,如果还想获取其它元信息,要用到os.path|os.stat()来收集数据;另,可结合os.path|列表推导过滤数据;os.listdir()返回的实体列表会根据系统默认的文件名编码来解码,但有时也有不能正常解码的文件名
names = [name for name in os.listdir('somedir') if os.path.isfile(os.path.join('somedir', name))]
dirnames = [name for name in os.listdir('somedir') if os.path.isdir(os.path.join('somedir', name))]
pyfiles = [name for name in os.listdir('somedir') if name.endswith('.py')]
import glob
pyfiles = glob.glob('somedir/*.py')
from fnmatch import fnmatch
pyfiles = [name for name in os.listdir('somedir') if fnmatch(name, '*.py')]
pyfiles = glob.glob('*.py')
name_sz_date = [(name, os.path.getsize(name), os.path.getmtime(name)) for name in pyfiles]
for name, size, mtime in name_sz_date:
print(name, size, mtime)
file_metadata = [(name, os.stat(name)) for name in pyfiles]
for name, meta in file_metadata:
print(name, meta.st_size, meta.st_mtime)
14、忽略文件名编码
使用一个原始字节字符串来指定一个文件名即可,如with open('jalape\xf1o.txt', 'w') as f:
In [93]: sys.getfilesystemencoding() #默认所有文件名都会根据此编码和解码
Out[93]: 'utf-8'
In [94]: with open('jalape\xf1o.txt', 'w') as f:
...: f.write('Spicy!')
...:
In [95]: os.listdir('.')
Out[95]:
['chardetect.exe',
'jalapeño.txt',
'somefile']
In [96]: os.listdir(b'.')
Out[96]:
[b'chardetect.exe',
b'jalape\xc3\xb1o.txt',
b'somefile']
15、打印不合法的文件名
当打印文件名时出现UnicodeEncodeError|surrogates not allowed,解决办法:
def bad_filename(filename):
return repr(filename)[1:-1]
try:
print(filename)
except UnicodeEncodeError:
print(bad_filename(filename))
16、增加或改变已打开文件的编码io.TextIOWrapper()
import io
from urllib.request import urlopen
u = urlopen('www.python.org')
f = io.TextIOWrapper(u, encoding='utf-8')
text = f.read()
17、将字节写入文本文件
在文本模式打开的文件中写入原始的字节数据(将字节数据直接写入文件的缓冲区即可);
import sys
sys.stdout.buffer.write(b'Hello\n') #sys.stdout.write('Hello\n')
In [107]: sys.stdout.buffer.write(b'Hello\n')
Hello
Out[107]: 6
18、将文件描述符包装成文件对象
将对应于OS上一个已打开的I/O通道的整型文件描述符(如文件|管道|套接字),包装成一个更高层的py文件对象;
一个文件描述符和一个打开的普通文件是不一样的,文件描述符仅仅是一个由操作系统指定的整数,用来指代某个系统的I/O通道,可用open()将其包装成一个py的文件对象;
在Unix中,这种包装文件描述符的技术可方便地将一个类文件接口作用于一个以不同方式打开的I/O通道上,如管道|套接字;
In [109]: fd = os.open(r'c:/test.txt', os.O_WRONLY|os.O_CREAT)
In [110]: f = open(fd, 'wt') #f = open(fd, 'wt', closefd=False),当高层的文件对象被关闭或破坏时,底层的文件描述符也会被关闭,如果这不是我们想要的可用closefd=False
In [111]: f.write('hhhhhhhhhhhhh\n')
Out[111]: 14
In [112]: f.close()
19、创建临时文件和文件夹tempfile.TemporaryFile|tempfile.NamedTemporaryFile|tempfile.TemporaryDirectory
程序执行时创建一个临时文件或目录,使用完后自动销毁;
from tempfile import TemporaryFile, NamedTemporaryFile, TemporaryDirectory
在unix上,TemporaryFile()创建的文件都是匿名的,甚至连目录都没有,如果想打破这个限制可用NamedTemporaryFile()代替,f.name属性为临时文件的文件名,当需要将文件名传递给其它代码来打开这个文件时会有用,和TemporaryFile()一样,文件关闭时会被自动删除掉,如果不这么做用delte=False
另,tempfile.mkstemp()|tempfile.mkdtemp(),可在更低级别创建临时文件和目录,并不会做进一步的管理,仅返回一个原始的OS文件描述符,需要自己将它转为一个真正的文件对象并清理这些文件;
tempfile.gettempdir() #临时文件在系统默认的位置被创建,如/var/tmp/
f = NamedTemporaryFile(prefix='mytemp', suffix='.txt', dir='/tmp') #所有临时文件相关函数都允许通过关键字参数prefix|suffix|dir来自定义目录及命名规则
f.name
In [115]: with TemporaryFile('w+t') as f: #模式'w+t'|'w+b',这个模式同时支持rw操作,还支持跟open()一样的参数encoding='utf-8',errors='ignore'
...: f.write('Hello world\n')
...: f.write('Testing\n')
...: f.seek(0)
...: data = f.read()
...: print(data)
...:
Hello world
Testing
In [116]: from tempfile import NamedTemporaryFile
In [117]: with NamedTemporaryFile('w+t') as f:
...: print(f.name)
...:
C:\Users\ADMINI~1.SC-\AppData\Local\Temp\tmpuim869zd
In [118]: from tempfile import TemporaryDirectory
In [119]: with TemporaryDirectory() as dirname:
...: print(dirname)
...:
C:\Users\ADMINI~1.SC-\AppData\Local\Temp\tmppff7oo_8
In [121]: from tempfile import gettempdir
In [122]: gettempdir() #临时文件在系统默认的位置被创建,如/var/tmp/
Out[122]: 'C:\\Users\\ADMINI~1.SC-\\AppData\\Local\\Temp'
20、与串行端口的数据通信serial
典型场景是和一些硬件设备打交道,如机器人|传感器;
所有涉及到串口的I/O都是二进制模式的,因此确保代码使用的是字节而不是文本,另当需要创建二进制编码的指令或数据包时,struct模块有用;
pip install pySerial #提供了对高级特性的支持,如超时|控制流|缓冲区刷新|握手协议等
21、序列化py对象pickle
import pickle
pickle.dump()
pickle.dumps() #将一个对象转储为一个字符串
pickle.load()
pickle.loads() #从字节流中恢复一个对象
六、数据编码和处理
1、读写csv数据csv.reader()|csv.DictReader()|csv.writer()|csv.DictWriter()
应优先选择csv模块来解析csv数据,如果自己手动分割row = line.split(',')还要处理一些棘手的细节问题,如被引号包围的字段值中有逗号程序会出错;
默认csv可识别excel所使用的csv编码规则,会带来最好的兼容性;
支持用tab分割的数据,f_csv = csv.reader(f, delimiter='\t')
读取数据时使用命名元组,要注意对列名进行合法性认证(要是py标识符),否则报ValueError;
csv产生的数据都是字符串类型的,它不会做任何其它类型的转换,要自己手动实现;
如果读取csv数据的目的是做数据分析和统计,用Pandas包,它包含了一个非常方便的函数pandas.read_csv()可加载csv数据到一个DataFrame对象中,然后利用这个对象可生成各种形式的统计|过滤数据及其它高级操作;
In [1]: import csv
In [2]: with open(r'c:/test.txt') as f:
返回reader对象
返回列表
...: for row in f_csv:
...: print(row) #访问字段需用row[0]这种方式
...:
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '181800']
['AIG', '71.38', '6/11/2007', '9:36am', '-0.15', '195500']
['AXP', '62.58', '6/11/2007', '9:36am', '-0.46', '935000']
['BA', '98.31', '6/11/2007', '9:36am', '+0.12', '104800']
['C', '53.08', '6/11/2007', '9:36am', '-0.25', '360900']
['CAT78.29"6/11/2007""9:36"0.232254']
In [6]: with open(r'c:/test.txt') as f:
...: f_csv = csv.reader(f)
...: headers = next(f_csv)
...: Row = namedtuple('Row', headers)
...: for r in f_csv:
...: row = Row(*r)
...: print(row) #可用row.Symbol,row.Change访问,前提列名要为合法的py标识符,否则要改原始的列名
...:
Row(Symbol='AA', Price='39.48', Date='6/11/2007', Time='9:36am', Change='-0.18', Volume='181800')
Row(Symbol='AIG', Price='71.38', Date='6/11/2007', Time='9:36am', Change='-0.15', Volume='195500')
Row(Symbol='AXP', Price='62.58', Date='6/11/2007', Time='9:36am', Change='-0.46', Volume='935000')
Row(Symbol='BA', Price='98.31', Date='6/11/2007', Time='9:36am', Change='+0.12', Volume='104800')
Row(Symbol='C', Price='53.08', Date='6/11/2007', Time='9:36am', Change='-0.25', Volume='360900')
Row(Symbol='CAT', Price='78.29', Date='6/11/2007', Time='9:36am', Change='-0.23', Volume='225400')
In [12]: with open(r'c:/test.txt') as f:
...: f_csv = csv.DictReader(f)
...: for row in f_csv:
...: print(row) #可用row['Symbol']访问
...:
OrderedDict([('Symbol', 'AA'), ('Price', '39.48'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.18'), ('Volume', '181800')])
OrderedDict([('Symbol', 'AIG'), ('Price', '71.38'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.15'), ('Volume', '195500')])
OrderedDict([('Symbol', 'AXP'), ('Price', '62.58'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.46'), ('Volume', '935000')])
OrderedDict([('Symbol', 'BA'), ('Price', '98.31'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '+0.12'), ('Volume', '104800')])
OrderedDict([('Symbol', 'C'), ('Price', '53.08'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.25'), ('Volume', '360900')])
OrderedDict([('Symbol', 'CAT'), ('Price', '78.29'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.23'), ('Volume', '225400')])
In [15]: headers = ['Symbol','Price','Date','Time','Change','Volume']
In [17]: rows = [('AA', 39.48, '6/11/2007', '9:36am', -0.18, 181800),('AIG', 71.38, '6/11/2007', '9:36am', -0.15, 19550
...: 0),('AXP', 62.58, '6/11/2007', '9:36am', -0.46, 935000)]
In [19]: with open(r'c:/test.txt','w') as f:
...: f_csv = csv.writer(f)
...: f_csv.writerow(headers)
是一行一行写入,writerows()是一次写多行
...:
In [20]: rows = [{'Symbol':'AA', 'Price':39.48, 'Date':'6/11/2007','Time':'9:36am', 'Change':-0.18, 'Volume':181800},{'
...: Symbol':'AIG', 'Price': 71.38, 'Date':'6/11/2007','Time':'9:36am', 'Change':-0.15, 'Volume': 195500},{'Symbol'
...: :'AXP', 'Price': 62.58, 'Date':'6/11/2007','Time':'9:36am', 'Change':-0.46, 'Volume': 935000}]
In [21]: with open(r'c:/test.txt','w') as f:
...: f_csv = csv.DictWriter(f, headers)
...: f_csv.writeheader()
...: f_csv.writerows(rows)
...:
In [22]: col_types = [str, float, str, str, float, int]
In [23]: with open(r'c:/test.txt') as f:
...: f_csv = csv.reader(f)
...: headers = next(f_csv)
...: for row in f_csv:
...: row = tuple(convert(value) for convert, value in zip(col_types, row))
...: print(row)
...:
()
('AA', 39.48, '6/11/2007', '9:36am', -0.18, 181800)
()
('AIG', 71.38, '6/11/2007', '9:36am', -0.15, 195500)
()
('AXP', 62.58, '6/11/2007', '9:36am', -0.46, 935000)
()
In [24]: field_types = [('Price', float),('Change', float),('Volume', int)]
In [25]: with open(r'c:/test.txt') as f:
...: for row in csv.DictReader(f):
...: row.update((key, convert(row[key])) for key, convert in field_types)
...: print(row)
...:
OrderedDict([('Symbol', 'AA'), ('Price', 39.48), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', -0.18), ('Volume', 181800)])
OrderedDict([('Symbol', 'AIG'), ('Price', 71.38), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', -0.15), ('Volume', 195500)])
OrderedDict([('Symbol', 'AXP'), ('Price', 62.58), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', -0.46), ('Volume', 935000)])
2、读写json数据json.dumps()|json.loads()
json编码支持的基本数据类型为None|bool|int|float|str及包含这些类型数据的list|tuple|dict,对于dict,key需要是字符串类型(如果是非字符串类型会转为字符串);
为遵循json规范,应只编码py的list|dict;
在web应用中,顶层对象被编码为一个dict是一个标准做法;
json编码格式对于py语法几乎是完全一样的,有一些小差异,如True-true,False-false,None-null;
json.dumps() #py-->json,在编码json时,若想有漂亮的格式化后输出,用indent参数,使得输出和pprint()类似
json.loads() #json-->py数据结构,一般json解码会根据提供的数据创建dict|list,如果想创建其它类型的对象,用object_pairs_hook或object_hook参数;
from pprint import pprint #可用此函数代替print(),它会按key的字母顺序并以一种更美观的方式输出
In [28]: import json
In [26]: data = {'name': 'ACME', 'shares':100, 'price':542.23}
In [30]: json_str = json.dumps(data) #py数据结构-->json
In [31]: json_str
Out[31]: '{"name": "ACME", "shares": 100, "price": 542.23}'
In [32]: data = json.loads(json_str) #json-->py
In [33]: data
Out[33]: {'name': 'ACME', 'shares': 100, 'price': 542.23}
In [35]: from collections import OrderedDict
In [36]: json_str
Out[36]: '{"name": "ACME", "shares": 100, "price": 542.23}'
In [38]: data = json.loads(json_str, object_pairs_hook=OrderedDict) #json-->py的OrderedDict
In [39]: data
Out[39]: OrderedDict([('name', 'ACME'), ('shares', 100), ('price', 542.23)])
In [40]: class PyObject:
...: def __init__(self, d):
...: self.__dict__ = d
...:
In [41]: data = json.loads(json_str, object_hook=PyObject) #json-->py对象,json解码后的字典作为一个单个参数传递给__init__(),然后就可以随心所欲的使用了
In [42]: data
Out[42]: <__main__.PyObject at 0x1d09dff9278>
In [43]: data.name
Out[43]: 'ACME'
In [46]: data
Out[46]: {'name': 'ACME', 'shares': 100, 'price': 542.23}
In [47]: json_str = json.dumps(data, indent=4)
In [50]: print(json_str)
{
"name": "ACME",
"shares": 100,
"price": 542.23
}
3、解析简单的xml数据xml.etree.ElementTree.parse()
P179
4、增量式解析大型xml文件xml.etree.ElementTruee.iterparse()
用尽可能少的内存从一个超大的xml文档中提取数据;
只要遇到增量式的数据处理,都要想到用迭代器和生成器;
P179
5、将字典转为xmlxml.etree.ElementTruee.Element
P183
6、解析和修改xmlxml.etree.ElementTree.parse()|xml.etree.ElementTree.Element
P185
7、利用命名空间解析xml文档
P187
8、与关系型数据库的交互
py中多行数据的标准方式是一个由元组构成的列表;
import sqlite3
db = sqlite3.connect('database.db')
c = db.cursor()
c.execute('') #执行查询语句,另c.executemany('')插入多条记录
db.commit()
9、编码解码16进制数binascii.b2a_hex()|binascii.a2b_hex()|base64.b16encode()|base64.b16decode()
binascii和base64,这2种技术的主要不同在于大小写的处理,base64只能操作大写形式的16进制字母,而binascii大小写都能处理;
注意:
编码函数所产生的输出总是一个字节字符串,如果想强制以unicode输出,要print(h.decode('ascii));
解码16进制数时,a2b_hex()和b16decode()可接受字节或unicode字符串,但unicode字符串必须仅只包含ascii编码的16进制数;
In [51]: import binascii
In [52]: s = b'hello'
In [53]: h = binascii.b2a_hex(s)
In [54]: h
Out[54]: b'68656c6c6f'
In [55]: binascii.a2b_hex(h)
Out[55]: b'hello'
In [56]: import base64
In [57]: h = base64.b16encode(s)
In [58]: h
Out[58]: b'68656C6C6F'
In [59]: base64.b16decode(h)
Out[59]: b'hello'
10、编码解码base64数据base64.b64encode()|base64.b64decode()
base64编码仅用于面向字节的数据,如字节字符串和字节数组,编码处理的输出结果是一个字节字符串;
如果想混合使用base64编码的数据和unicode文本,必须添加一个额外的解码步骤,a = base64.b64encode(s).decode('ascii');
解码base64时,字节字符串和unicode文本都可作为参数,但unicode字符串只能包含ascii字符;
In [60]: s
Out[60]: b'hello'
In [61]: a = base64.b64encode(s)
In [62]: a
Out[62]: b'aGVsbG8='
In [63]: base64.b64decode(a)
Out[63]: b'hello'
11、读写二进制数组数据struct
P192
12、读取嵌套和可变长二进制数据struct
struct模块可被用来编码解码几乎所有类型的二进制的数据结构;
P196
13、数据的累加与统计操作pandas
对于任何涉及到统计|时间序列|其它相关技术的数据分析问题,可考虑用pandas;
P206
十、模块与包
1、构建一个模块的层级包__init__.py
封装成包,确保每个目录都定义了__init__.py文件,就能执行import语句;
绝大多数时候让__init__.py空着,但有些情况下可能包含代码,如此文件能用来自动加载子模块;
__init__.py还可将多个文件合并到一个逻辑命名空间;
2、控制模块被全部导入的内容__all__
somemodule.py #当用from somemodule import *时,希望从模块或包导出的符号进行精确控制;如果__all__中包含未定义的名字,报AttributeError
def spam():
pass
def grok():
pass
blah = 42
__all__ = ['spam', 'grok']
3、使用相对路径名导入包中子模块
mypackage/
__init__.py
A/
__init__.py
spam.py
grok.py
B/
__init__.py
bar.py
from . import grok #在mypackage.A.spam中,既可用相对路径也可用绝对路径
from ..B import bar ##在mypackage.A.spam中
4、将模块分割成多个文件
5、利用命名空间导入目录分散的代码
6、
本文共计19281个文字,预计阅读时间需要78分钟。
目录:+ 数据结构和算法 + 1. 数据结构:4 + 1.1 解压序列列给多个变量赋值 + 2. 解压可迭代对象给多个变量赋值 + 3. 保留最后一个元素:collections.deque
目录
一、数据结构和算法: 4
1、解压序列赋值给多个变量 4
2、解压可迭代对象赋值给多个变量 5
3、保留最后N个元素collections.deque 5
4、查找最大或最小的N个元素heapq.nlargest|nsmallest 5
5、实现一个优先级队列heapq.heappush()|heapq.heappop() 6
6、字典中的键映射多个值(multidict)collections.defaultdict 8
7、字典排序(有序字典)collections.OrderedDict 8
8、字典运算 9
9、查找2字典的相同点 10
10、删除序列中相同元素并保持顺序 11
11、命名切片slice(start,stop,step) 12
12、序列中出现次数最多的元素collections.Counter 13
13、通过某个关键字排序一个字典列表operator.itemgetter 13
14、排序不支持原生比较的对象operator.attrgetter 14
15、通过某个字段将记录分组itertools.groupby 15
16、过滤序列元素 16
17、从字典中提取子集: 18
18、映射名称到序列元素collections.namedtuple 19
19、转换并同时计算数据,生成器表达式 20
20、合并多个字典或映射collections.ChainMap 21
二、字符串和文本 23
1、使用多个界定符分割字符串re.split() 23
2、字符串开头或结尾匹配str.startswith()|str.endswith() 24
3、用shell通配符匹配字符串fnmatch.fnmatch 24
4、字符串匹配和搜索re.match()|re.findall() 25
5、字符串搜索和替换re.sub() 26
6、字符串忽略大小写的搜索替换 27
7、最短匹配模式.*?|.+? 28
8、多行匹配模式 28
9、将Unicode文本标准化 28
10、正则表达式使用Unicode 29
11、删除字符串中不需要的字符str.strip()|re.sub('\s+','',s1) 29
12、审查清理文本字符串 29
13、字符串对齐format() 29
14、合并拼接字符串join() 30
15、字符串中插入变量format()|format_map() 31
16、以指定列宽格式化字符串textwrap.fill() 32
17、在字符串中处理html|xml 33
18、字符串令牌解析 34
19、实现一个简单的递归下降分析器 34
20、字节字符串上的字符串操作 34
三、数字|日期|时间 35
1、数字的四舍五入round(value, ndigits) 35
2、执行精确的浮点数运算 36
3、数字的格式化输出format() 37
4、二|八|十六进制整数 38
5、字节到大整数的打包与解包 38
6、复数的数学运算cmath|numpy 39
7、无穷大与NaN 40
8、分数运算fractions.Fraction 41
9、大型数组运算numpy 41
10、矩阵np.matrix()与线性代数运算 42
11、随机选择random 42
12、基本的日期与时间转换datetime|dateutil 43
13、计算最后一个周五的日期 45
14、计算当月份的日期范围 45
15、字符串转换为日期datetime.strptime() 45
16、结合时区的日期操作pytz 46
四、迭代器与生成器 46
1、手动遍历迭代器 47
2、代理迭代__iter__() 48
3、使用生成器创建新的迭代模式 49
4、实现迭代器协议 50
5、反向迭代reversed(lst)|__reversed__() 51
6、带有外部状态的生成器函数 52
7、迭代器切片itertools.islice() 53
8、跳过可迭代对象的开始部分itertools.dropwhile()|itertools.islice() 54
9、排列组合的迭代itertools.permutations()|itertools.combinations()|itertools.combinations_with_replacement() 55
10、序列上索引值迭代enumerate() 55
11、同时迭代多个序列zip()|itertools.zip_longest() 57
12、不同集合上元素的迭代itertools.chain() 58
***13、创建数据处理管道 59
14、展开嵌套的序列collections.Iterable 60
15、顺序迭代合并后的排序迭代对象heapq.merge() 61
16、迭代器替代while无限循环 62
七、函数 63
1、可接受任意数量参数的函数 64
2、只接受关键字参数的函数 64
3、给函数参数增加元信息 65
4、返回多个值的函数 66
5、定义有默认参数的函数 66
6、定义匿名函数或内联函数 68
7、匿名函数捕获变量值 68
8、减少可调用对象的参数个数functools.partial() 69
9、将单方法的类转换为函数(闭包) 70
10、带额外状态信息的回调函数 70
11、内联回调函数 72
12、访问闭包中定义的变量 72
十三、脚本编程与系统管理 73
1、通过重定向|管道|文件接受输入fileinput.input() 73
2、终止程序并给出错误信息raise 74
3、解析命令行选项argparse 74
4、运行时弹出密码输入提示getpass 76
5、获取终端的大小os.get_terminal_size() 76
6、执行外部命令并获取它的输出subprocess.check_output() 77
7、复制或移动文件或目录shutil 78
8、创建和解压归档文件shutil.make_archive()|shutil.unpack_archive() 79
9、通过文件名查找文件os.walk() 80
10、读取.ini配置文件configparser.ConfigParser 81
11、给简单脚本增加日志功能logging 83
12、给函数库增加日志功能logging 85
13、实现一个计时器time.perf_counter() 86
14、限制内存和cpu的使用量resource.getrlimit()|resource.setrlimit() 87
15、启动一个web浏览器webbrowser 87
八、类与对象 88
1、改变对象的字符串显示__str__()|__repr__() 88
2、自定义字符串的格式化__format__() 89
3、让对象支持上下文管理协议__enter__()|__exit__()|contextmanager 89
4、创建大量对象时节省内存的方法__slots__ 91
5、在类中封装属性名_ 92
6、创建可管理的属性@property 93
7、调用父类的方法super() 94
8、子类中扩展property 95
9、创建新的类或实例属性__get__()|__set__()|__delete__() 96
10、使用延迟计算特性_描述器类 97
11、简化数据结构的初始化 98
12、定义接口或抽象基类abc 98
13、实现数据模型的类型约束 99
五、文件与IO 99
1、读写文本数据open() 99
2、打印输出至文件中print('content', file=f) 100
3、使用其它分隔符或行终止符打印print('content', sep=',', end='!!\n') 100
4、读写字节数据open() 100
5、文件不存在才能写入open() 102
6、字符串的I/O操作io.StringIO()|io.BytesIO() 102
7、读写压缩文件gzip.open()|bz2.open() 103
8、固定大小记录的文件迭代functools.partial()|iter() 103
9、读取二进制数据到可变缓冲区中_f.readinto() 103
10、内存映射的二进制文件mmap 104
11、文件路径名的操作os.path中的函数 104
12、测试文件是否存在os.path.exists() 105
13、获取目录中的文件列表os.listdir() 105
14、忽略文件名编码 106
15、打印不合法的文件名 107
16、增加或改变已打开文件的编码io.TextIOWrapper() 107
17、将字节写入文本文件 107
18、将文件描述符包装成文件对象 107
19、创建临时文件和文件夹tempfile.TemporaryFile|tempfile.NamedTemporaryFile|tempfile.TemporaryDirectory 108
20、与串行端口的数据通信serial 109
21、序列化py对象pickle 109
六、数据编码和处理 110
1、读写csv数据csv.reader()|csv.DictReader()|csv.writer()|csv.DictWriter() 110
2、读写json数据json.dumps()|json.loads() 113
3、解析简单的xml数据xml.etree.ElementTree.parse() 115
4、增量式解析大型xml文件xml.etree.ElementTruee.iterparse() 115
5、将字典转为xmlxml.etree.ElementTruee.Element 115
6、解析和修改xmlxml.etree.ElementTree.parse()|xml.etree.ElementTree.Element 115
7、利用命名空间解析xml文档 115
8、与关系型数据库的交互 116
9、编码解码16进制数binascii.b2a_hex()|binascii.a2b_hex()|base64.b16encode()|base64.b16decode() 116
10、编码解码base64数据base64.b64encode()|base64.b64decode() 117
11、读写二进制数组数据struct 117
12、读取嵌套和可变长二进制数据struct 117
13、数据的累加与统计操作pandas 118
一、数据结构和算法:
1、解压序列赋值给多个变量
p = (4,5)
x, y = p
_, x = p # _或ign为废弃名称
record = ('ACME', 50, 123.45, (12,18,2012))
name, *_, (*_, year) = record
2、解压可迭代对象赋值给多个变量
record = ('Dave', 'dave@example.com', '4524510', '4527926')
name, email, *phone_numbers = record #phone_numbers为list
*trailing, current = [10,8.7,1,9,5.10,1] # current为1
3、保留最后N个元素collections.deque
from collections import deque
deque(maxlen=N) # 新建一个固定大小的队列,当新的元素加入且这个队列已满时,最老的元素会自动被移除掉;虽列表也能实现,但这个方案更优雅运行更快,在队列两端插入或删除时间复杂度都是O(1),而在列表的开头插入或删除时间复杂度是O(N)
In [8]: from collections import deque
In [9]: q = deque(maxlen=3)
In [10]: q.append(1)
In [11]: q.append(2)
In [12]: q.append(3)
In [13]: q
Out[13]: deque([1, 2, 3])
In [14]: q.append(4)
In [15]: q
Out[15]: deque([2, 3, 4])
In [16]: q.appendleft(5)
In [17]: q
Out[17]: deque([5, 2, 3])
4、查找最大或最小的N个元素heapq.nlargest|nsmallest
from heapq import nlargest, nsmallest # 底层使用堆排序,适合于要查找的元素相对较少时;若仅想查找唯一的最大或最小元素,使用min()和max()更快;如果要查找的元素和集合中的元素相当,用sorted(items)[:N]更快
In [18]: from heapq import nsmallest,nlargest
In [19]: lst = [1,2,3,4,5,6,7,8]
In [20]: nlargest(3,lst)
Out[20]: [8, 7, 6]
In [21]: nsmallest(3,lst)
Out[21]: [1, 2, 3]
5、实现一个优先级队列heapq.heappush()|heapq.heappop()
实现一个按优先级排序的队列,且在这个队列上每次pop()总是返回优先级最高的那个元素;
heapq.heappush(heap, item) -> None. Push item onto heap, maintaining the heap invariant.
heapq.heappop().
push和pop操作时间复杂度为O(logN),N是堆的大小,因此即使N很大运行速度仍然很快;
结合下例,这2函数分别在队列_queue上插入和删除第一个元素,且队列_queue保证第一个元素拥有最小优先级,因为heapq.heappop()返回的结果是最高优先级;
若要在多线程中使用同一个队列,需要增加适当的锁和信号量机制;
In [178]: import heapq
In [179]: class PriorityQueue:
...: def __init__(self):
...: self._queue = []
变量的作用是保证同等优先级元素的正确排序,通过保存一个不断增加的下标变量,可确保元素按照它们插入的顺序排序,且_index变量也在相同优先级元素比较时起到重要作用
...: def push(self, item, priority):
元组中,优先级为负数的目的是使得元素按照优先级从高到低排序,这跟普通的按优先级从低到高排序的堆排序恰巧相反;
...: self._index += 1
...: def pop(self):
...: return heapq.heappop(self._queue)[-1]
...:
In [180]: class Item: #该实例不可排序,若Item('foo') < Item('bar')会出错;但(1, Item('foo'))< (5, Item('bar'))则可以,如果(1, Item('foo')) < (1, Item('bar'))会报错,通过再引入index则避免这个报错,(1, 0, Item('foo')) < (1,1,Item('bar'))
...: def __init__(self, name):
...: self.name = name
...: def __repr__(self):
...: return 'Item({!r})'.format(self.name)
...:
In [181]: q = PriorityQueue()
In [182]: q.push(Item('foo'), 1)
In [183]: q.push(Item('bar'), 5)
In [184]: q.push(Item('span'), 4)
In [185]: q.push(Item('grok'), 1)
In [186]: q.pop()
Out[186]: Item('bar')
In [187]: q.pop()
Out[187]: Item('span')
In [188]: q.pop()
Out[188]: Item('foo')
In [189]: q.pop()
Out[189]: Item('grok')
6、字典中的键映射多个值(multidict)collections.defaultdict
用途:数据处理中的记录归类;
from collections import defaultdict
In [26]: from collections import defaultdict
In [27]: d = defaultdict(list)
In [28]: d['a'].append(1)
In [29]: d['a'].append(2)
In [30]: d['b'].append(4)
In [31]: d
Out[31]: defaultdict(list, {'a': [1, 2], 'b': [4]})
In [32]: d2 = defaultdict(set)
In [33]: d2['a'].add(1)
In [34]: d2['a'].add(2)
In [35]: d2['b'].add(4)
In [36]: d2
Out[36]: defaultdict(set, {'a': {1, 2}, 'b': {4}})
In [40]: d
Out[40]: {}
In [41]: d.setdefault('a',[]).append(1)
In [42]: d.setdefault('a',[]).append(2)
In [43]: d.setdefault('b',[]).append(4)
In [44]: d
Out[44]: {'a': [1, 2], 'b': [4]}
7、字典排序(有序字典)collections.OrderedDict
用途:精确控制以josn编码后字段的顺序
from collections import OrderedDict # 内部维护着一个双向链表,且该大小是普通字典的2倍,要考虑消耗内存情况
In [46]: d = OrderedDict()
In [47]: d['foo']=1
In [48]: d['bar']=2
In [49]: d['span']=3
In [50]: d['grok']=4
In [51]: d
Out[51]: OrderedDict([('foo', 1), ('bar', 2), ('span', 3), ('grok', 4)])
8、字典运算
在字典中执行计算(最小值|最大值|排序等)
在一个字典上执行普通的数学运算,仅能作用于键,而不能作用于值,对于如下场景使用zip()将值和键反转;
当通过zip()将字典的key和value反转后,在使用min()或max()计算时,若有多个value相同,再去比较key;
In [53]: prices = {
...: 'ACME': 45.23,
...: 'AAPL': 612.78,
...: 'IBM': 205.55,
...: 'HPQ': 37.20,
...: 'FB': 10.75
...: }
In [54]: min_price = min(zip(prices.values(), prices.keys()))
InIn [55]: min_price
Out[55]: (10.75, 'FB')
In [56]: max_price = max(zip(prices.values(), prices.keys()))
In [57]: max_price
Out[57]: (612.78, 'AAPL')
In [59]: zip(prices.values(), prices.keys())
Out[59]: <zip at 0x256bc2eda88>
In [60]: iter(zip(prices.values(), prices.keys()))
Out[60]: <zip at 0x256bc3741c8>
In [61]: for i in zip(prices.values(), prices.keys()): # zip()创建的是一个只能访问一次的迭代器
...: print(i)
...:
(45.23, 'ACME')
(612.78, 'AAPL')
(205.55, 'IBM')
(37.2, 'HPQ')
(10.75, 'FB')
In [64]: min(prices.values())
Out[64]: 10.75
In [65]: max(prices.values())
Out[65]: 612.78
In [66]: min(prices)
Out[66]: 'AAPL'
In [67]: max(prices)
Out[67]: 'IBM'
In [68]: min(prices, key=lambda k: prices[k]) # 通过key参数可对字典的值进行计算,而返回字典的key信息
Out[68]: 'FB'
In [69]: max(prices, key=lambda k: prices[k])
Out[69]: 'AAPL'
In [70]: prices = { # 当value相同时,才比较key
...: 'AAA': 45.23,
...: 'ZZZ': 45.23
...: }
In [71]: min(zip(prices.values(), prices.keys()))
Out[71]: (45.23, 'AAA')
In [72]: max(zip(prices.values(), prices.keys()))
Out[72]: (45.23, 'ZZZ')
9、查找2字典的相同点
在2字典的keys()和items()结果上执行集合操作;
字典的key,有一特性是也支持集合操作,不用先转换成一个set,用.keys()直接进行操作;
字典的.items()方法返回(k,v),这个对象也支持集合操作;
字典的.values()不支持集合操作,原因不能保证所有值是不相同的,要先转为set再进行集合操作;
In [74]: a = {
...: 'x': 1,
...: 'y': 2,
...: 'z': 3
...: }
In [75]: b = {
...: 'w': 10,
...: 'x': 11,
...: 'y': 2
...: }
In [76]: a.keys()
Out[76]: dict_keys(['x', 'y', 'z'])
In [77]: b.keys()
Out[77]: dict_keys(['w', 'x', 'y'])
In [78]: a.keys() & b.keys()
Out[78]: {'x', 'y'}
In [79]: a.keys() - b.keys()
Out[79]: {'z'}
In [80]: a.items() & b.items()
Out[80]: {('y', 2)}
In [82]: {k:a[k] for k in a.keys() - {'z', 'w'}} #字典生成式
Out[82]: {'x': 1, 'y': 2}
10、删除序列中相同元素并保持顺序
对于可hash的值:
def dedupe(items):
seen = set()
for item in items:
if item not in seen:
yield item #使用生成器函数,使得更加通用,不仅仅局限于列表处理,也可用于读取文件消除重复行
seen.add(item)
In [88]: def dedupe(items):
...: seen = set()
...: for item in items:
...: if item not in seen:
...: yield item
...: seen.add(item)
...:
In [89]: a = [1,5,2,1,9,1,2,5,10]
In [90]: list(dedupe(a))
Out[90]: [1, 5, 2, 9, 10]
对于不可hash的值:
In [91]: def dedupe(items, key=None):
...: seen = set()
...: for item in items:
...: val = item if key is None else key(item)
...: if val not in seen:
...: yield item
...: seen.add(val)
...:
In [92]: a = [{'x':1, 'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
In [93]: list(dedupe(a,key=lambda d: (d['x'],d['y'])))
Out[93]: [{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]
In [94]: list(dedupe(a, key=lambda d: d['x'])) # 适合场景:基于某个字段|属性,或更大的数据结构来消除重复元素
Out[94]: [{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]
In [96]: a = [1,5,2,1,9,1,2,5,10]
In [97]: set(a) # 若仅是消除重复元素,用集合,这种不能维护元素的顺序
Out[97]: {1, 2, 5, 9, 10}
11、命名切片slice(start,stop,step)
避免大量无法理解的硬编码下标,使代码更加清晰可读;
slice(start, stop[, step])内置函数,创建一个切片对象,可被用作任何切片允许使用的地方,通过.start|.stop|.step访问其属性;
In [101]: record='....................100 .......513.25 ..........'
In [102]: SHARES = slice(20,23)
In [103]: PRICE = slice(31,37)
In [104]: cost = int(record[SHARES]) * float(record[PRICE])
In [105]: cost
Out[105]: 51325.0
In [106]: SHARES
Out[106]: slice(20, 23, None)
In [107]: PRICE
Out[107]: slice(31, 37, None)
In [108]: record[SHARES]
Out[108]: '100'
In [110]: a = slice(5,50,2)
In [111]: a.start
Out[111]: 5
In [112]: a.stop
Out[112]: 50
In [113]: a.step
Out[113]: 2
12、序列中出现次数最多的元素collections.Counter
from collections import Counter # Counter对象可接受任意的hashable序列对象,底层实现上一个Counter对象就是一个字典,将元素映射到它出现的次数上;
用途:Counter对象几乎所有需要制表或计数数据的场合都可用,且优先选择使用Counter,而不是手动的利用字典实现;
In [116]: words = [
...: 'look', 'into', 'my', 'eyes', 'look', 'into', 'my', 'eyes',
...: 'the', 'eyes', 'the', 'eyes', 'the', 'eyes', 'not', 'around', 'the',
...: 'eyes', "don't", 'look', 'around', 'the', 'eyes', 'look', 'into',
...: 'my', 'eyes', "you're", 'under'
...: ]
In [117]: from collections import Counter
In [118]: word_counts = Counter(words)
In [119]: word_counts #若要手动增加计数,用word_counts['no']+=1或update()方法
Out[119]:
Counter({'look': 4,
'into': 3,
'my': 3,
'eyes': 8,
'the': 5,
'not': 1,
'around': 2,
"don't": 1,
"you're": 1,
'under': 1})
In [120]: top_three = word_counts.most_common(3)
In [121]: top_three
Out[121]: [('eyes', 8), ('the', 5), ('look', 4)]
13、通过某个关键字排序一个字典列表operator.itemgetter
from operator import itemgetter
In [126]: rows = [
...: {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
...: {'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
...: {'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
...: {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}
...: ]
In [127]: rows_by_fname = sorted(rows, key=itemgetter('fname')) #同lambda表达式rows_by_fname = sorted(rows, key=lambda r: r['fname']),itemgetter()效率更高且支持多个key比较;sorted()或min()或max()
In [128]: rows_by_fname
Out[128]:
[{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001}]
In [129]: rows_by_uid = sorted(rows, key=itemgetter('uid'))
In [130]: rows_by_uid
Out[130]:
[{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004}]
In [131]: rows_by_lfname = sorted(rows, key=itemgetter('lname','fname')) # itemgetter()支持多个key
In [132]: rows_by_lfname
Out[132]:
[{'fname': 'David', 'lname': 'Beazley', 'uid': 1002},
{'fname': 'John', 'lname': 'Cleese', 'uid': 1001},
{'fname': 'Big', 'lname': 'Jones', 'uid': 1004},
{'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}]
14、排序不支持原生比较的对象operator.attrgetter
from operator import attrgetter
In [135]: class User:
...: def __init__(self, user_id):
...: self.user_id = user_id
...: def __repr__(self):
...: return 'User({})'.format(self.user_id)
...:
In [136]: users = [User(23),User(3),User(99)]
In [137]: sorted(users, key=lambda u: u.user_id)
Out[137]: [User(3), User(23), User(99)]
In [138]: from operator import attrgetter
In [139]: sorted(users, key=attrgetter('user_id'))
Out[139]: [User(3), User(23), User(99)]
15、通过某个字段将记录分组itertools.groupby
from operator import itemgetter
from itertools import groupby
groupby()扫描整个序列并且查找连续相同值的元素序列,在每次迭代时,它会返回一个值和一个迭代器对象;
一个重要准备是先要根据指定的字段将数据排序,因为groupby()仅检查连续的元素;
In [140]: rows = [
...: {'address': '5412 N CLARK', 'date': '07/01/2012'},
...: {'address': '5148 N CLARK', 'date': '07/04/2012'},
...: {'address': '5800 E 58TH', 'date': '07/02/2012'},
...: {'address': '2122 N CLARK', 'date': '07/03/2012'},
...: {'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
...: {'address': '1060 W ADDISON', 'date': '07/02/2012'},
...: {'address': '4801 N BROADWAY', 'date': '07/01/2012'},
...: {'address': '1039 W GRANVILLE', 'date': '07/04/2012'},
...: ]
In [142]: rows.sort(key=itemgetter('date')) # 先按指定的字段排序,再调用groupby()
In [145]: for date, items in groupby(rows, key=itemgetter('date')):
...: print(date)
...: for i in items:
...: print(' ', i)
...:
07/01/2012
{'address': '5412 N CLARK', 'date': '07/01/2012'}
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}
07/02/2012
{'address': '5800 E 58TH', 'date': '07/02/2012'}
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
07/03/2012
{'address': '2122 N CLARK', 'date': '07/03/2012'}
07/04/2012
{'address': '5148 N CLARK', 'date': '07/04/2012'}
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}
In [146]: from collections import defaultdict #若仅仅是想根据date字段将数据分组到一个大的数据结构中去,且允许随机访问,用defaultdict()来构建一个多值字典,有用先对rows进行排序
In [147]: rows_by_date = defaultdict(list)
In [148]: rows_by_date
Out[148]: defaultdict(list, {})
In [149]: rows
Out[149]:
[{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'},
{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'},
{'address': '2122 N CLARK', 'date': '07/03/2012'},
{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}]
In [150]: for row in rows:
...: rows_by_date[row['date']].append(row)
...:
In [151]: rows_by_date
Out[151]:
defaultdict(list,
{'07/01/2012': [{'address': '5412 N CLARK', 'date': '07/01/2012'},
{'address': '4801 N BROADWAY', 'date': '07/01/2012'}],
'07/02/2012': [{'address': '5800 E 58TH', 'date': '07/02/2012'},
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'},
{'address': '1060 W ADDISON', 'date': '07/02/2012'}],
'07/03/2012': [{'address': '2122 N CLARK', 'date': '07/03/2012'}],
'07/04/2012': [{'address': '5148 N CLARK', 'date': '07/04/2012'},
{'address': '1039 W GRANVILLE', 'date': '07/04/2012'}]})
In [152]: for r in rows_by_date['07/02/2012']:
...: print(r)
...:
{'address': '5800 E 58TH', 'date': '07/02/2012'}
{'address': '5645 N RAVENSWOOD', 'date': '07/02/2012'}
{'address': '1060 W ADDISON', 'date': '07/02/2012'}
16、过滤序列元素
列表推导(列表生成式),缺陷长度很长时占内存;
生成器表达式;
列表推导和生成器表达式是过滤数据最简单的方式,还可在过滤时转换数据;过滤操作的一个变种就是将不符合条件的值用新的值代替而不是丢弃它们,如在一列数据中不仅想要正数,且还想将不是正数的数替换成指定的数;
过滤规则复杂时(如过滤时需要处理一些异常或其它复杂情况),将过滤代码放到一个函数中,使用内建filter()函数;
In [153]: lst = [1,4,-5,10,-7,2,3,-1]
In [154]: [n for n in lst if n>0]
Out[154]: [1, 4, 10, 2, 3]
In [155]: [n for n in lst if n<0]
Out[155]: [-5, -7, -1]
In [156]: pos = (n for n in lst if n>0)
In [157]: pos
Out[157]: <generator object <genexpr> at 0x00000256BC2A8888>
In [158]: for x in pos:
...: print(x)
...:
1
4
10
2
3
In [159]: values = ['1','2','-3','-','4','N/A','5']
In [160]: def is_int(val):
...: try:
...: x = int(val)
...: return True
...: except ValueError:
...: return False
...:
In [161]: ivals = list(filter(is_int, values)) #filter()创建一个迭代器
In [162]: ivals
Out[162]: ['1', '2', '-3', '4', '5']
In [166]: lst = [1,4,-5,10,-7,2,3,-1]
In [167]: import math
In [168]: [math.sqrt(n) for n in lst if n>0]
Out[168]: [1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
In [169]: clip_neg = [n if n>0 else 0 for n in lst]
In [170]: clip_neg
Out[170]: [1, 4, 0, 10, 0, 2, 3, 0]
In [171]: addresses = [
...: '5412 N CLARK',
...: '5148 N CLARK',
...: '5800 E 58TH',
...: '2122 N CLARK'
...: '5645 N RAVENSWOOD',
...: '1060 W ADDISON',
...: '4801 N BROADWAY',
...: '1039 W GRANVILLE',
...: ]
In [172]: counts = [ 0, 3, 10, 4, 1, 7, 6, 1]
In [173]: from itertools import compress
In [174]: more5 = [n>5 for n in counts]
In [175]: more5
Out[175]: [False, False, True, False, False, True, True, False]
In [176]: list(compress(addresses, more5)) # compress()也是返回一个迭代器
Out[176]: ['5800 E 58TH', '4801 N BROADWAY', '1039 W GRANVILLE']
17、从字典中提取子集:
字典推导,或通过创建一个元组序列然后把它传给dict(),字典推导要比dict()运行快一倍;
In [179]: prices = {
...: 'ACME': 45.23,
...: 'AAPL': 612.78,
...: 'IBM': 205.55,
...: 'HPQ': 37.20,
...: 'FB': 10.75
...: }
In [180]: p1 = {k:v for k,v in prices.items() if v>200}
In [181]: p1
Out[181]: {'AAPL': 612.78, 'IBM': 205.55}
In [182]: tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}
In [183]: p2 = {k:v for k,v in prices.items() if k in tech_names}
In [184]: p2
Out[184]: {'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.2}
In [185]: p2 = {k:prices[k] for k in prices.keys() & tech_names}
In [186]: p2
Out[186]: {'AAPL': 612.78, 'HPQ': 37.2, 'IBM': 205.55}
18、映射名称到序列元素collections.namedtuple
from collections import namedtuple
用途:
将代码从下标操作中解脱出来;
作为字典的替代,字典存储需要更多的内存空间,如果需要构建一个非常大的包含字典的数据结构,使用命名元组更加高效;
如果是要定义一个需要更新很多实例属性的高效数据结构,命名元组并不是最佳选择,可考虑定义一个包含__slots__方法的类;
注意:
命名元组不可更改,如要更改要用实例的_replace(),它会创建一个全新的命名元组并将对应的字段用新的值取代;
_replace()有用的特性,当你的命名元组拥有可选或缺失字段时,它是一个非常方便的填充数据的方法,可先创建一个包含缺少值的原型元组,然后使用_replace()创建新的值被更新过的实例;
In [187]: def compute_cost(records):
...: total = 0.0
...: for rec in records:
...: total += rec[1] * rec[2]
...: return total
In [195]: compute_cost(((8,9,10),)) #普通元组代码
Out[195]: 90.0
Stock = namedtuple('Stock', ['name', 'shares', 'price'])
In [198]: def compute_cost(records):
...: total = 0.0
...: for rec in records:
...: s = Stock(*rec)
...: total += s.shares * s.price
...: return total
In [200]: compute_cost((('jowin',88,10),)) # 使用命名元组版本
Out[200]: 880.0
In [201]: s = Stock('jowin',100,123.45)
In [202]: s
Out[202]: Stock(name='jowin', shares=100, price=123.45)
In [203]: s.shares # 不可更改,如果更改要用实例的_replace()方法
Out[203]: 100
In [204]: s = s._replace(shares=75)
In [205]: s
Out[205]: Stock(name='jowin', shares=75, price=123.45)
In [206]: Stock = namedtuple('Stock',['name','shares','price','date','time'])
In [207]: stock_prototype = Stock('', 0, 0.0, None, None)
In [208]: def dict_to_stock(s):
...: return stock_prototype._replace(**s)
...:
In [209]: a = {'name': 'ACME', 'shares': 100, 'price': 123.45}
In [210]: dict_to_stock(a)
Out[210]: Stock(name='ACME', shares=100, price=123.45, date=None, time=None)
19、转换并同时计算数据,生成器表达式
用优雅的方式,即生成器表达式,比先创建一个临时列表更加高效和优雅;
s = sum(x*x for x in nums) #这种方式更优雅;而不必sum((x*x for x in nums))多加一对括号,显式地传递一个生成器表达式对象
s = sum([x*x for x in nums]) #这种会多一步骤,即先创建一个额外的列表,当列表非常大时仅被使用一次就被丢弃,而生成器方案会以迭代的方式转换数据,因此更省内存
In [212]: nums = [1,2,3,4,5]
In [213]: s = sum(x*x for x in nums) #这种方式更优雅;而不必sum((x*x for x in nums))多加一对括号,显式地传递一个生成器表达式对象
In [214]: s
Out[214]: 55
In [217]: files = os.listdir(r'E:/')
In [221]: if any(name.endswith('.py') for name in files):
...: print('there be python')
...: else:
...: print('no python')
...:
no python
In [222]: if any(name.endswith('end') for name in files):
...: print('there be python')
...: else:
...: print('no python')
...:
there be python
In [223]: s = ('ACME',50,123,45)
In [224]: print(','.join(str(x) for x in s)) # csv
ACME,50,123,45
In [226]: portfolio = [
...: {'name':'GOOG', 'shares': 50},
...: {'name':'YHOO', 'shares': 75},
...: {'name':'AOL', 'shares': 20},
...: {'name':'SCOX', 'shares': 65}
...: ]
In [227]: min_shares = min(s['shares'] for s in portfolio)
In [228]: min_shares
Out[228]: 20
In [229]: min_shares = min(portfolio, key=lambda s: s['shares']) #min()|max()|sorted(),使用key参数更佳
In [230]: min_shares
Out[230]: {'name': 'AOL', 'shares': 20}
20、合并多个字典或映射collections.ChainMap
from collection import ChainMap # 一个ChainMap接受多个字典并将它们在逻辑上变为一个字典,然后这些字典并不是真的合并在一起了,ChainMap类只是在内部创建了一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表,大部分字典操作可用;如果有重复key,返回第一次出现的映射值;对于字典的更新或删除,始终影响的是列表中第一个字典;
若使用update()将2字典合并,要创建一个完全不同的字典对象,如果源字典做了更新,这种改变不会反应到新的合并字典中去,merged = dict(b);
ChainMap使用原来的字典,它自己不创建新的字典,源字典a中的数据更新会影响merged结果,merged = ChainMap(a, b),
用途:
对于编程中的作用域,globals|locals是非常有用的,values = ChainMap(),values['x']=1,values = values.new_child(),values= values.parents;
In [1]: from collections import ChainMap
In [2]: a = {'x':1, 'z':3}
In [3]: b = {'y':2, 'z':4}
In [4]: c = ChainMap(a,b)
In [5]: c['x']
Out[5]: 1
In [6]: c['y']
Out[6]: 2
In [7]: c['z']
Out[7]: 3
In [9]: len(c)
Out[9]: 3
In [10]: list(c.keys())
Out[10]: ['x', 'z', 'y']
In [11]: list(c.values())
Out[11]: [1, 3, 2]
In [12]: c['z'] = 10
In [13]: c['w'] = 40
In [14]: c
Out[14]: ChainMap({'x': 1, 'z': 10, 'w': 40}, {'y': 2, 'z': 4})
In [15]: del c['x']
In [16]: a
Out[16]: {'z': 10, 'w': 40}
In [17]: b
Out[17]: {'y': 2, 'z': 4}
In [18]: del c['y']
……
KeyError: "Key not found in the first mapping: 'y'"
In [19]: a
Out[19]: {'z': 10, 'w': 40}
In [20]: b
Out[20]: {'y': 2, 'z': 4}
In [22]: merged = dict(b) #创建一个新的字典对象
In [23]: merged.update(a)
In [24]: merged['z']
Out[24]: 10
In [25]: merged['y']
Out[25]: 2
In [26]: merged['w']
Out[26]: 40
In [27]: a['z'] = 8
In [28]: merged['z']
Out[28]: 10
In [29]: a
Out[29]: {'z': 8, 'w': 40}
In [30]: b
Out[30]: {'y': 2, 'z': 4}
In [31]: merged = ChainMap(a, b) #ChainMap引用原来的字典对象
In [32]: merged['z']
Out[32]: 8
In [33]: a['z'] = 88
In [34]: merged['z']
Out[34]: 88
二、字符串和文本
1、使用多个界定符分割字符串re.split()
re.split() #返回字段列表同str.split()
如果想保留分割字符,使用捕获分组;
In [35]: line = 'test test1; test2, test3,test4, test5'
In [36]: import re
In [37]: re.split(r'[;,\s]\s*', line)
Out[37]: ['test', 'test1', 'test2', 'test3', 'test4', 'test5']
In [38]: fields = re.split(r'(;|,|\s)\s*', line) #捕获分组,保留分割字符串
In [39]: fields
Out[39]: ['test', ' ', 'test1', ';', 'test2', ',', 'test3', ',', 'test4', ',', 'test5']
In [40]: values = fields[::2]
In [41]: values
Out[41]: ['test', 'test1', 'test2', 'test3', 'test4', 'test5']
In [42]: delimiters = fields[1::2] + ['']
In [43]: ''.join(v+d for v,d in zip(values, delimiters))
Out[43]: 'test test1;test2,test3,test4,test5'
In [44]: re.split(r'(?:,|;|\s)\s*', line) #获取非捕获分组,(?:)
Out[44]: ['test', 'test1', 'test2', 'test3', 'test4', 'test5']
2、字符串开头或结尾匹配str.startswith()|str.endswith()
str.startswith()
str.endswith()
可用切片实现,没上面优雅;
可用re.match('www.python.org'
In [49]: url.startswith('www.python.org'
In [49]: url.startswith('www.python.org') #启动一个浏览器,使用默认浏览器打开指定网页,与平台无关
webbrowser.open_new() #对网页打开方式做更多控制
webbrowser.open_new_tab()
c = webbrowser.get('firefox') #指定浏览器
c.open()
c.open_new_tab()
八、类与对象
1、改变对象的字符串显示__str__()|__repr__()
__repr__() #返回一个实例的代码表示形式,通常用来重新构造这个实例,内置的repr()返回这个字符串,跟用交互式解释器显示的值是一样的
__str__() #将实例转换为一个字符串,使用str()或print()会输出这个字符串,如果该方法未定义就会使用__repr__()来代替输出
自定义__repr__()和__str__()通常是好的习惯,因为它能简化调试和实例输出,会看到实例更加详细与有用的信息;
'{!r}'.format() #格式化时的!r表示指明输出使用__repr__()来代替默认的__str__()
In [327]: class Pair:
...: def __init__(self, x, y):
...: self.x = x
...: self.y = y
...: def __repr__(self):
指代self
...: def __str__(self):
...: return '({0.x!s}, {0.y!s})'.format(self)
...:
In [328]: p = Pair(3,4)
In [329]: p
Out[329]: Pair(3, 4)
In [330]: print(p)
(3, 4)
2、自定义字符串的格式化__format__()
__format__() #此方法给py的字符串格式化功能提供了一个钩子,着重强调的是格式化代码的解析工作完全由类自己决定,因此格式化代码可以是任何值,参照datetime.date
In [334]: _formats = {'ymd': '{d.year}-{d.month}-{d.day}','mdy': '{d.month}/{d.day}/{d.
...: year}','dmy': '{d.day}/{d.month}/{d.year}'}
In [341]: class Date:
...: def __init__(self, year, month, day):
...: self.year = year
...: self.month = month
...: self.day = day
...: def __format__(self, code):
...: if code == '':
...: code = 'ymd'
...: fmt = _formats[code]
...: return fmt.format(d=self)
...:
In [342]: d = Date(2019, 11, 16)
In [343]: format(d)
Out[343]: '2019-11-16'
In [344]: 'The date is {:mdy}'.format(d)
Out[344]: 'The date is 11/16/2019'
In [345]: 'The date is {:dmy}'.format(d)
Out[345]: 'The date is 16/11/2019'
In [346]: from datetime import date
In [347]: d = date(2019, 11, 16)
In [348]: format(d)
Out[348]: '2019-11-16'
In [349]: format(d, '%A, %B, %d, %Y')
Out[349]: 'Saturday, November, 16, 2019'
In [350]: 'The end is {:%d %b %Y}. Goodbye'.format(d)
Out[350]: 'The end is 16 Nov 2019. Goodbye'
3、让对象支持上下文管理协议__enter__()|__exit__()|contextmanager
为兼容with语句,需实现__enter__()和__exit__();
当出现with语句时,对象的__enter__()被触发,它返回的值被赋值给as声明的变量,然后with语句块里的代码开始执行,最后__exit__()被触发进行清理工作;
不管with代码块中发生什么,LazyConnection中的控制流都会执行完,就算代码块中发生异常也一样,__exit__()中第三个参数包含了异常类型|异常值|追溯信息,__exit__()方法能自己决定怎样利用这个异常信息,或忽略它返回None,如果__exit__()返回True那么异常会被清空,好像什么都没发生一样,with语句后的程序继续正常执行;
在需要管理一些资源,如文件|网络连接|锁的编程环境中,使用上下文管理器很普遍,这些资源的一个重要特征是它们必须被手动的关闭或释放来确保程序的正确运行,如请求了一个锁那就必须确保之后要释放它,否则可能会产生死锁;
在contextmanager模块中有一个标准的上下文管理方案模板可参考;
In [351]: from socket import socket, AF_INET, SOCK_STREAM
In [352]: class LazyConnection: #此类表示了一个网络连接,初始化时并没建立连接,连接的建立和关闭是用with语句自动完成的;此例只能允许一个socket连接,如果正在使用一个socket时又重复使用with语句就会产生异常
...: def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
...: self.address = address
...: self.family = family
...: self.type = type
...: self.sock = None
...: def __enter__(self):
...: if self.sock is not None:
...: raise RuntimeError('Already connected')
...: self.sock = socket(self.family, self.type)
...: self.sock.connect(self.address)
...: return self.sock
...: def __exit__(self, exc_ty, exc_val, tb):
...: self.sock.close()
...: self.sock = None
...:
In [3]: class LazyConnection: #支持多个嵌套with语句,LazyConnection被看作是连接工厂,在内部列表被用来构造一个栈,每次__enter__()执行时它复制创建一个新的连接,并将其加入到栈里面,__exit__()简单的从栈中弹出最后一个连接并关闭它
...: def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
...: self.address = address
...: self.family = family
...: self.type = type
...: self.connections = []
...: def __enter__(self):
...: sock = socket(self.family, self.type)
...: sock.connect(self.address)
...: self.connections.append(sock)
...: return sock
...: def __exit__(self, exc_ty, exc_val, tb):
...: self.connections.pop().close()
...:
In [4]: from functools import partial
In [5]: conn = LazyConnection(('www.python.org', 80))
In [6]: with conn as s1:
...: pass
...: with conn as s2:
...: pass
...:
4、创建大量对象时节省内存的方法__slots__
__slots__ #定义该属性后,py就会为实例使用一种更加紧凑的内部表示,实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟tuple|list类似,在__slots__中列出的属性名在内部被映射到这个数组的指定小标上;使用__slots__不好处是,不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名
In [7]: class Date:
...: __slots__ = ['year', 'month', 'day']
...: def __init__(self, year, month, day):
...: self.year = year
...: self.month = month
...: self.day = day
...:
5、在类中封装属性名_
_开头的命名 #任何以_开头的名字都应该是内部实现,同样也适用于模块名和模块级别函数(模块级别函数在使用时比sys._getframe()得加倍小心),py并不会真的阻止访问这些属性,但如果这么做不好可能会导致脆弱的代码
__开头的命名 #使用__开始会导致名称变成其它形式,如类B和类C中,_B__private|_B__private_method和_C__private|_C__private_method是不一样的,目的是继承用,这种属性通过继承是无法被覆盖的
使用注意:
大多数情况下,非公共名称以_开头方案;
如果代码涉及到子类,且有些内部属性应在子类中隐藏,才考虑使用__方案;
如果定义的一个变量和某个保留关键字冲突,使用_作为后缀,如lambda_ = 2.0,这里不使用_前缀的原因是避免误解它的使用初衷(使用_前缀的目的是为了防止命名冲突而不是指明这个属性是私有的)
In [8]: class A:
...: def __init__(self):
...: self._internal = 0
...: self.public = 1
...: def public_method(self):
...: pass
...: def _internal_method(self):
...: pass
...:
In [9]: class B:
...: def __init__(self):
...: self.__private = 0
...: def __private_method(self):
...: pass
...: def public_method(self):
...: pass
...: self.__private_method()
...:
In [11]: class C(B):
...: def __init__(self):
...: self.__private = 0
...: def __private_method(self):
...: pass
...: def public_method(self):
...: pass
...: self.__private_method()
...:
6、创建可管理的属性@property
@property #的一个关键特征是它看上去跟普通的attribute没区别,但访问它时会自动触发getter|setter|deleter方法;
一个property属性其实就是一系列相关方法的集合,查看拥有property的类,发现property本身的fget|fset|fdel属性就是类里面的普通方法,通常不会直接调用fget|fset,它会在访问property时自动被触发;
只有当需要对attribute执行其它额外操作时才应使用property,不要写没做任何其它额外操作的property,1代码臃肿易出错丑陋且会迷惑阅读者,2程序运行会变慢,3 P241;
property还是一种定义动态计算attribute的方法,这种类型的attribute并不会被实际的存储,而是在需要时计算出来,对半径|直径|周长|面积的访问都通过属性访问,跟访问简单的attribute一样,如:
@property
def area(self):
return math.pi * self.radius ** 2
In [12]: class Person:
...: def __init__(self, first_name):
...: self.first_name注意此处为self.first_name不是self._first_name,是想在初始化时也进行类型检查,初始化时会自动调用setter方法进行参数检查
...: @property
...: def first_name(self): #这3个方法的名字必须一样,依次为getter|setter|deleter函数,getter函数使得first_name成为一个属性,只有在getter被创建后setter和deleter才能被定义
...: return self._first_name
...: @first_name.setter
...: def first_name(self, value):
...: if not isinstance(value, str):
...: raise TypeError('Expected a string')
...: self._first_name = value
...: @first_name.deleter
...: def first_name(self):
...: raise AttributeError("Can't delete attribute")
...:
In [13]: a = Person('Guido')
In [14]: a.first_name
Out[14]: 'Guido'
In [15]: a.first_name = 42
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-15-6341bdece797> in <module>
----> 1 a.first_name = 42
<ipython-input-12-d496c776c69d> in first_name(self, value)
8 def first_name(self, value):
9 if not isinstance(value, str):
---> 10 raise TypeError('Expected a string')
11 self._first_name = value
12 @first_name.deleter
TypeError: Expected a string
In [16]: del a.first_name
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-16-50ce9077a584> in <module>
----> 1 del a.first_name
<ipython-input-12-d496c776c69d> in first_name(self)
12 @first_name.deleter
13 def first_name(self):
---> 14 raise AttributeError("Can't delete attribute")
15
AttributeError: Can't delete attribute
In [23]: Person.first_name.fget
Out[23]: <function __main__.Person.first_name(self)>
In [24]: Person.first_name.fset
Out[24]: <function __main__.Person.first_name(self, value)>
In [25]: Person.first_name.fdel
Out[25]: <function __main__.Person.first_name(self)>
7、调用父类的方法super()
要正确使用super(),super().__init__(),不要直接用Base.__init__(self),在复杂情况下如多继承代码中就可能导致问题发生,比如父类Base.__init__()会被调用多次;
原因是py实现多继承的原理:
对于定义的每一个类,py会计算出一个所谓的方法解析顺序MRO列表(C.__mro__是一个简单的所有基类的线性顺序表),为实现继承py会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止;
这个MRO列表的构造是通过一个C3线性算法来实现的,实际上是合并所有父类的MRO列表并遵循3条准则(子类会先于父类被检查|多个父类会根据它们在列表中的顺序被检查|如果对下一个类存在两个合法的选择会选择第一个父类);
需要知道的是MRO列表中的类顺序会让我们定义的任意类层次级关系变得有意义;
使用super()时,py会在MRO列表上继续搜索下一个类,只要每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用1次;
P246
用法1,在__init__()确保父类被正确的初始化了;
用法2,出现在覆盖py特殊方法的代码中;
8、子类中扩展property
P250
9、创建新的类或实例属性__get__()|__set__()|__delete__()
创建一个新的拥有一些额外功能的实例属性类型,如类型检查;
一个描述器就是一个实现了3个核心的属性访问操作(get|set|delete)的类,分别为__get__()|__set__()|__delete__()这三个特殊的方法,这些方法接受一个实例作为输入,之后相应的操作实例底层的__dict__;
描述器可实现大部分py类特性中的底层魔法,包括@classmethod|@staticmethod|@property甚至是__slots__;
通过定义一个描述器,可在底层捕获核心的实例操作(get|set|delete),且可完全自定义它们的行为,这是一个强大的工具,有了它就可实现很多高级功能,并且它也是很多高级库和框架中的重要工具之一;
描述器的一个比较困惑的地方是它只能在类级别被定义,而不能为每个实例单独定义;
描述器通常是那些使用到装饰器或元类的大型框架中的一个组件,同时它们的使用也被隐藏在后面;
如果一个描述器仅定义了一个__get__(),它比通常的具有更弱的绑定,只有当被访问属性不在实例底层的__dict__时__get__()方法才会被触发;
P252
10、使用延迟计算特性_描述器类
P254
如果一个描述器仅定义了一个__get__(),它比通常的具有更弱的绑定,只有当被访问属性不在实例底层的__dict__时__get__()方法才会被触发;
11、简化数据结构的初始化
P258
12、定义接口或抽象基类abc
定义一个接口或抽象类,通过执行类型检查来确保子类实现了某些特定的方法;
from abc import ABCMeta, abstractmethod
抽象类的1个特点是,它不能直接被实例化;
抽象类的目的是,让别的类继承它并实现特定的抽象方法;
抽象类的主要用途,在代码中检查某些类是否为特定类型,实现了特定接口;
13、实现数据模型的类型约束
五、文件与IO
1、读写文本数据open()
rt #读取文本文件
wt #写入一个文本文件
at #在已存在文件中添加内容
encoding='utf-8' #如果确定读写的文本是其它编码使用encoding指定,py支持的有ascii|latin-1|utf-8|utf-16,web应用程序中通常使用的都是utf-8
In [37]: sys.getdefaultencoding() #文件的读写操作使用的系统编码
Out[37]: 'utf-8'
注意:
1、在没用with语句时,要记得手动关闭文件,f = open('somefile.txt', 'rt'),data = f.read(),f.close();
2、换行符的识别,unix中是\n,win中是\r\n,默认py会以统一模式处理换行符,这种模式下,在读取文本时,py可识别所用的普通换行符并将其转换为单个\n字符,在输出时会将换行符\n转换为系统默认的换行符,如果不希望这种默认的处理方式,可给open()传入参数newline='';
3、文本文件中可能出现的编码错误,最好确认该文件的编码是正确的;
2、打印输出至文件中print('content', file=f)
In [39]: with open(r'c:/test.txt', 'wt') as f:
中指定file关键字参数
...:
3、使用其它分隔符或行终止符打印print('content', sep=',', end='!!\n')
在print()中使用sep和end关键字参数,end参数也可在输出中禁止换行;
In [40]: print('ACME', 50, 91.5)
ACME 50 91.5
In [41]: print('ACME', 50, 91.5, sep=',')
ACME,50,91.5
In [42]: print('ACME', 50, 91.5, sep=',', end='!!\n')
ACME,50,91.5!!
In [43]: for i in range(3):
...: print(i, end=' ')
...:
0 1 2
In [47]: row = ('ACME', 50, 91.5)
In [48]: print(*row, sep=',') #使用非空格分隔符输出数据,这种方式最简单;如果用str.join(lst),lst中的元素必须都为字符串,如果不是得转换,这样麻烦
ACME,50,91.5
In [49]: print(','.join(str(x) for x in row))
ACME,50,91.5
4、读写字节数据open()
rb #open()使用的模式,以二进制方式读取数据
wb
在读取二进制数据时:
字节字符串和文本字符串的语义差异可能会导致一个潜在的陷阱,需要注意,索引和迭代动作返回的是字节的值而不是字节字符串;
所有返回的数据都是字节字符串格式,而不是文本字符串,类似在写入时,必须保证参数是以字节形式对外暴露数据的对象,如字节字符串|字节数组对象等;
如果想从二进制模式的文件中读取或写入文本数据,必须确保要进行解码和编码操作;
二进制i/o还有一个鲜为人知的特性是数组和C结构体类型能直接被写入,而不需要中间转换为自己对象,这个适用于任何实现了“缓冲接口”的对象,这种对象会直接暴露其底层的内存缓冲区给能处理它的操作,二进制数据的写入就是这类操作之一;
很多对象还允许通过使用文件对象的readinto()方法直接读取二进制数据到其底层的内存中去,使用这种技术时需格外小心,因为它具有平台相关性,可能会依赖字长和字节顺序(高位优先|低位优先);
with open('somefile.bin', 'rb') as f:
data = f.read()
In [50]: b = b'Hello World'
In [52]: for c in b:
...: print(c, end='\t')
...:
...:
72 101 108 108 111 32 87 111 114 108 100
In [53]: import array
In [55]: nums = array.array('i', [1,2,3,4])
In [56]: with open(r'c:/data.bin', 'wb') as f:
...: f.write(nums)
...:
In [57]: a = array.array('i', [0,0,0,0,0,0,0,0])
In [59]: with open(r'c:/data.bin', 'rb') as f:
...: f.readinto(a)
...:
In [60]:
In [60]: a
Out[60]: array('i', [1, 2, 3, 4, 0, 0, 0, 0])
5、文件不存在才能写入open()
x #open()中使用x模式代替w模式,xt|xb,仅py3对open()特有的扩展
解决不小心覆盖一个已存在的文件;
In [64]: if not os.path.exists('somefile'): #这种方式没有open('somefile', 'xt')好
...: with open('somefile', 'wt') as f:
...: f.write('Hello\n')
...: else:
...: print('File alredy exists!')
...:
6、字符串的I/O操作io.StringIO()|io.BytesIO()
使用操作类文件对象的程序来操作文本或二进制字符串;
io.StringIO() #用于文本
io.BytesIO() #用于二进制数据
注意,StringIO|BytesIO实例并没有正确的整数类型的文件描述符,因此它们不能在那些需要使用真实的系统级文件如文件|管道|套接字的程序中使用;
用途:
在单元测试中,可用StringIO来创建一个包含测试数据的类文件对象,该对象可被传给某个参数为普通文件对象的函数;
In [66]: import io
In [67]: s = io.StringIO()
In [68]: s.write('Hello World\n')
Out[68]: 12
In [69]: print('This is a test', file=s)
In [70]: s.getvalue()
Out[70]: 'Hello World\nThis is a test\n'
In [71]: s = io.StringIO('Hello\nWorld\n')
In [72]: s.read(4)
Out[72]: 'Hell'
In [73]: s.read()
Out[73]: 'o\nWorld\n'
7、读写压缩文件gzip.open()|bz2.open()
gzip.open() #默认二进制模式,.open()接受的参数同内置open(),当写入数据时compresslevel=5来指定压缩级别,默认9最高压缩级别,0-9,等级越低性能越好数据压缩程度越低;rt|wt,所有的I/O操作都使用文本模式并执行Unicode编码/解码,如果操作二进制数据,用rb|wb
bz2.open()
gzip.open()|bz2.open()还有一特性是,可作用在一个已存在并以二进制模式打开的文件上,这样就允许gzip和bz2模块可工作在许多类文件对象上,如套接字|管道|内存中文件;
with gzip.open('somefile.gz', 'wt', compresslevel=5) as f:
f.write(text)
import gzip
f = open('somefile.gz', 'rb')
with gzip.open(f, 'rt') as g:
text = g.read()
8、固定大小记录的文件迭代functools.partial()|iter()
from functools import partial
RECORD_SIZE = 32
with open('somefile.data', 'rb') as f: #以二进制模式打开的文件,通常是读取固定大小的记录;对于文本文件,通常是一行一行地读取
对象是一个可迭代对象,它会不断的产生固定大小的数据块,直到文件末尾,注意如果总记录大小不是块大小的整数倍的话,最后一个返回的元素的字节数会比期望值少
for r in records:
......
P145
9、读取二进制数据到可变缓冲区中_f.readinto()
P147
def read_into_buffer(filename):
buf = bytearray(os.path.getsize(filename))
with open(filename, 'rb') as f:
f.readinto(buf)
return buf
>with open('sample.bin', 'wb') as f:
f.write(b'Hello World')
>buf = read_into(buffer('sample.bin')
>buf[0:5] = b'Hallo'
>with open('newsample.bin', 'wb') as f:
f.write(buf)
10、内存映射的二进制文件mmap
P149
11、文件路径名的操作os.path中的函数
os.path #对于任何的文件名的操作,都应使用os.path模块,而不是使用标准字符串操作来构造自己的代码(不应浪费时间重复造轮子),特别是为了可移植性考虑时更应如此
In [74]: path = '/Users/beazley/Data/data.csv'
In [75]: os.path.basename(path)
Out[75]: 'data.csv'
In [76]: os.path.dirname(path)
Out[76]: '/Users/beazley/Data'
In [77]: os.path.join('tmp', 'data', os.path.basename(path))
Out[77]: 'tmp\\data\\data.csv'
In [78]: path = '~/Data/data.csv'
In [79]: os.path.expanduser(path)
Out[79]: 'C:\\Users\\Administrator.SC-201908130831/Data/data.csv'
In [81]: os.path.splitext(path)
Out[81]: ('~/Data/data', '.csv')
12、测试文件是否存在os.path.exists()
操作时注意文件权限问题,尤其是获取元数据时;
In [82]: os.path.exists(r'c:/test.txt')
Out[82]: True
In [83]: os.path.isfile(r'c:/test.txt')
Out[83]: True
In [84]: os.path.isdir(r'c:/test.txt')
Out[84]: False
In [85]: os.path.islink(r'c:/test.txt')
Out[85]: False
In [86]: os.path.realpath('c:/test.txt')
Out[86]: 'c:\\test.txt'
In [87]: os.path.getsize('c:/test.txt')
Out[87]: 14
In [88]: os.path.getmtime('c:/test.txt')
Out[88]: 1574055034.3199937
In [89]: import time
In [90]: time.ctime(os.path.getmtime(r'c:/test.txt'))
Out[90]: 'Mon Nov 18 13:30:34 2019'
13、获取目录中的文件列表os.listdir()
os.listdir() #返回目录中所有文件列表,包括文件|子目录|符号链接等,如果还想获取其它元信息,要用到os.path|os.stat()来收集数据;另,可结合os.path|列表推导过滤数据;os.listdir()返回的实体列表会根据系统默认的文件名编码来解码,但有时也有不能正常解码的文件名
names = [name for name in os.listdir('somedir') if os.path.isfile(os.path.join('somedir', name))]
dirnames = [name for name in os.listdir('somedir') if os.path.isdir(os.path.join('somedir', name))]
pyfiles = [name for name in os.listdir('somedir') if name.endswith('.py')]
import glob
pyfiles = glob.glob('somedir/*.py')
from fnmatch import fnmatch
pyfiles = [name for name in os.listdir('somedir') if fnmatch(name, '*.py')]
pyfiles = glob.glob('*.py')
name_sz_date = [(name, os.path.getsize(name), os.path.getmtime(name)) for name in pyfiles]
for name, size, mtime in name_sz_date:
print(name, size, mtime)
file_metadata = [(name, os.stat(name)) for name in pyfiles]
for name, meta in file_metadata:
print(name, meta.st_size, meta.st_mtime)
14、忽略文件名编码
使用一个原始字节字符串来指定一个文件名即可,如with open('jalape\xf1o.txt', 'w') as f:
In [93]: sys.getfilesystemencoding() #默认所有文件名都会根据此编码和解码
Out[93]: 'utf-8'
In [94]: with open('jalape\xf1o.txt', 'w') as f:
...: f.write('Spicy!')
...:
In [95]: os.listdir('.')
Out[95]:
['chardetect.exe',
'jalapeño.txt',
'somefile']
In [96]: os.listdir(b'.')
Out[96]:
[b'chardetect.exe',
b'jalape\xc3\xb1o.txt',
b'somefile']
15、打印不合法的文件名
当打印文件名时出现UnicodeEncodeError|surrogates not allowed,解决办法:
def bad_filename(filename):
return repr(filename)[1:-1]
try:
print(filename)
except UnicodeEncodeError:
print(bad_filename(filename))
16、增加或改变已打开文件的编码io.TextIOWrapper()
import io
from urllib.request import urlopen
u = urlopen('www.python.org')
f = io.TextIOWrapper(u, encoding='utf-8')
text = f.read()
17、将字节写入文本文件
在文本模式打开的文件中写入原始的字节数据(将字节数据直接写入文件的缓冲区即可);
import sys
sys.stdout.buffer.write(b'Hello\n') #sys.stdout.write('Hello\n')
In [107]: sys.stdout.buffer.write(b'Hello\n')
Hello
Out[107]: 6
18、将文件描述符包装成文件对象
将对应于OS上一个已打开的I/O通道的整型文件描述符(如文件|管道|套接字),包装成一个更高层的py文件对象;
一个文件描述符和一个打开的普通文件是不一样的,文件描述符仅仅是一个由操作系统指定的整数,用来指代某个系统的I/O通道,可用open()将其包装成一个py的文件对象;
在Unix中,这种包装文件描述符的技术可方便地将一个类文件接口作用于一个以不同方式打开的I/O通道上,如管道|套接字;
In [109]: fd = os.open(r'c:/test.txt', os.O_WRONLY|os.O_CREAT)
In [110]: f = open(fd, 'wt') #f = open(fd, 'wt', closefd=False),当高层的文件对象被关闭或破坏时,底层的文件描述符也会被关闭,如果这不是我们想要的可用closefd=False
In [111]: f.write('hhhhhhhhhhhhh\n')
Out[111]: 14
In [112]: f.close()
19、创建临时文件和文件夹tempfile.TemporaryFile|tempfile.NamedTemporaryFile|tempfile.TemporaryDirectory
程序执行时创建一个临时文件或目录,使用完后自动销毁;
from tempfile import TemporaryFile, NamedTemporaryFile, TemporaryDirectory
在unix上,TemporaryFile()创建的文件都是匿名的,甚至连目录都没有,如果想打破这个限制可用NamedTemporaryFile()代替,f.name属性为临时文件的文件名,当需要将文件名传递给其它代码来打开这个文件时会有用,和TemporaryFile()一样,文件关闭时会被自动删除掉,如果不这么做用delte=False
另,tempfile.mkstemp()|tempfile.mkdtemp(),可在更低级别创建临时文件和目录,并不会做进一步的管理,仅返回一个原始的OS文件描述符,需要自己将它转为一个真正的文件对象并清理这些文件;
tempfile.gettempdir() #临时文件在系统默认的位置被创建,如/var/tmp/
f = NamedTemporaryFile(prefix='mytemp', suffix='.txt', dir='/tmp') #所有临时文件相关函数都允许通过关键字参数prefix|suffix|dir来自定义目录及命名规则
f.name
In [115]: with TemporaryFile('w+t') as f: #模式'w+t'|'w+b',这个模式同时支持rw操作,还支持跟open()一样的参数encoding='utf-8',errors='ignore'
...: f.write('Hello world\n')
...: f.write('Testing\n')
...: f.seek(0)
...: data = f.read()
...: print(data)
...:
Hello world
Testing
In [116]: from tempfile import NamedTemporaryFile
In [117]: with NamedTemporaryFile('w+t') as f:
...: print(f.name)
...:
C:\Users\ADMINI~1.SC-\AppData\Local\Temp\tmpuim869zd
In [118]: from tempfile import TemporaryDirectory
In [119]: with TemporaryDirectory() as dirname:
...: print(dirname)
...:
C:\Users\ADMINI~1.SC-\AppData\Local\Temp\tmppff7oo_8
In [121]: from tempfile import gettempdir
In [122]: gettempdir() #临时文件在系统默认的位置被创建,如/var/tmp/
Out[122]: 'C:\\Users\\ADMINI~1.SC-\\AppData\\Local\\Temp'
20、与串行端口的数据通信serial
典型场景是和一些硬件设备打交道,如机器人|传感器;
所有涉及到串口的I/O都是二进制模式的,因此确保代码使用的是字节而不是文本,另当需要创建二进制编码的指令或数据包时,struct模块有用;
pip install pySerial #提供了对高级特性的支持,如超时|控制流|缓冲区刷新|握手协议等
21、序列化py对象pickle
import pickle
pickle.dump()
pickle.dumps() #将一个对象转储为一个字符串
pickle.load()
pickle.loads() #从字节流中恢复一个对象
六、数据编码和处理
1、读写csv数据csv.reader()|csv.DictReader()|csv.writer()|csv.DictWriter()
应优先选择csv模块来解析csv数据,如果自己手动分割row = line.split(',')还要处理一些棘手的细节问题,如被引号包围的字段值中有逗号程序会出错;
默认csv可识别excel所使用的csv编码规则,会带来最好的兼容性;
支持用tab分割的数据,f_csv = csv.reader(f, delimiter='\t')
读取数据时使用命名元组,要注意对列名进行合法性认证(要是py标识符),否则报ValueError;
csv产生的数据都是字符串类型的,它不会做任何其它类型的转换,要自己手动实现;
如果读取csv数据的目的是做数据分析和统计,用Pandas包,它包含了一个非常方便的函数pandas.read_csv()可加载csv数据到一个DataFrame对象中,然后利用这个对象可生成各种形式的统计|过滤数据及其它高级操作;
In [1]: import csv
In [2]: with open(r'c:/test.txt') as f:
返回reader对象
返回列表
...: for row in f_csv:
...: print(row) #访问字段需用row[0]这种方式
...:
['AA', '39.48', '6/11/2007', '9:36am', '-0.18', '181800']
['AIG', '71.38', '6/11/2007', '9:36am', '-0.15', '195500']
['AXP', '62.58', '6/11/2007', '9:36am', '-0.46', '935000']
['BA', '98.31', '6/11/2007', '9:36am', '+0.12', '104800']
['C', '53.08', '6/11/2007', '9:36am', '-0.25', '360900']
['CAT78.29"6/11/2007""9:36"0.232254']
In [6]: with open(r'c:/test.txt') as f:
...: f_csv = csv.reader(f)
...: headers = next(f_csv)
...: Row = namedtuple('Row', headers)
...: for r in f_csv:
...: row = Row(*r)
...: print(row) #可用row.Symbol,row.Change访问,前提列名要为合法的py标识符,否则要改原始的列名
...:
Row(Symbol='AA', Price='39.48', Date='6/11/2007', Time='9:36am', Change='-0.18', Volume='181800')
Row(Symbol='AIG', Price='71.38', Date='6/11/2007', Time='9:36am', Change='-0.15', Volume='195500')
Row(Symbol='AXP', Price='62.58', Date='6/11/2007', Time='9:36am', Change='-0.46', Volume='935000')
Row(Symbol='BA', Price='98.31', Date='6/11/2007', Time='9:36am', Change='+0.12', Volume='104800')
Row(Symbol='C', Price='53.08', Date='6/11/2007', Time='9:36am', Change='-0.25', Volume='360900')
Row(Symbol='CAT', Price='78.29', Date='6/11/2007', Time='9:36am', Change='-0.23', Volume='225400')
In [12]: with open(r'c:/test.txt') as f:
...: f_csv = csv.DictReader(f)
...: for row in f_csv:
...: print(row) #可用row['Symbol']访问
...:
OrderedDict([('Symbol', 'AA'), ('Price', '39.48'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.18'), ('Volume', '181800')])
OrderedDict([('Symbol', 'AIG'), ('Price', '71.38'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.15'), ('Volume', '195500')])
OrderedDict([('Symbol', 'AXP'), ('Price', '62.58'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.46'), ('Volume', '935000')])
OrderedDict([('Symbol', 'BA'), ('Price', '98.31'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '+0.12'), ('Volume', '104800')])
OrderedDict([('Symbol', 'C'), ('Price', '53.08'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.25'), ('Volume', '360900')])
OrderedDict([('Symbol', 'CAT'), ('Price', '78.29'), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', '-0.23'), ('Volume', '225400')])
In [15]: headers = ['Symbol','Price','Date','Time','Change','Volume']
In [17]: rows = [('AA', 39.48, '6/11/2007', '9:36am', -0.18, 181800),('AIG', 71.38, '6/11/2007', '9:36am', -0.15, 19550
...: 0),('AXP', 62.58, '6/11/2007', '9:36am', -0.46, 935000)]
In [19]: with open(r'c:/test.txt','w') as f:
...: f_csv = csv.writer(f)
...: f_csv.writerow(headers)
是一行一行写入,writerows()是一次写多行
...:
In [20]: rows = [{'Symbol':'AA', 'Price':39.48, 'Date':'6/11/2007','Time':'9:36am', 'Change':-0.18, 'Volume':181800},{'
...: Symbol':'AIG', 'Price': 71.38, 'Date':'6/11/2007','Time':'9:36am', 'Change':-0.15, 'Volume': 195500},{'Symbol'
...: :'AXP', 'Price': 62.58, 'Date':'6/11/2007','Time':'9:36am', 'Change':-0.46, 'Volume': 935000}]
In [21]: with open(r'c:/test.txt','w') as f:
...: f_csv = csv.DictWriter(f, headers)
...: f_csv.writeheader()
...: f_csv.writerows(rows)
...:
In [22]: col_types = [str, float, str, str, float, int]
In [23]: with open(r'c:/test.txt') as f:
...: f_csv = csv.reader(f)
...: headers = next(f_csv)
...: for row in f_csv:
...: row = tuple(convert(value) for convert, value in zip(col_types, row))
...: print(row)
...:
()
('AA', 39.48, '6/11/2007', '9:36am', -0.18, 181800)
()
('AIG', 71.38, '6/11/2007', '9:36am', -0.15, 195500)
()
('AXP', 62.58, '6/11/2007', '9:36am', -0.46, 935000)
()
In [24]: field_types = [('Price', float),('Change', float),('Volume', int)]
In [25]: with open(r'c:/test.txt') as f:
...: for row in csv.DictReader(f):
...: row.update((key, convert(row[key])) for key, convert in field_types)
...: print(row)
...:
OrderedDict([('Symbol', 'AA'), ('Price', 39.48), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', -0.18), ('Volume', 181800)])
OrderedDict([('Symbol', 'AIG'), ('Price', 71.38), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', -0.15), ('Volume', 195500)])
OrderedDict([('Symbol', 'AXP'), ('Price', 62.58), ('Date', '6/11/2007'), ('Time', '9:36am'), ('Change', -0.46), ('Volume', 935000)])
2、读写json数据json.dumps()|json.loads()
json编码支持的基本数据类型为None|bool|int|float|str及包含这些类型数据的list|tuple|dict,对于dict,key需要是字符串类型(如果是非字符串类型会转为字符串);
为遵循json规范,应只编码py的list|dict;
在web应用中,顶层对象被编码为一个dict是一个标准做法;
json编码格式对于py语法几乎是完全一样的,有一些小差异,如True-true,False-false,None-null;
json.dumps() #py-->json,在编码json时,若想有漂亮的格式化后输出,用indent参数,使得输出和pprint()类似
json.loads() #json-->py数据结构,一般json解码会根据提供的数据创建dict|list,如果想创建其它类型的对象,用object_pairs_hook或object_hook参数;
from pprint import pprint #可用此函数代替print(),它会按key的字母顺序并以一种更美观的方式输出
In [28]: import json
In [26]: data = {'name': 'ACME', 'shares':100, 'price':542.23}
In [30]: json_str = json.dumps(data) #py数据结构-->json
In [31]: json_str
Out[31]: '{"name": "ACME", "shares": 100, "price": 542.23}'
In [32]: data = json.loads(json_str) #json-->py
In [33]: data
Out[33]: {'name': 'ACME', 'shares': 100, 'price': 542.23}
In [35]: from collections import OrderedDict
In [36]: json_str
Out[36]: '{"name": "ACME", "shares": 100, "price": 542.23}'
In [38]: data = json.loads(json_str, object_pairs_hook=OrderedDict) #json-->py的OrderedDict
In [39]: data
Out[39]: OrderedDict([('name', 'ACME'), ('shares', 100), ('price', 542.23)])
In [40]: class PyObject:
...: def __init__(self, d):
...: self.__dict__ = d
...:
In [41]: data = json.loads(json_str, object_hook=PyObject) #json-->py对象,json解码后的字典作为一个单个参数传递给__init__(),然后就可以随心所欲的使用了
In [42]: data
Out[42]: <__main__.PyObject at 0x1d09dff9278>
In [43]: data.name
Out[43]: 'ACME'
In [46]: data
Out[46]: {'name': 'ACME', 'shares': 100, 'price': 542.23}
In [47]: json_str = json.dumps(data, indent=4)
In [50]: print(json_str)
{
"name": "ACME",
"shares": 100,
"price": 542.23
}
3、解析简单的xml数据xml.etree.ElementTree.parse()
P179
4、增量式解析大型xml文件xml.etree.ElementTruee.iterparse()
用尽可能少的内存从一个超大的xml文档中提取数据;
只要遇到增量式的数据处理,都要想到用迭代器和生成器;
P179
5、将字典转为xmlxml.etree.ElementTruee.Element
P183
6、解析和修改xmlxml.etree.ElementTree.parse()|xml.etree.ElementTree.Element
P185
7、利用命名空间解析xml文档
P187
8、与关系型数据库的交互
py中多行数据的标准方式是一个由元组构成的列表;
import sqlite3
db = sqlite3.connect('database.db')
c = db.cursor()
c.execute('') #执行查询语句,另c.executemany('')插入多条记录
db.commit()
9、编码解码16进制数binascii.b2a_hex()|binascii.a2b_hex()|base64.b16encode()|base64.b16decode()
binascii和base64,这2种技术的主要不同在于大小写的处理,base64只能操作大写形式的16进制字母,而binascii大小写都能处理;
注意:
编码函数所产生的输出总是一个字节字符串,如果想强制以unicode输出,要print(h.decode('ascii));
解码16进制数时,a2b_hex()和b16decode()可接受字节或unicode字符串,但unicode字符串必须仅只包含ascii编码的16进制数;
In [51]: import binascii
In [52]: s = b'hello'
In [53]: h = binascii.b2a_hex(s)
In [54]: h
Out[54]: b'68656c6c6f'
In [55]: binascii.a2b_hex(h)
Out[55]: b'hello'
In [56]: import base64
In [57]: h = base64.b16encode(s)
In [58]: h
Out[58]: b'68656C6C6F'
In [59]: base64.b16decode(h)
Out[59]: b'hello'
10、编码解码base64数据base64.b64encode()|base64.b64decode()
base64编码仅用于面向字节的数据,如字节字符串和字节数组,编码处理的输出结果是一个字节字符串;
如果想混合使用base64编码的数据和unicode文本,必须添加一个额外的解码步骤,a = base64.b64encode(s).decode('ascii');
解码base64时,字节字符串和unicode文本都可作为参数,但unicode字符串只能包含ascii字符;
In [60]: s
Out[60]: b'hello'
In [61]: a = base64.b64encode(s)
In [62]: a
Out[62]: b'aGVsbG8='
In [63]: base64.b64decode(a)
Out[63]: b'hello'
11、读写二进制数组数据struct
P192
12、读取嵌套和可变长二进制数据struct
struct模块可被用来编码解码几乎所有类型的二进制的数据结构;
P196
13、数据的累加与统计操作pandas
对于任何涉及到统计|时间序列|其它相关技术的数据分析问题,可考虑用pandas;
P206
十、模块与包
1、构建一个模块的层级包__init__.py
封装成包,确保每个目录都定义了__init__.py文件,就能执行import语句;
绝大多数时候让__init__.py空着,但有些情况下可能包含代码,如此文件能用来自动加载子模块;
__init__.py还可将多个文件合并到一个逻辑命名空间;
2、控制模块被全部导入的内容__all__
somemodule.py #当用from somemodule import *时,希望从模块或包导出的符号进行精确控制;如果__all__中包含未定义的名字,报AttributeError
def spam():
pass
def grok():
pass
blah = 42
__all__ = ['spam', 'grok']
3、使用相对路径名导入包中子模块
mypackage/
__init__.py
A/
__init__.py
spam.py
grok.py
B/
__init__.py
bar.py
from . import grok #在mypackage.A.spam中,既可用相对路径也可用绝对路径
from ..B import bar ##在mypackage.A.spam中
4、将模块分割成多个文件
5、利用命名空间导入目录分散的代码
6、

