-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathsearch.xml
288 lines (288 loc) · 74.7 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[类与对象-1-改变对象实例的字符串显示]]></title>
<url>%2F2018%2F04%2F10%2F%E7%B1%BB%E4%B8%8E%E5%AF%B9%E8%B1%A1-1-%E6%94%B9%E5%8F%98%E5%AF%B9%E8%B1%A1%E5%AE%9E%E4%BE%8B%E7%9A%84%E5%AD%97%E7%AC%A6%E4%B8%B2%E6%98%BE%E7%A4%BA%2F</url>
<content type="text"><![CDATA[问题描述如果我们想改变对象实例的打印或显示输出,让它们更具可读性,应该怎么做呢? 解决方案想要改变一个对象实例的字符串显示,可重新定义它的 __str__() 和 __repr__() 方法。 代码演示: 12345678910class Pair: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) def __str__(self): return '({0.x!s}, {0.y!s})'.format(self) __repr__() 方法返回一个实例的代码表示形式,通常用来重新构造这个实例。 内置的 repr() 函数返回这个字符串,跟我们使用交互式解释器显示的值是一样的。 __str__() 方法将实例转换为一个字符串,使用 str() 或 print() 函数会输出这个字符串。 简而言之: 如果要把一个实例通过 print 显示成字符串,就要实现 __str__(),如果要在交互命令行下直接通过调用实例显示,就要实现 __repr__() 。 代码示例: 123456>>> p = Pair(3, 4)>>> pPair(3, 4) # 输出的是 __repr__() >>> print(p)(3, 4) # 输出的是 __str__() >>> 扩展讨论为何要自定义 __repr__() 和 __str__() ?自定义 __repr__() 和 __str__() 通常是很好的习惯,因为它能简化调试和实例输出。 例如,对于某个对象的实例,如果我们仅仅只是打印输出或日志输出,那么我们就会看到更加详细与有用的实例信息。 简而言之: __repr__() 是给程序员看的。 __str__() 是给用户看的。 几种格式化方法上面的例子中我们还演示了在格式化的时候怎样使用不同的字符串表现形式。 !r 格式化代码!r 格式化代码指明输出使用 __repr__() 来代替默认的 __str__() 。 用前面的类来测试一下: 123456>>> p = Pair(3, 4)>>> print('p is {0!r}'.format(p))p is Pair(3, 4) # 使用 !r 格式化后输出的是 __repr__()>>> print('p is {0}'.format(p))p is (3, 4) # 没有格式化的默认输出是 __str__()>>> {0.x} 格式化代码上面的 format() 方法的使用看上去很有趣,格式化代码 {0.x} 对应的是第 1 个参数的 x 属性。 因此,在下面的方法中,0 实际上指的就是 self 本身。 示例代码: 12def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) % 格式化代码作为上面这种实现的一个替代,我们也可以使用 % 操作符。 示例代码: 12def __repr__(self): return 'Pair(%r, %r)' % (self.x, self.y)]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>notes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-20-合并多个字典或映射]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-20-%E5%90%88%E5%B9%B6%E5%A4%9A%E4%B8%AA%E5%AD%97%E5%85%B8%E6%88%96%E6%98%A0%E5%B0%84%2F</url>
<content type="text"><![CDATA[问题描述我们想把多个字典或者映射合并,然后再执行某些操作(如:查找或校验某些 key 是否存在)。 应该如何做呢? 解决方案使用 collections 模块中的 ChainMap 类。 示例代码: 12345678910111213141516from collections import ChainMapdef test_chainmap(): """合并多个字典""" a_dict = {'x': 1, 'z': 3} b_dict = {'y': 2, 'z': 4} # 合并两个字典(映射),从逻辑上变成了一个 cm = ChainMap(a_dict, b_dict) assert cm['x'] == 1 assert cm['y'] == 2 assert cm['z'] == 3 # 遇到重复的 key,总是返回第一次出现的 value # 因此不管访问几次,cm['z'] 总是返回字典 a 中的 value。 assert cm['z'] == 3 扩展讨论一个 ChainMap 接受多个字典并将它们在逻辑上变为一个字典。 然后, 这些字典并不是真的合并在一起了, ChainMap 类只是在内部创建了一个容纳这些字典的列表并重新定义了一些常见的字典操作来遍历这个列表。 ChainMap的字典操作使用 ChainMap 合并后,大部分字典操作都是可以正常使用的。 示例代码: 1234567891011121314151617181920def test_chainmap_operate(): """ChainMap的字典操作""" a_dict = {'x': 1, 'z': 3} b_dict = {'y': 2, 'z': 4} cm = ChainMap(a_dict, b_dict) assert len(cm) == 3 assert list(cm.keys()) == ['x', 'y', 'z'] assert list(cm.values()) == [1, 2, 3] # ChainMap的更新操作 cm['z'] = 10 cm['w'] = 40 # 结果只影响列表中第一个字典,也就是 a_dict assert a_dict == {'w': 40, 'x': 1, 'z': 10} # ChainMap的删除操作 del cm['x'] # 结果也只影响列表中第一个字典,也就是 a_dict assert a_dict == {'w': 40, 'z': 10} ChainMap 对于编程语言中的作用范围变量(比如 globals , locals 等)是非常有用的。 ChainMap更多的用法1234567891011121314151617181920212223>>> values = ChainMap()>>> values['x'] = 1>>> # 添加一个新的映射>>> values = values.new_child()>>> values['x'] = 2>>> # 添加一个新的映射>>> values = values.new_child()>>> values['x'] = 3>>> valuesChainMap({'x': 3}, {'x': 2}, {'x': 1})>>> values['x']3>>> # 放弃最后的映射>>> values = values.parents>>> values['x']2>>> #放弃最后的映射>>> values = values.parents>>> values['x']1>>> valuesChainMap({'x': 1})>>> update vs ChainMap也许你可能想使用 update() 合并两个字典。 比如: 1234567891011>>> a = {'x': 1, 'z': 3 }>>> b = {'y': 2, 'z': 4 }>>> merged = dict(b)>>> merged.update(a)>>> merged['x']1>>> merged['y']2>>> merged['z']3>>> 这样也OK,但它需要你创建一个完全不同的字典对象(或者是破坏现有字典结构)。而且,如原字典做了更新,合并后的新字典并不会同步更新。 比如: 123>>> a['x'] = 13>>> merged['x']1 而ChainMap 使用的是原来的字典,它不是创建新的字典,所以避免了上面所说的结果。 比如: 123456789>>> a = {'x': 1, 'z': 3 }>>> b = {'y': 2, 'z': 4 }>>> merged = ChainMap(a, b)>>> merged['x']1>>> a['x'] = 42>>> merged['x'] # Notice change to merged dicts42>>>]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-19-转换并同时计算数据]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-19-%E8%BD%AC%E6%8D%A2%E5%B9%B6%E5%90%8C%E6%97%B6%E8%AE%A1%E7%AE%97%E6%95%B0%E6%8D%AE%2F</url>
<content type="text"><![CDATA[问题描述如果我们需要在数据序列上执行聚集函数(比如 sum() , min() , max() ), 首先需要转换或者过滤数据,如何做更好呢? 解决方案一个非常优雅的方式就是使用一个生成器表达式参数去结合数据计算与转换。 比如,如果计算平方和,可以像下面这样做: 12nums = [1, 2, 3, 4, 5]s = sum(x * x for x in nums) 下面是更多的例子: 123456789101112131415161718# Determine if any .py files exist in a directoryimport osfiles = os.listdir('dirname')if any(name.endswith('.py') for name in files): print('There be python!')else: print('Sorry, no python.')# Output a tuple as CSVs = ('ACME', 50, 123.45)print(','.join(str(x) for x in s))# Data reduction across fields of a data structureportfolio = [ {'name':'GOOG', 'shares': 50}, {'name':'YHOO', 'shares': 75}, {'name':'AOL', 'shares': 20}, {'name':'SCOX', 'shares': 65}]min_shares = min(s['shares'] for s in portfolio) 扩展讨论上面的示例演示了当生成器表达式作为一个单独参数传递给函数时候的巧妙语法(你并不需要多加一个括号)。 比如,下面这些语句是等效的: 12s = sum((x * x for x in nums)) # 显示的传递一个生成器表达式对象s = sum(x * x for x in nums) # 更加优雅的实现方式,省略了括号 使用一个生成器表达式作为参数会比先创建一个临时列表更加高效和优雅。比如,如果不使用生成器表达式的话,可能会考虑使用下面的实现方式: 12nums = [1, 2, 3, 4, 5]s = sum([x * x for x in nums]) 这种方式同样可以达到想要的效果,但是它会多一个步骤,先创建一个额外的列表。 对于小型列表可能没什么关系,但是如果元素数量非常大的时候, 它会创建一个巨大的仅仅被使用一次就被丢弃的临时数据结构。而生成器方案会以迭代的方式转换数据,因此更省内存。 在使用一些聚集函数比如 min() 和 max() 的时候你可能更加倾向于使用生成器版本, 它们接受的一个 key 关键字参数或许对你很有帮助。 比如,在上面的证券例子中,你可能会考虑下面的实现版本: 1234# Original: Returns 20min_shares = min(s['shares'] for s in portfolio)# Alternative: Returns {'name': 'AOL', 'shares': 20}min_shares = min(portfolio, key=lambda s: s['shares'])]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-18-映射名称到序列元素]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-18-%E6%98%A0%E5%B0%84%E5%90%8D%E7%A7%B0%E5%88%B0%E5%BA%8F%E5%88%97%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[问题描述假设我们有一段通过下标访问列表或元组中元素的代码,有时这样的代码会难以阅读 怎么办呢?如何通过名称来访问这些元素呢? 换句话说:如何给序列起个名呢? 解决方案使用 collections.namedtuple() 能解决这个问题。 这个函数实际上是一个返回 Python 中标准元组类型子类的一个工厂方法。 我们需要向 namedtuple 传递一个「类型名」和需要的「字段」给它,然后它就会返回一个「类」,我们可以初始化这个类,为定义的字段传递值。 代码示例: 12345678910>>> from collections import namedtuple>>> Subscriber = namedtuple('Subscriber', ['addr', 'joined'])>>> sub = Subscriber('[email protected]', '2012-10-19')>>> subSubscriber(addr='[email protected]', joined='2012-10-19')>>> sub.addr'[email protected]'>>> sub.joined'2012-10-19'>>> 尽管 namedtuple 的实例看起来像一个普通的类实例,但是它跟元组类型是可交换的,支持所有的普通元组操作,比如索引和解压。 比如: 12345678>>> len(sub)2>>> addr, joined = sub>>> addr'[email protected]'>>> joined'2012-10-19'>>> 命名元组的一个主要用途是将代码从下标操作中解脱出来。 因此,如果从数据库调用中返回了一个很大的元组列表,通过下标去操作其中的元素, 当在表中添加了新的列的时候代码可能就会出错了。 但是如果使用了命名元组,那么就不会有这样的顾虑。 为了说明清楚,下面是使用普通元组的代码: 12345def compute_cost(records): total = 0.0 for rec in records: total += rec[1] * rec[2] return total 下标操作通常会让代码表意不清晰,并且非常依赖 records 的结构。 下面是使用命名元组的版本: 123456789from collections import namedtupleStock = namedtuple('Stock', ['name', 'shares', 'price'])def compute_cost(records): total = 0.0 for rec in records: s = Stock(*rec) total += s.shares * s.price return total 扩展讨论命名元组另一个用途就是作为字典的替代,因为字典存储需要更多的内存空间。 如果你需要构建一个非常大的包含字典的数据结构,那么使用命名元组会更加高效。 但是需要注意的是,不像字典那样,一个命名元组是不可更改的。 比如: 12345678>>> s = Stock('ACME', 100, 123.45)>>> sStock(name='ACME', shares=100, price=123.45)>>> s.shares = 75Traceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: can't set attribute>>> 如果真的需要改变属性的值,那么可以使用命名元组实例的 _replace() 方法, 它会创建一个全新的命名元组并将对应的字段用新的值取代。 比如: 1234>>> s = s._replace(shares=75)>>> sStock(name='ACME', shares=75, price=123.45)>>> _replace() 方法还有一个很有用的特性就是当你的命名元组拥有可选或者缺失字段时候, 它是一个非常方便的填充数据的方法。 你可以先创建一个包含缺省值的原型元组,然后使用 _replace() 方法创建新的值被更新过的实例。 比如: 12345678910from collections import namedtupleStock = namedtuple('Stock', ['name', 'shares', 'price', 'date', 'time'])# Create a prototype instancestock_prototype = Stock('', 0, 0.0, None, None)# Function to convert a dictionary to a Stockdef dict_to_stock(s): return stock_prototype._replace(**s) 下面是它的使用方法: 1234567>>> a = {'name': 'ACME', 'shares': 100, 'price': 123.45}>>> dict_to_stock(a)Stock(name='ACME', shares=100, price=123.45, date=None, time=None)>>> b = {'name': 'ACME', 'shares': 100, 'price': 123.45, 'date': '12/17/2012'}>>> dict_to_stock(b)Stock(name='ACME', shares=100, price=123.45, date='12/17/2012', time=None)>>> 最后要说的是: 如果要定义一个需要更新很多实例属性的高效数据结构,那么命名元组并不是最佳选择。 这时候我们应该考虑定义一个包含 __slots__ 方法的类。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-17-从字典中提取子集]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-17-%E4%BB%8E%E5%AD%97%E5%85%B8%E4%B8%AD%E6%8F%90%E5%8F%96%E5%AD%90%E9%9B%86%2F</url>
<content type="text"><![CDATA[问题描述我们想利用一个字典的子集构造一个新字典,怎么做呢? 解决方案最简单的方式是使用字典推导。 示例代码: 123456789101112prices = { 'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.20, 'FB': 10.75}# prices 大于 200 的元素创造一个新字典p1 = {key: value for key, value in prices.items() if value > 200}# 包含在tech_names中的key创建一个字典tech_names = {'AAPL', 'IBM', 'HPQ', 'MSFT'}p2 = {key: value for key, value in prices.items() if key in tech_names} 扩展讨论大多数情况下字典推导能做到的,通过创建一个元组序列然后把它传给 dict() 函数也能实现。 比如: 1p1 = dict((key, value) for key, value in prices.items() if value > 200) 但是,字典推导方式表意更清晰,并且实际上也会运行的更快些 (在这个例子中,实际测试几乎比 dcit() 函数方式快整整一倍)。 有时候完成同一件事会有多种方式。比如,第二个例子程序也可以像这样重写: 123# Make a dictionary of tech stockstech_names = { 'AAPL', 'IBM', 'HPQ', 'MSFT' }p2 = { key:prices[key] for key in prices.keys() & tech_names } 但是,运行时间测试结果显示这种方案大概比第一种方案慢 1.6 倍。 如果对程序运行性能要求比较高的话,需要花点时间去做计时测试。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-16-过滤序列元素]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-16-%E8%BF%87%E6%BB%A4%E5%BA%8F%E5%88%97%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[问题描述如果我们想利用一些规则从一个数据序列中提取出需要的值或者是缩短序列,应该如何做呢? 解决方案最简单的过滤序列元素的方法就是使用列表推导。 比如: 123456>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]>>> [n for n in mylist if n > 0][1, 4, 10, 2, 3]>>> [n for n in mylist if n < 0][-5, -7, -1]>>> 使用列表推导的一个潜在缺陷就是如果输入非常大的时候会产生一个非常大的结果集,占用大量内存。 如果对内存比较敏感,那么我们可以使用生成器表达式迭代产生过滤的元素。 比如: 123456789101112>>> pos = (n for n in mylist if n > 0)>>> pos<generator object <genexpr> at 0x1006a0eb0>>>> for x in pos:... print(x)...141023>>> 有时候,过滤规则比较复杂,不能简单的在列表推导或者生成器表达式中表达出来。 比如,过滤的时候需要处理一些异常或者其他复杂情况。 这时候我们可以将过滤代码放到一个函数中, 然后使用内建的 filter() 函数。 示例代码: 12345678910values = ['1', '2', '-3', '-', '4', 'N/A', '5']def is_int(val): try: x = int(val) return True except ValueError: return Falseivals = list(filter(is_int, values))print(ivals)# Outputs ['1', '2', '-3', '4', '5'] filter() 函数创建了一个迭代器,因此如果你想得到一个列表的话,就得像示例那样使用 list()去转换。 讨论列表推导和生成器表达式通常情况下是过滤数据最简单的方式。 其实它们还能在过滤的时候转换数据。 比如: 12345>>> mylist = [1, 4, -5, 10, -7, 2, 3, -1]>>> import math>>> [math.sqrt(n) for n in mylist if n > 0][1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]>>> 过滤操作的一个变种就是将不符合条件的值用新的值代替,而不是丢弃它们。 比如, 在一列数据中我们可能不仅想找到正数,而且还想将不是正数的数替换成指定的数。 通过将过滤条件放到条件表达式中去,可以很容易的解决这个问题。 就像这样: 1234567>>> clip_neg = [n if n > 0 else 0 for n in mylist]>>> clip_neg[1, 4, 0, 10, 0, 2, 3, 0]>>> clip_pos = [n if n < 0 else 0 for n in mylist]>>> clip_pos[0, 0, -5, 0, -7, 0, 0, -1]>>> itertools.compress() 另外一个值得关注的过滤工具就是 itertools.compress() , 它以一个 iterable 对象和一个相对应的 Boolean 选择器序列作为输入参数。 然后输出 iterable 对象中对应选择器为 True 的元素。 当需要用另外一个相关联的序列来过滤某个序列的时候,这个函数是非常有用的。 比如,假如现在有下面两列数据: 1234567891011addresses = [ '5412 N CLARK', '5148 N CLARK', '5800 E 58TH', '2122 N CLARK', '5645 N RAVENSWOOD', '1060 W ADDISON', '4801 N BROADWAY', '1039 W GRANVILLE',]counts = [ 0, 3, 10, 4, 1, 7, 6, 1] 现在我们想将那些对应 count 值大于 5 的地址全部输出。 可以这样做: 1234567>>> from itertools import compress>>> more5 = [n > 5 for n in counts]>>> more5[False, False, True, False, False, True, True, False]>>> list(compress(addresses, more5))['5800 E 58TH', '1060 W ADDISON', '4801 N BROADWAY']>>> 这里的关键点在于先创建一个 Boolean 序列,指示哪些元素符合条件。 然后 compress() 函数根据这个序列去选择输出对应位置为 True 的元素。 和 filter() 函数类似, compress() 也是返回的一个迭代器。 因此,如果我们需要得到一个列表, 需要使用 list() 来将结果转换为列表类型。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-15-通过某个字段将记录分组]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-15-%E9%80%9A%E8%BF%87%E6%9F%90%E4%B8%AA%E5%AD%97%E6%AE%B5%E5%B0%86%E8%AE%B0%E5%BD%95%E5%88%86%E7%BB%84%2F</url>
<content type="text"><![CDATA[问题描述有一个字典或者实例的序列,如: 12345678910rows = [ {'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'},] 然后我们想根据某个特定的字段(比如 date )来分组进行迭代访问,如何做呢? 解决方案现在假设我们想在按 date 分组后的数据块上进行迭代访问。 为了这样做, 我们首先需要按照指定的字段(这里就是 date )排序, 然后调用 itertools.groupby() 函数: 123456789101112131415161718192021from itertools import groupbyfrom operator import itemgetterrows = [ {'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'},]def test_group_by(): # 通过 date 进行排序 rows.sort(key=itemgetter('date')) # 通过 itertools.groupby 进行分组迭代,日期相同的会被分到同一组 for date, items in groupby(rows, key=itemgetter('date')): print(date) for i in items: print(' ', i) 运行结果: 12345678910111207/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'} 扩展讨论groupby() 函数扫描整个序列并且查找连续相同值(或者根据指定 key 函数返回值相同)的元素序列。 在每次迭代的时候,它会返回一个值和一个迭代器对象, 这个迭代器对象生成的元素值等于上面那个值组中的所有对象。 有一个非常重要的准备步骤是「要根据指定的字段将数据排序」。 因为 groupby() 仅仅检查连续的元素,如果事先并没有排序完成的话,分组函数将得不到想要的结果。 如果我们仅仅只是想根据 date 字段将数据分组到一个大的数据结构中去,并且允许随机访问, 那最好使用 defaultdict() 来构建一个多值字典。 比如: 12345from collections import defaultdictrows_by_date = defaultdict(list) #一个多值字典for row in rows: rows_by_date[row['date']].append(row) 这样就可以很轻松的对每个指定日期访问对应的记录: 123456>>> for r in rows_by_date['07/01/2012']:... print(r)...{'date': '07/01/2012', 'address': '5412 N CLARK'}{'date': '07/01/2012', 'address': '4801 N BROADWAY'}>>> 在上面这个例子中,我们没有必要先将记录排序。因此,如果对内存占用不是很关心, 这种方式会比先排序然后再通过 groupby() 函数迭代的方式运行得快一些。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-14-排序不支持原生比较的对象]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-14-%E6%8E%92%E5%BA%8F%E4%B8%8D%E6%94%AF%E6%8C%81%E5%8E%9F%E7%94%9F%E6%AF%94%E8%BE%83%E7%9A%84%E5%AF%B9%E8%B1%A1%2F</url>
<content type="text"><![CDATA[问题描述如果我们想排序类型相同的对象,但是他们不支持原生的比较操作,怎么办呢? 解决方案内置的 sorted() 函数有一个关键字参数 key ,可以传入一个 callable 对象给它, 这个 callable对象对每个传入的对象返回一个值,这个值会被 sorted 用来排序这些对象。 比如, 如果在应用程序里面有一个 User 实例序列,并且我们希望通过他们的 user_id 属性进行排序,怎么办呢? 我们可以提供一个以 User 实例作为输入并输出对应 user_id 值的 callable 对象。 代码如下: 12345678910111213141516171819202122from operator import attrgetterclass User: def __init__(self, user_id): self.user_id = user_id def __repr__(self): return 'User({})'.format(self.user_id)def test_sorted_not_compare(): users = [User(23), User(3), User(99)] # 看一下顺序 print(users) # [User(23), User(3), User(99)] # 使用 lambda print(sorted(users, key=lambda u: u.user_id)) # [User(3), User(23), User(99)] # 使用 operator.attrgetter() 结果是相同的,但速度更快,还可以同时比较多个字段。 assert sorted(users, key=lambda u: u.user_id) == sorted(users, key=attrgetter('user_id')) 扩展讨论选择使用 lambda 函数或者是 attrgetter() 可能取决于个人喜好。 但是, attrgetter() 函数通常会运行的快点,并且还能同时允许多个字段进行比较。这个跟 operator.itemgetter() 函数作用于字典类型很类似。 例如,如果 User 实例还有一个 first_name 和 last_name 属性,那么可以向下面这样排序: 1by_name = sorted(users, key=attrgetter('last_name', 'first_name')) 同样需要注意的是,本文用到的技术同样适用于像 min() 和 max() 之类的函数。 比如: 12345>>> min(users, key=attrgetter('user_id'))User(3)>>> max(users, key=attrgetter('user_id'))User(99)>>>]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-13-通过某个关键字排序一个字典列表]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-13-%E9%80%9A%E8%BF%87%E6%9F%90%E4%B8%AA%E5%85%B3%E9%94%AE%E5%AD%97%E6%8E%92%E5%BA%8F%E4%B8%80%E4%B8%AA%E5%AD%97%E5%85%B8%E5%88%97%E8%A1%A8%2F</url>
<content type="text"><![CDATA[问题描述如果我们有一个字典列表,如何根据某个或某几个字典字段来排序这个列表呢? 解决方案通过使用 operator 模块的 itemgetter 函数,可以非常容易的排序这样的数据结构。 假设我们从数据库中检索出来网站会员信息列表,并且以下列的数据结构返回: 123456rows = [ {'fname': 'Brian', 'lname': 'Jones', 'uid': 1003}, {'fname': 'David', 'lname': 'Beazley', 'uid': 1002}, {'fname': 'John', 'lname': 'Cleese', 'uid': 1001}, {'fname': 'Big', 'lname': 'Jones', 'uid': 1004}] 根据任意的字典字段来排序输入结果行是很容易实现的。 代码示例: 123456789from operator import itemgetter# 按 fname 排序rows_by_fname = sorted(rows, key=itemgetter('fname'))# 按 uid 排序rows_by_uid = sorted(rows, key=itemgetter('uid'))print(rows_by_fname)print(rows_by_uid) 代码的输出如下: 12345678[{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'}][{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'},{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'}] itemgetter() 函数也支持多个 keys。 比如: 123# itemgetter 中使用多个 keyrows_by_lfname = sorted(rows, key=itemgetter('lname','fname'))print(rows_by_lfname) 会产生如下的输出: 1234[{'fname': 'David', 'uid': 1002, 'lname': 'Beazley'},{'fname': 'John', 'uid': 1001, 'lname': 'Cleese'},{'fname': 'Big', 'uid': 1004, 'lname': 'Jones'},{'fname': 'Brian', 'uid': 1003, 'lname': 'Jones'}] 扩展讨论在上面例子中, rows 被传递给接受一个关键字参数的 sorted() 内置函数。 这个参数是 callable类型,并且从 rows 中接受一个单一元素,然后返回被用来排序的值。 itemgetter() 函数就是负责创建这个 callable 对象的。 operator.itemgetter() 函数有一个被 rows 中的记录用来查找值的索引参数。可以是一个字典「键」名称, 一个整型值或者任何能够传入一个对象的 __getitem__() 方法的值。 如果我们传入多个索引参数给 itemgetter() ,它生成的 callable 对象会返回一个包含所有元素值的元组, 并且 sorted() 函数会根据这个元组中元素顺序去排序。 但我们想要同时在几个字段上面进行排序(比如通过姓和名来排序,也就是例子中的那样)的时候这种方法是很有用的。 itemgetter() 有时候也可以用 lambda 表达式代替,比如: 12rows_by_fname = sorted(rows, key=lambda r: r['fname'])rows_by_lfname = sorted(rows, key=lambda r: (r['lname'],r['fname'])) 这种方案也不错。 但是, 使用 itemgetter() 方式会运行的稍微快点。 因此,如果你对性能要求比较高的话就使用 itemgetter() 方式。 最后,不要忘了这节中展示的技术也同样适用于 min() 和 max() 等函数。 比如: 12345>>> min(rows, key=itemgetter('uid')){'fname': 'John', 'lname': 'Cleese', 'uid': 1001}>>> max(rows, key=itemgetter('uid')){'fname': 'Big', 'lname': 'Jones', 'uid': 1004}>>>]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-12-序列中出现次数最多的元素]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-12-%E5%BA%8F%E5%88%97%E4%B8%AD%E5%87%BA%E7%8E%B0%E6%AC%A1%E6%95%B0%E6%9C%80%E5%A4%9A%E7%9A%84%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[问题描述怎样找出一个序列中出现次数最多的元素呢? 解决方案collections.Counter 类就是专门为这类问题而设计的, 它甚至有一个有用的 most_common() 方法直接给了答案。 为了演示,先假设我们有一个单词列表并且想找出哪个单词出现频率最高。 你可以这样做: 1234567891011121314from collections import Counterwords = [ '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']word_counts = Counter(words)# 出现频率最高的3个单词top_three = word_counts.most_common(3)print(top_three)# Outputs [('eyes', 8), ('the', 5), ('look', 4)] 扩展讨论作为输入, Counter 对象可以接受任意的由可哈希(hashable)元素构成的序列对象。 在底层实现上,一个 Counter 对象就是一个字典,将元素映射到它出现的次数上。 比如: 12345>>> word_counts['not']1>>> word_counts['eyes']8>>> 如果我们想手动增加计数,可以简单的用加法: 1234567>>> morewords = ['why','are','you','not','looking','in','my','eyes']>>> for word in morewords:... word_counts[word] += 1...>>> word_counts['eyes']9>>> 或者你可以使用 update() 方法: 12>>> word_counts.update(morewords)>>> Counter 实例一个鲜为人知的特性是它们可以很容易的跟数学运算操作相结合。 比如: 1234567891011121314151617181920>>> a = Counter(words)>>> b = Counter(morewords)>>> aCounter({'eyes': 8, 'the': 5, 'look': 4, 'into': 3, 'my': 3, 'around': 2,"you're": 1, "don't": 1, 'under': 1, 'not': 1})>>> bCounter({'eyes': 1, 'looking': 1, 'are': 1, 'in': 1, 'not': 1, 'you': 1,'my': 1, 'why': 1})>>> # Combine counts>>> c = a + b>>> cCounter({'eyes': 9, 'the': 5, 'look': 4, 'my': 4, 'into': 3, 'not': 2,'around': 2, "you're": 1, "don't": 1, 'in': 1, 'why': 1,'looking': 1, 'are': 1, 'under': 1, 'you': 1})>>> # Subtract counts>>> d = a - b>>> dCounter({'eyes': 7, 'the': 5, 'look': 4, 'into': 3, 'my': 2, 'around': 2,"you're": 1, "don't": 1, 'under': 1})>>> 毫无疑问, Counter 对象在几乎所有需要制表或者计数数据的场合是非常有用的工具。 在解决这类问题的时候我们应该优先选择它,而不是手动的利用字典去实现。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-11-给切片命名]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-11-%E7%BB%99%E5%88%87%E7%89%87%E5%91%BD%E5%90%8D%2F</url>
<content type="text"><![CDATA[问题描述假如我们的程序已经出现了一大堆已无法直视的硬编码切片下标,应该如何清理呢? 解决方案假设我们有一段代码要从一个记录字符串中几个固定位置提取出特定的数据字段。 比如: 123###### 0123456789012345678901234567890123456789012345678901234567890'record = '....................100 .......513.25 ..........'cost = int(record[20:23]) * float(record[31:37]) 上面切片的写法可读性非常差,如果给切片命个名,可读性就会强很多。 改进代码,使其可读性更好: 123SHARES = slice(20, 23) # 给切片命名PRICE = slice(31, 37) # 给切片命名cost = int(record[SHARES]) * float(record[PRICE]) # 使用命名的切片 扩展讨论一般来讲,代码中如果出现大量的硬编码下标值会使得可读性和可维护性大大降低,给切片命名可以更加清晰的表达代码到底要做什么,可读性非常高。 内置的 slice() 函数创建了一个切片对象,可以被用在任何切片允许使用的地方。 比如: 123456789101112>>> items = [0, 1, 2, 3, 4, 5, 6]>>> a = slice(2, 4) # 给切片命名>>> items[2:4][2, 3]>>> items[a][2, 3]>>> items[a] = [10,11]>>> items[0, 1, 10, 11, 4, 5, 6]>>> del items[a]>>> items[0, 1, 4, 5, 6] 如果我们有一个切片对象 a,那么就可以分别调用它的 a.start , a.stop , a.step 属性来获取更多的信息。 比如: 12345678>>> a = slice(5, 50, 2)>>> a.start5>>> a.stop50>>> a.step2>>> 另外,我们还能通过调用切片的 indices(size) 方法将它映射到一个确定大小的序列上, 这个方法返回一个三元组 (start, stop, step) ,所有值都会被合适的缩小以满足边界限制, 从而使用的时候避免出现 IndexError 异常。 比如: 12345678910>>> s = 'HelloWorld'>>> a.indices(len(s))(5, 10, 2)>>> for i in range(*a.indices(len(s))):... print(s[i])...Wrd>>>]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-10-删除序列中重复的元素并保持顺序不变]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-10-%E5%88%A0%E9%99%A4%E5%BA%8F%E5%88%97%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E5%85%83%E7%B4%A0%E5%B9%B6%E4%BF%9D%E6%8C%81%E9%A1%BA%E5%BA%8F%E4%B8%8D%E5%8F%98%2F</url>
<content type="text"><![CDATA[问题描述怎样在一个序列中删除重复的元素并保持元素顺序不变? 解决方案如果序列上的值都是 hashable 类型,那么可以很简单的利用集合或者生成器来解决这个问题。 比如: 123456def dedupe(items): seen = set() for item in items: if item not in seen: yield item seen.add(item) 下面是使用上述函数的例子: 1234>>> a = [1, 5, 2, 1, 9, 1, 5, 10]>>> list(dedupe(a))[1, 5, 2, 9, 10]>>> 这个方法仅仅在序列中元素为 hashable 的时候才管用。 如果我们想消除元素不可哈希的序列(比如 dict类型)中重复元素的话,需要将上述代码稍微改变一下,就像这样: 1234567def 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) 这里的 key 参数指定了一个函数,将序列元素转换成 hashable 类型。 下面是它的用法示例: 123456>>> a = [ {'x':1, 'y':2}, {'x':1, 'y':3}, {'x':1, 'y':2}, {'x':2, 'y':4}]>>> list(dedupe(a, key=lambda d: (d['x'],d['y'])))[{'x': 1, 'y': 2}, {'x': 1, 'y': 3}, {'x': 2, 'y': 4}]>>> list(dedupe(a, key=lambda d: d['x']))[{'x': 1, 'y': 2}, {'x': 2, 'y': 4}]>>> 如果你想基于单个字段、属性或者某个更大的数据结构来消除重复元素,第二种方案同样可以胜任。 扩展讨论如果我们仅仅就是想消除重复元素,通常可以简单的构造一个集合。 比如: 12345>>> a[1, 5, 2, 1, 9, 1, 5, 10]>>> set(a){1, 2, 10, 5, 9}>>> 然而,这种方法不能维护元素的顺序,生成的结果中的元素位置被打乱,而上面的方法可以避免这种情况。 在本节中我们使用了生成器函数让我们的函数更加通用,不仅仅是局限于列表处理。 比如,如果我们想读取一个文件,消除重复行,可以这样做: 123with open(somefile,'r') as f:for line in dedupe(f): ... 上述 key 函数参数模仿了 sorted() , min() 和 max() 等内置函数的相似功能。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-9-查找两个字典的相同点]]></title>
<url>%2F2018%2F04%2F09%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-9-%E6%9F%A5%E6%89%BE%E4%B8%A4%E4%B8%AA%E5%AD%97%E5%85%B8%E7%9A%84%E7%9B%B8%E5%90%8C%E7%82%B9%2F</url>
<content type="text"><![CDATA[问题描述怎样在两个字典中寻找相同点呢?(比如相同的键或相同的值) 解决方案考虑下面两个字典: 1234567891011a = { 'x' : 1, 'y' : 2, 'z' : 3}b = { 'w' : 10, 'x' : 11, 'y' : 2} 为了寻找两个字典的相同点,可以简单的在两个字典的 keys() 或 items() 返回的结果上执行集合操作。 比如: 123456# 找出两个字典相同的 keya.keys() & b.keys() # { 'x', 'y' }# 找出a中有,但b中无的 keya.keys() - b.keys() # { 'z' }# 找出两个字典中 key 和 value 都相同的元素a.items() & b.items() # { ('y', 2) } 这些操作也可以用于修改或者过滤字典元素。 比如,假如我们想以现有字典构造一个排除了几个指定「键」的新字典。 利用字典推导来实现这样的需求: 123# 先从字典 a 中删除「键」 {'z','w'},再遍历 a.keys() 生成一个新的字典c = {key:a[key] for key in a.keys() - {'z', 'w'}}# c is {'x': 1, 'y': 2} 更多讨论一个字典就是一个键集合与值集合的映射关系。 字典的 keys() 方法返回一个展现「键」集合的键视图对象。 键视图的一个很少被了解的特性就是它们也支持集合操作,比如集合并、交、差运算。 所以,如果我们想对集合的「键」执行一些普通的集合操作,可以直接使用键视图对象而不用先将它们转换成一个 set。 字典的 items() 方法返回一个包含 (键,值) 对的元素视图对象。 这个对象同样也支持集合操作,并且可以被用来查找两个字典有哪些相同的键值对。 尽管字典的 values() 方法也是类似,但是它并不支持这里介绍的集合操作。 某种程度上是因为值视图不能保证所有的值互不相同,这样会导致某些集合操作会出现问题。 不过,如果硬要在「值」上面执行这些集合操作的话,你可以先将值集合转换成 set,然后再执行集合运算就行了。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-8-字典的运算]]></title>
<url>%2F2018%2F04%2F07%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-8-%E5%AD%97%E5%85%B8%E7%9A%84%E8%BF%90%E7%AE%97%2F</url>
<content type="text"><![CDATA[问题描述1234567prices = { 'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.20, 'FB': 10.75 } 怎样在数据字典中执行一些计算操作(比如求最小值、最大值、排序等等)? 解决方案STEP1. 把「键」和「值」反转,方便对比值的大小。 STEP2. 使用 min() 、max()、sorted() 进行操作。 示例代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051def test_data_dict_operate(): """数据字典的操作,求最大、最小值,排序等""" prices = { 'ACME': 45.23, 'AAPL': 612.78, 'IBM': 205.55, 'HPQ': 37.20, 'FB': 10.75 } # 字典上执行普通的数学运算,它们仅仅作用于键 assert min(prices) == 'AAPL' assert max(prices) == 'IBM' # 但这不是我们想要的,我们想要的是值 # 于是我们尝试使用 values 获得值 assert min(prices.values()) == 10.75 assert max(prices.values()) == 612.78 # 可是光有值也不行啊,我们想要的是键和值对应起来,这样才更清晰 # 于是 # 在 min() 和 max() 函数中提供 key 函数参数来获取最小值或最大值对应的键的信息 assert min(prices, key=lambda k: prices[k]) == 'FB' assert max(prices, key=lambda k: prices[k]) == 'AAPL' # 可键的信息是有了,值的信息还得再查找一次 # 于是 assert prices[min(prices, key=lambda k: prices[k])] == 10.75 assert prices[max(prices, key=lambda k: prices[k])] == 612.78 # 太麻烦了,有没有简单一点的? # 有! # STEP1. 使用zip()将字典转换为一个以元组为元素的列表,注意值(数据)在前面了 new_prices = zip(prices.values(), prices.keys()) # STEP2. 使用min()取出最小值 assert min(new_prices) == (10.75, 'FB') # STEP1. 由于zip() 函数创建的是一个只能访问一次的迭代器,因此我们还得再创建一次 new_prices = zip(prices.values(), prices.keys()) # STEP2. 使用max()取出最小值 assert max(new_prices) == (612.78, 'AAPL') # 上面的例子这样写会更好: assert min(zip(prices.values(), prices.keys())) == (10.75, 'FB') assert max(zip(prices.values(), prices.keys())) == (612.78, 'AAPL') # 排序可以使用 zip + sorted assert sorted(zip(prices.values(), prices.keys())) == [(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM'), (612.78, 'AAPL')] 注意: 当多个「键」拥有相同的值的时候,「键」会决定返回结果。 比如,在执行 min() 和 max() 操作的时候,如果恰巧最小或最大值是相同的,那么将按照键的大小进行对比: 1234567> >>> prices = { 'AAA' : 45.23, 'ZZZ': 45.23 } # 值是相同的> >>> min(zip(prices.values(), prices.keys())) # 值相同时按键进行对比> (45.23, 'AAA')> >>> max(zip(prices.values(), prices.keys()))> (45.23, 'ZZZ')> >>>>]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-7-字典排序]]></title>
<url>%2F2018%2F04%2F07%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-7-%E5%AD%97%E5%85%B8%E6%8E%92%E5%BA%8F%2F</url>
<content type="text"><![CDATA[问题描述假如我们想创建一个字典,并且在迭代或序列化这个字典的时候能够控制元素的顺序。如何实现呢? 解决方案为了能控制字典中元素的顺序,我们可以使用 collections 模块中的 OrderedDict 类。 在迭代操作的时候 OrderedDict 会保持元素被插入时的顺序。 代码示例: 1234567891011121314151617181920212223from collections import OrderedDictimport jsondef test_ordereddict(): """使用OrderedDict保持字典中元素的顺序""" od = OrderedDict() # 向字典中插入元素 od['one'] = 1 od['two'] = 2 od['three'] = 3 od['four'] = 4 # 获取字典的值时,顺序没变哦 assert list(od.values()) == [1, 2, 3, 4] # 遍历时,顺序也是没变的哦 for key in od: print(key, od[key]) # 序列化时顺序也是保持不变的 assert json.dumps(od) == {"one": 1, "two": 2, "three": 3, "four": 4} 从上面的例子可以看出,如果我们想精确控制使用 JSON 编码后「字段的顺序」,可以使用 OrderedDict 来构建这样的数据。 扩展讨论OrderedDict 内部维护着一个根据键插入顺序排序的双向链表。 每次当一个新的元素插入进来的时候, 它会被放到链表的尾部。对于一个已经存在的键的重复赋值不会改变键的顺序。 需要注意的是, 一个 OrderedDict 的大小是一个普通字典的两倍,因为它内部维护着另外一个链表。 所以如果你要构建一个需要大量 OrderedDict 实例的数据结构的时候(比如读取 100,000 行 CSV 数据到一个 OrderedDict 列表中去), 那么你就得仔细权衡一下是否使用 OrderedDict 带来的好处要大过额外内存消耗的影响。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-6-字典中的键映射多个值]]></title>
<url>%2F2018%2F04%2F04%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-6-%E5%AD%97%E5%85%B8%E4%B8%AD%E7%9A%84%E9%94%AE%E6%98%A0%E5%B0%84%E5%A4%9A%E4%B8%AA%E5%80%BC%2F</url>
<content type="text"><![CDATA[问题描述怎样实现一个键对应多个值的字典(也叫 multidict)? 解决方案我们都知道一个字典就是一个键对应一个单值的映射。 如:{"one":1} 可如果我们想要让一个键映射多个值,那么就需要将这多个值放到另外的容器(列表或集合)中。 如:{"one":[1,2,3,4]} 选择使用列表还是集合取决于你的实际需求。 如果你想保持元素的插入顺序就应该使用列表。 如果想去掉重复元素就使用集合(并且不关心元素的顺序)。 使用 defaultdict我们可以很方便的使用 collections 模块中的 defaultdict 来构造这样的字典。 defaultdict 的一个特征是它会自动初始化每个 key 刚开始对应的值,所以我们只需要关注添加元素操作就行了。 比如: 12345678910111213141516171819202122232425262728293031from collections import defaultdictdef test_multidict_use_defaultdict(): """使用defaultdict创建多值字典""" # 用列表来存储多个值,会保持元素的插入顺序,且不会去掉重复的元素 d = defaultdict(list) # 向「键」中加入多个值 d['name'].append("张三") d['name'].append("李四") d['name'].append("王二") d['name'].append("王二") d['age'].append(20) assert d.get('name') == ['张三', '李四', '王二', '王二'] assert d.get('age') == [20] # 用集合来存储多个值,会去掉重复的元素,且不关心元素顺序 d2 = defaultdict(set) d2['name'].add("张三") d2['name'].add("李四") d2['name'].add("王二") d2['name'].add("王二") d2['name'].add("王二") d2['name'].add("王二") d2['age'].add(20) assert d2.get('name') == {'李四', '张三', '王二'} assert d2.get('age') == {20} 需要注意的是, defaultdict 会自动为将要访问的键(就算目前字典中并不存在这样的键)创建映射实体。 如果我们不需要这样的特性,可以在一个普通的字典上使用 setdefault() 方法来代替。 使用 setdefault使用setdefault创建多值字典,比如: 12345678910def test_multidict_use_setdefault(): """使用setdefault创建多值字典""" d = {} # 一个普通的字典 d.setdefault('a', []).append(1) # 每次都要创建一个新的初始值实例 d.setdefault('a', []).append(2) d.setdefault('b', []).append(4) assert d.get('a') == [1, 2] assert d.get('b') == [4] 但是很多程序员觉得 setdefault() 用起来有点别扭。因为每次调用都得创建一个新的初始值的实例(例子程序中的空列表 [] )。 自已动手一般来讲,创建一个多值映射字典是很简单的。 但是,如果你选择自己实现的话,那么对于值的初始化可能会有点麻烦。 你可能会像下面这样来实现: 12345d = {}for key, value in pairs: if key not in d: d[key] = [] d[key].append(value) 如果使用 defaultdict 的话代码就更加简洁了: 123d = defaultdict(list)for key, value in pairs: d[key].append(value)]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[「数据结构和算法」5-实现一个优先级队列]]></title>
<url>%2F2018%2F04%2F03%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-5-%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E4%BC%98%E5%85%88%E7%BA%A7%E9%98%9F%E5%88%97%2F</url>
<content type="text"><![CDATA[问题描述怎样实现一个按优先级排序的队列? 并且在这个队列上面每次 pop 操作总是返回优先级最高的那个元素? 解决方案可使用 heapq 模块实现了一个简单的优先级队列。 代码演示: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152import heapqclass PriorityQueue: """利用 heapq 模块实现了一个简单的优先级队列""" def __init__(self): self._queue = [] self._index = 0 def push(self, item, priority): """ 添加队列 :param item: 元素 :param priority: 优先级 """ # 优先级为负数的目的是使得元素按照优先级从高到低排序 # 这个跟普通的按优先级从低到高排序的堆排序恰巧相反 heapq.heappush(self._queue, (-priority, self._index, item)) # 通过不断增加 index 保证元素按照它们插入的顺序挺排序 self._index += 1 def pop(self): """弹出队列""" return heapq.heappop(self._queue)[-1]class Item: """使用方式""" def __init__(self, name): self.name = name def __repr__(self): return 'Item({!r})'.format(self.name)def test_priority_queue(): q = PriorityQueue() # 插入元素 q.push(Item('foo'), 1) q.push(Item('bar'), 5) q.push(Item('spam'), 4) q.push(Item('grok'), 1) # 按优先级 pop 元素 print(q.pop()) # Item('bar') print(q.pop()) # Item('spam') # 如果优先级相同,则按插入顺序返回 print(q.pop()) # Item('foo') print(q.pop()) # Item('grok') 函数 heapq.heappush() 和 heapq.heappop() 分别在队列 _queue 上插入和删除第一个元素, 并且队列 _queue 保证第一个元素拥有最高优先级。 heappop() 函数总是返回「最小的」的元素,这就是保证队列 pop 操作返回正确元素的关键。 另外,由于 push 和 pop 操作时间复杂度为 O(log N),其中 N 是堆的大小,因此就算是 N 很大的时候它们运行速度也依旧很快。 在上面代码中,队列包含了一个 (-priority, index, item) 的元组。 优先级为负数(-priority)的目的是使得元素按照优先级从高到低排序。 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。 index 变量的作用是保证同等优先级元素的正确排序。 通过保存一个不断增加的 index 下标变量,可以确保元素按照它们插入的顺序排序。 而且, index 变量也在相同优先级元素比较的时候起到重要作用。 Tips: 如果我们想在多个线程中使用同一个队列,就需要增加适当的锁和信号量机制。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>note</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-4-查找最大或最小的N个元素]]></title>
<url>%2F2018%2F03%2F13%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-4-%E6%9F%A5%E6%89%BE%E6%9C%80%E5%A4%A7%E6%88%96%E6%9C%80%E5%B0%8F%E7%9A%84%20N%20%E4%B8%AA%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[问题描述怎样从一个集合中获得最大或者最小的 N 个元素列表? 解决方案heapq 模块有两个函数:nlargest() 和 nsmallest() 可以完美解决这个问题。 示例代码: 12345678def test_nlargest_nsmallest(): li = [3, 8, 4, 5, 9, 0, 2, 6] heapq.heapify(li) # 获得n个最大的元素 assert heapq.nlargest(3, li) == [9, 8, 6] # 获得n个最小的元素 assert heapq.nsmallest(3, li) == [0, 2, 3] 两个函数都能接受一个关键字参数,用于更复杂的数据结构中: 123456789101112131415161718192021222324def test_heapq_nsmallest_nlargest(): portfolio = [ {'name': 'IBM', 'shares': 100, 'price': 91.1}, {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75}, {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'ACME', 'shares': 75, 'price': 115.65} ] # 以 price 的值进行比较,取最小的3个 cheap = heapq.nsmallest(3, portfolio, key=lambda s: s['price']) assert cheap == [ {'name': 'YHOO', 'shares': 45, 'price': 16.35}, {'name': 'FB', 'shares': 200, 'price': 21.09}, {'name': 'HPQ', 'shares': 35, 'price': 31.75} ] # 以 price 的值进行比较,取最大的3个 expensive = heapq.nlargest(3, portfolio, key=lambda s: s['price']) assert expensive == [ {'name': 'AAPL', 'shares': 50, 'price': 543.22}, {'name': 'ACME', 'shares': 75, 'price': 115.65}, {'name': 'IBM', 'shares': 100, 'price': 91.1} ] 扩展讨论如果你想在一个集合中查找最小或最大的 N 个元素,并且 N 小于集合元素数量,那么这些函数提供了很好的性能。 因为在底层实现里面,首先会先将集合数据进行堆排序后放入一个列表中: 1234567>>> nums = [1, 8, 2, 23, 7, -4, 18, 23, 42, 37, 2]>>> import heapq>>> heap = list(nums)>>> heapq.heapify(heap)>>> heap[-4, 2, 1, 23, 7, 2, 18, 23, 42, 37, 8] # heap[0] 永远是最小的元素>>> 堆数据结构最重要的特征是 heap[0] 永远是最小的元素。 并且剩余的元素可以很容易的通过调用 heapq.heappop() 方法得到, 该方法会先将第一个元素弹出来,然后用下一个最小的元素来取代被弹出元素(这种操作时间复杂度仅仅是 O(log N),N 是堆大小)。 比如,如果想要查找最小的 3 个元素,你可以这样做: 123456>>> heapq.heappop(heap)-4>>> heapq.heappop(heap)1>>> heapq.heappop(heap)2 当要查找的元素个数相对比较小的时候,函数 nlargest() 和 nsmallest() 是很合适的。 如果你仅仅想查找唯一的最小或最大(N=1)的元素的话,那么使用 min() 和 max() 函数会更快些。 类似的, 如果 N 的大小和集合大小接近的时候,通常先排序这个集合然后再使用切片操作会更快点 ( sorted(items)[:N] 或者是 sorted(items)[-N:] )。 需要在正确场合使用函数 nlargest() 和 nsmallest() 才能发挥它们的优势 (如果 N 快接近集合大小了,那么使用排序操作会更好些)。 尽管你没有必要一定使用这里的方法,但是堆数据结构的实现是一个很有趣并且值得你深入学习的东西。 基本上只要是数据结构和算法书籍里面都会有提及到。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>notes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-3.保留最后加入的固定个数的元素]]></title>
<url>%2F2018%2F03%2F08%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-3-%E4%BF%9D%E7%95%99%E6%9C%80%E5%90%8E%E5%8A%A0%E5%85%A5%E7%9A%84%E5%9B%BA%E5%AE%9A%E4%B8%AA%E6%95%B0%E7%9A%84%E5%85%83%E7%B4%A0%2F</url>
<content type="text"><![CDATA[问题描述在迭代操作或者其他操作的时候,怎样只保留最后加入的固定个数的元素? 解决方案使用 collections.deque(maxlen=N) 建立固定大小的队列,当新的元素加入并且这个队列已满的时候, 最老的元素会自动被移除掉。 如: 12345678910111213141516def test_deque_maxlen(): """测试如何使用固定大小的队列""" q = deque(maxlen=3) # 新建一个固定大小的队列 q.append(1) # 添加元素 q.append(2) q.append(3) assert list(q) == [1, 2, 3] # 正好添加3个 # 再添加2个 q.append(4) q.append(5) # 会把老的元素移除 assert list(q) == [3, 4, 5] 举个例子: 假设现在有一个文件,我们遍历文件的每一行,但是保留最后读取的 5 行内容,应该如何做呢? 示例代码: 12345678910111213141516171819202122232425262728293031323334353637import osfrom collections import dequerelative_path = "somefile.txt"filepath = os.path.join(os.path.dirname(os.path.realpath(__file__)), os.sep.join(relative_path.split('/')))def search(lines, pattern, history=5): """ 搜索关键字内容,返回最后读取的5行内容 :param lines: 一行内容 :param pattern: 匹配的关键字 :param history: 用于生成固定队列的大小 :return: """ previous_lines = deque(maxlen=history) # 使用 deque 新建一个固定大小的队列 for line in lines: if pattern in line: yield line, previous_lines # 返回一个迭代器 previous_lines.append(line)def test_last_n(): with open(filepath) as f: # 遍历迭代器 search for line, prevlines in search(f, 'Python', 5): # prevlines 首次返回的是空,因此跳过去 if prevlines: assert len(prevlines) == 5 # 其余情况一定是固定的5个元素 # 遍历 previous_lines for pline in prevlines: print(pline, end='') # 打印出来 print('-' * 20) 我们在写查询元素的代码时,通常会使用包含 yield 表达式的生成器函数,这样可以将搜索过程代码和使用搜索结果代码解耦。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>notes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-2.解压可迭代对象赋值给多个变量]]></title>
<url>%2F2018%2F03%2F07%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-2-%E8%A7%A3%E5%8E%8B%E5%8F%AF%E8%BF%AD%E4%BB%A3%E5%AF%B9%E8%B1%A1%E8%B5%8B%E5%80%BC%E7%BB%99%E5%A4%9A%E4%B8%AA%E5%8F%98%E9%87%8F%2F</url>
<content type="text"><![CDATA[问题描述如果我们遇到一个不确定个数的可迭代对象,如何解压它们呢? 如果直接用变量解压,变量个数超过元素个数时,会抛出一个 ValueError 。 如: 12345# 变量个数超过元素>>> a,b,c,d = [1,2]Traceback (most recent call last): File "<stdin>", line 1, in <module>ValueError: not enough values to unpack (expected 4, got 2) 那么怎样才能从这个可迭代对象中解压出想要的元素来呢? 解决方案Python 的星号表达式可以用来解决这个问题。 示例1:星号表达式用在中间部分 你在学习一门课程,在学期末的时候, 你想统计下家庭作业的平均成绩,但是需要排除掉第一个和最后一个分数,如何实现呢? 如果只有四个分数,你可能就直接去简单的手动赋值, 但如果有 24 个呢? 这时候星号表达式就派上用场了。 代码演示: 12345678def test_use_asterisk_expr(): grades = (30, 40, 60, 80, 90, 98, 100) # 使用*号表达式 first, *middle, last = grades assert middle == [40, 60, 80, 90, 98] assert 73.6 == sum(middle) / len(middle) 示例2:星号表达式用在后面的部分 假设你现在有一些用户的记录列表,每条记录包含一个名字、邮件,接着就是不确定数量的电话号码。 如: 12> record = ('Dave', '[email protected]', '773-555-1212', '847-555-1212')> 如何分解这些记录呢? 代码演示: 12345678910def test_use_asterisk_expr_behind(): record = ('张三', '[email protected]', '773-555-1212', '847-555-1212') # 使用星号表达式 name, email, *phone_numbers = record assert name == "张三" assert email == '[email protected]' # 解压出的 phone_numbers 变量永远都是列表类型,不管解压的电话号码数量是多少(包括 0 个) assert phone_numbers == ['773-555-1212', '847-555-1212'] 注意: 上面解压出的 phone_numbers 变量永远都是列表类型,不管解压的电话号码数量是多少(包括 0 个)。 所以,任何使用到 phone_numbers 变量的代码就不需要做多余的类型检查去确认它是否是列表类型了。 示例3:星号表达式用在前面的部分 假设你有一个公司前 8 个月销售数据的序列, 但是你想看下最近一个月数据和前面 7 个月的平均值的对比。 如: 12> datas = [10, 8, 7, 1, 9, 5, 10, 3]> 如何获得最近1个和前面7个数据呢? 代码演示: 1234567def test_use_asterisk_expr_front(): datas = [10, 8, 7, 1, 9, 5, 10, 3] *trailing, last = datas assert trailing == [10, 8, 7, 1, 9, 5, 10] assert last == 3 更多讨论 扩展的迭代解压语法是专门为解压不确定个数或任意个数元素的可迭代对象而设计的。 在什么时候使用星号表达式呢? 当可迭代对象的元素结构有确定的规则的(比如第 1 个元素后面都是电话号码),星号表达式让我们可以很容易的利用这些规则来解压出元素来, 而不是通过一些比较复杂的手段去获取这些关联的元素值。 遍历可变的长元组序列值得注意的是,星号表达式在迭代元素为可变的长元组序列时是很有用的。 示例代码: 1234567891011121314151617181920def test_var_long_tuple(): # 一个可变长元组的序列 records = [ ('foo', 1, 2), ('bar', 'hello'), ('foo', 3, 4), ] def do_foo(x, y): print('foo:', x, y) def do_bar(s): print('bar:', s) # 使用星号表达式遍历一个可变长元组的序列 for tag, *args in records: if tag == 'foo': do_foo(*args) elif tag == 'bar': do_bar(*args) 字符串的分割星号解压语法在字符串操作的时候也会很有用,比如字符串的分割。 示例代码: 123456def test_separate_str(): line = 'nobody:*:-2:-2:Unprivileged User:/var/empty:/usr/bin/false' uname, *fields, homedir, sh = line.split(':') assert uname == "nobody" assert homedir == "/var/empty" assert sh == "/usr/bin/false" 解压一些元素后丢弃有时候,我们想解压一些元素后丢弃它们,虽然不能简单就使用 * , 但是可以使用一个普通的废弃名称,比如 _ 或者 ign (ignore)。 示例代码: 12345def test_ignore(): record = ('ACME', 50, 123.45, (12, 18, 2012)) name, *_, (*_, year) = record assert name == 'ACME' assert year == 2012 分隔列表在很多函数式语言中,星号解压语法跟列表处理有许多相似之处。 比如,如果你有一个列表, 你可以很容易的将它分割成几部分。 示例代码: 123456789101112131415161718def test_use_asterisk_expr(): grades = (30, 40, 60, 80, 90, 98, 100) # 在中间使用星号表达式 first, *middle, last = grades assert first == 30 assert middle == [40, 60, 80, 90, 98] assert last == 100 # 在后边使用星号表达式 first, *behind= grades assert first == 30 assert behind == [40, 60, 80, 90, 98, 100] # 在前边使用星号表达式 *front, last = grades assert front == [30, 40, 60, 80, 90, 98] assert last == 100]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>notes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[如何修改macOS上的命令提示符]]></title>
<url>%2F2018%2F01%2F28%2F%E5%A6%82%E4%BD%95%E4%BF%AE%E6%94%B9macOS%E4%B8%8A%E7%9A%84%E5%91%BD%E4%BB%A4%E6%8F%90%E7%A4%BA%E7%AC%A6%2F</url>
<content type="text"><![CDATA[命令提示符是什么?当我们打开一个终端时首先看到的就是 Bash 命令提示符,格式为: 1用户名@主机名 ~$ 例如: 1bixiaopeng@bixiaopengtekiMacBook-Pro ~$ 查看主机名: 12$ hostnamebixiaopengtekiMacBook-Pro.local 这个提示符太长了,我想修改一下怎么改呢? 修改命令提示符默认情况下,Bash 命令提示符中最长的就是主机名,我们一起来看一下怎么修改。 修改主机名把主机名改为 macOS: 12$ sudo scutil --set HostName macOSPassword:(输入密码) 新打开一个终端窗口修改才会生效,效果为: 1bixiaopeng@macOS ~$ 配置提示符一些必要的说明Bash 命令提示符是通过环境变量PS1(Prompt String 1) 来设置的,查看现有的设置: 12bixiaopeng@macOS ~$ echo $PS1\[\033[01;33m\]\u@\h\[\033[01;31m\] \W\$\[\033[00m\] 效果是这样的: \[\033[01;33m\] 是颜色,去掉配置的颜色,看得会更清晰一些: 1\u@\h \W\$ 翻译为: 1用户名@主机名 当前工作目录名称$ 配置参数的解释: 12345678\u:用户名\h:主机名\w:当前所在工作目录路径\W:当前工作目录名称,~ 表示主目录\t:当前时间\n:换行\D{%c}:获取本地化的日期和时间\$:使用 $ 作为提示符,如果 root 过的话,则显示 # 尝试配置自己的提示符12345$ PS1="\u@\h \W\$"bixiaopeng@macOS ~$# 给点颜色bixiaopeng@macOS ~$ PS1="\[\033[01;33m\]\u@\h\[\033[01;31m\] \W\$\[\033[00m\] " 效果如下图所示: 上面的命令都可以看作是效果的预览,如果想让配置真正的生效,还需要 export 一下: 12# 接上述命令bixiaopeng@macOS ~$ export PS1 或在接在 ~/.bash_profile 中修改配置: 1export PS1="\[\033[01;33m\]\u@\h\[\033[01;31m\] \W\$\[\033[00m\] "]]></content>
<categories>
<category>了不起的macOS</category>
</categories>
<tags>
<tag>macOS</tag>
</tags>
</entry>
<entry>
<title><![CDATA[搞清楚Python中的模块-包-库-应用程序]]></title>
<url>%2F2018%2F01%2F24%2F%E6%90%9E%E6%B8%85%E6%A5%9APython%E4%B8%AD%E7%9A%84%E6%A8%A1%E5%9D%97-%E5%8C%85-%E5%BA%93-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F%2F</url>
<content type="text"><![CDATA[Python 中经常会有一些名词让我们感觉似懂非懂,比如:模块(Module)、包(Package)、库(Library)、应用(Application)、框架(Framework),它们是什么?有什么关系?有什么不同? 今天我们来梳理清楚。 先来看一个项目结构的例子: 1234567├── myapp # 包含 __init__.py 文件的文件夹都叫做包(package)│ ├── __init__.py│ └── utils.py # .py 文件都称为模块(module)├── setup.py├── tests│ ├── __init__.py│ ├── test_help.py 说明: 包含 __init__.py 文件的文件夹叫做「包」,如本例中的 myapp 和 tests; 所有 .py 文件都叫「模块」,如:__init__.py 和 utils.py; 所有这些代码组成了一个「应用程序」,用于完成某项或多项特定的工作; 本质上「库」、「框架」都是「应用程序」,只不过因为使用场景、提供的功能和解决问题的复杂度有所区分。 来张图可能会更直观一些: 可以再简单一点的理解为: 「函数」和「类」组成了「模块」,多个「模块」组成了「包」,多个「包」或者多个「模块」组成了「应用程序」,「应用程序」具有某些特定功能且作为依赖提供给其他项目使用叫作「库」,提供某一领域解决方案的「库」叫框架。 总结一下: 名词 英文 解释 模块 Module .py 文件都称叫模块。 包 Package 包含 __init__.py 文件的文件夹叫做包。 应用程序 Application 应用程序是完成某项或多项特定功能的代码集合,由包和模块组成。 库 Library 发布在 PYPI 上供他人使用的应用程序叫第三方库,Python 内置的叫标准库,库一般用于解决特定问题,如:requests、sh、arrow、pipenv。 框架 Framework 为解决某一特殊领域的问题而设计的具有一定约束性和支撑性的库叫框架,如 Flask、Django、Selenium、Tensorflow。]]></content>
<categories>
<category>品味Python</category>
</categories>
<tags>
<tag>python</tag>
</tags>
</entry>
<entry>
<title><![CDATA[数据结构和算法-1.解压序列然后赋值给多个变量]]></title>
<url>%2F2018%2F01%2F21%2F%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E5%92%8C%E7%AE%97%E6%B3%95-1-%E8%A7%A3%E5%8E%8B%E5%BA%8F%E5%88%97%E7%84%B6%E5%90%8E%E8%B5%8B%E5%80%BC%E7%BB%99%E5%A4%9A%E4%B8%AA%E5%8F%98%E9%87%8F%2F</url>
<content type="text"><![CDATA[问题描述有一个元组或序列包含了 N 个元素,如:a_list = [4, 5, 6] 如何将它里面的值解压后同时赋值给 N 个变量呢? 解决方案任何一个序列(或者可迭代的对象)都可以通过一个简单的赋值语句解压并赋值给多个变量。 不过,需要注意的一个前提就是:变量的个数必须跟序列元素的个数相同。 实例演示: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970# 校验把一个「元组」解压并赋值给多个变量def test_unzip_assign_a_tuple(): a_tuple = (1, 2, 3) # 元组 # 把元组解压并赋值给3个变量 t1, t2, t3 = a_tuple assert t1 == 1 assert t2 == 2 assert t3 == 3# 校验把一个「列表」解压并赋值给多个变量def test_unzip_assign_a_list(): # 列表 a_list = [4, 5, 6] # 把列表解压并赋值给3个变量 l1, l2, l3 = a_list assert l1 == 4 assert l2 == 5 assert l3 == 6# 校验把一个「字符串」解压并赋值给多个变量def test_unzip_assign_a_str(): # 字符串 a_str = "fan" # 把字符串解压并赋值给3个变量 s1, s2, s3 = a_str assert s1 == "f" assert s2 == "a" assert s3 == "n"# 校验把一个「混合列表」解压并赋值给多个变量def test_unzip_assign_a_mix_list(): # 混合的列表 mix_list = [4, 5, 'bi', (2018, 1, 21)] # 把混合的列表解压并赋值给4个变量 m1, m2, m3, m4 = mix_list assert m1 == 4 assert m2 == 5 assert m3 == 'bi' assert m4 == (2018, 1, 21) # 把混合的列表解压并赋值给6个变量 m1, m2, m3, (m4, m5, m6) = mix_list assert m1 == 4 assert m2 == 5 assert m3 == 'bi' assert m4 == 2018 assert m5 == 1 assert m6 == 21# 校验把生成器解压并赋值给多个变量def test_unzip_assign_generator(): a_generator = (x * 2 for x in range(3)) g1, g2, g3 = a_generator assert g1 == 0 assert g2 == 2 assert g3 == 4 如果我们只想获取一个列表中的一部分值,其他的值并不关心,怎么办呢? 123456# 校验只解压一部分值并赋值给变量def test_unzip_part(): # 把不关心的变量使用一些变量占位,这样就可以只获取关心的变量了 _, name, _, gender = [1, "bixiaofan", 2, "帅哥"] assert name == "bixiaofan" assert gender == "帅哥" 注意:必须要保证用于占位的变量在其他地方没有被使用。 小贴示: 这种解压赋值可以用在任何可迭代的对象上,不仅限于列表、元组和字符串,文件对象、迭代器和生成器也是可以的。]]></content>
<categories>
<category>PythonCookbook</category>
</categories>
<tags>
<tag>python</tag>
<tag>notes</tag>
</tags>
</entry>
<entry>
<title><![CDATA[显示命令的帮助手册-man]]></title>
<url>%2F2018%2F01%2F21%2F%E6%98%BE%E7%A4%BA%E5%91%BD%E4%BB%A4%E7%9A%84%E5%B8%AE%E5%8A%A9%E6%89%8B%E5%86%8C-man%2F</url>
<content type="text"><![CDATA[man 是 manual(手册)的缩写,用来快速查询某个命令的 man-pages(帮助手册页面),并格式化显示。 常用方法:1234567891011# 显示某个命令的帮助手册$ man 命令名# 显示某个命令帮助手册的位置而不是内容:$ man -w 命令名# 显示帮助手册的搜索路径$ man --path# 查询所有包含某个关键字的帮助手册$ man -k 关键字 实例演示:实例1:显示 sort 命令的帮助手册1~$ man sort 命令执行后会进入帮助手册页面,如图所示。帮助手册页面包括了命令的介绍和用法,以及命令选项的说明。 帮助手册页面其实是进入了less查看模式,只要使用less的快捷键操作来查看页面就可以了,非常方便。 实例2:显示man命令查找帮助手册的路径123456~$ man --path/opt/subversion/man:/Library/Frameworks/Python.framework/Versions/2.7/share/man:/usr/local/share/man:/usr/share/man:/opt/X11/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man# 或 使用man -w,后面不带参数哦。~$ man -w/opt/subversion/man:/Library/Frameworks/Python.framework/Versions/2.7/share/man:/usr/local/share/man:/usr/share/man:/opt/X11/share/man:/Applications/Xcode.app/Contents/Developer/usr/share/man:/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/share/man 实例3:显示 ssh 命令的帮助手册地址12~$ man -w ssh/usr/share/man/man1/ssh.1 实例4:查询所有包含 awk 的帮助手册1234~$ man -k awkEnglish(3pm) - use nice English (or awk) names for ugly punctuation variablesa2p(1) - Awk to Perl translatorawk(1) - pattern-directed scanning and processing language]]></content>
<categories>
<category>每天一个Linux命令</category>
</categories>
<tags>
<tag>linux</tag>
</tags>
</entry>
</search>