chapter 1
杂
records = [('foo1', 1, 2),
('foo2', 'sad'),
('foo1', 4, 5)]
def fun1(x, y):
print('foo1', x, y)
def fun2(n):
print('fool2', n)
for fun_name, *args in records:
if fun_name == 'foo1':
fun1(*args)
if fun_name == 'foo2':
fun2(*args)
对于嵌套不必要类容丢失的分解:
record=('ACME',50,123.45,(12,18,2012))
name,*_,(*_,year)=record
print(name,year)
%%%%%
拆分方法的精巧函数:
def sum_Z(items):
head,*tail=items
return head+sum_Z(tail) if tail else head
%%%
建议python不要写递归,python递归很艹,是因为其内在的递归限制所致的.但是还是可以完成一些有趣的操作.
1.3 保存最后N个元素
目的: 希望在迭代或是其他形式的处理过程中对最后几项记录做一个有限的历史纪录统计.
解决方法: 利用collection.deque
下面的代码对一系列文本做简单的文本匹配操作,当发现输出前匹配行以及最后检查过的N行文本.
from collections import deque
def search(lines,pattern,history=5):
previous_lines=deque(maxlen=history)
for line in lines:
if pattern in line:
yield line,previous_lines
previous_lines.append(line)
\#Example use on a file
if __name__ == '__name__':
with open('somefime.txt') as f:
for line,prevlines in search(f,'python',5):
for pline in prevlines:
print(pline,end='')
print(line,end="")
print('-'*20)
1.4 找到最大或最小的N个元素
解决方法: heapq模块的两个元素 nlargest()和 nsmallest()
import heapq
nums=[1,2,3,4,6,7,8,9,10]
print(heapq.nlargest(3,nums))
print(heapq.nsmallest(3,nums))
# [10, 9, 8]
# [1, 2, 3]
import heapq
portfoli = [{'name': 'IBM', 'shares': 100, 'price': 91.1},
{'name': 'A', 'shares': 10, 'price': 9.11},
{'name': 'B', 'shares': 50, 'price': 90},
{'name': 'C', 'shares': 1, 'price': 91}]
cheap = heapq.nsmallest(3, portfoli, key=lambda s: s['price'])
expensive = heapq.nlargest(3, portfoli, key=lambda s: s['price'])
print(cheap)
print(expensive)
如果在寻找最大或最小的N个元素,且同集合中元素的总数比总数小的多,堆可以提供很好的性能,这些函数首先会在底层转化为列表,且元素会以堆的方式排列.
例:
nums=[-11,8,1,-1123,15,512,1020,123,921]
import heapq
heap=list(nums)
heapq.heapify(heap)
print(heap)
#[-1123, -11, 1, 8, 15, 512, 1020, 123, 921]
堆最重要的特性就是heap[0]总是最小的拉一个元素.此外,接下来的元素可以依次通过heapq.heappop()的方法轻松得到.
该方法会将第一个元素(最小的)弹出,然后第二个最小的元素取而代之(复杂度是o(log(N)),N表示堆的大小).例如找到第三个最小的元素可以连续heappop()3次.
但是如果要找第N个最大或者最小的元素heapq里的函数nlargest()和nsmallest()才是最适用的,如果只是简单的找到最大或最小的一个元素max()和min()会更快.
同样,如果N和集合本身的大小差不多,更快的方法是对集合排序然后切片(例:sorted(list)[:N]或sorted(list)[-N:]).但是要注意的是nlargest()和nsmallest()的实际实现会根据使用它们的方式有所不同,会有一些优化(如N的大小和输出差不多时,会采用排序的方法).
1.5 实现优先队列
问题 要想实现一个队列,它们能够以给定的优先级来对元素排序,且每次pop操作时都会返回优先级最高的元素.
解决方法
利用heapq模块实现一个简单的优先级队列.
import heapq
class PriorityQueue:
def __init__(self):
self._queue=[]
self._index=0
def push(self,item,prioity):
heapq.heappush(self._queue,(-prioity,self._index,item))
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)
q=PriorityQueue()
q.push(Item('bar'),5)
q.push(Item('foo'),1)
q.push(Item('spam'),4)
q.push(Item('grok'),2)
for _ in range(4):
print(q.pop())
#Item('bar')
#Item('spam')
#Item('grok')
#Item('foo')
核心是对heapq模块的使用.函数heaqp.heappush()以及heapq.heappop()分别实现将元素从列表_queue中插入和移除,且保证了列表中第一个元素的优先级最低.
heappop()方法总是返回'最小'的元素,因此这就是把元素正确弹出的关键.此外,由于push()和pop()操作的复杂度都是log(n),效率很高.
在这段代码中队列以元组的形式(-priority,index,item) 把priority取负是为了按照优先级由高到第的方式排顺序,变量index的作用是为了将具有相同优先级的元素以适当的顺序排序,通过增加一个不断递增的引索,元素将以它们入队时的顺序排列.但是index在对具有相同优先级的元素之间作比较也扮演了重要的角色.
为了说明Item实例是无法比较的:
a=Item('dokuo')
b=Item('boki')
#print(a
# print(a
如果以元组(priority,item)的形式来表示元素,那么只要优先级不同,它们就可以进行比较.但是,如果两个元组的优先值相同,做比较操作还是会失败.
可以通过引入额外的引索值,以(priority,index,item)的方式建立元组,可以完全的避免这个问题.因为没有拉两组会有相同的index值.如果要用于线程间通信,还需要增加适当的锁和信号机制.
1.6 在字典中将键映射到多个值上
问题 建立多值字典(multidict)
解决方法
字典是一种关联容器,每一个键都将映射到一个单独的值上.如果想让键映射到多个值,需要将这些值保存在一个容器中(如:列表或集合).
例:
d = {
'a': [1, 2, 3],
'b': [1, 41]
}
e = {
'a': {1, 2, 34},
'b': {61, 12}
}
要使用集合还是列表取决于用途.如果希望保留插入顺序使用list,否则set.
为了方便的创建字典可以使用collections模块的defaultdict类.defaultdict的一个特点就是会自动初始化第一个值,这样只需要关注添加元素即可.
例如:
from collections import defaultdict
d=defaultdict(list)
d['a'].append(1)
d['a'].append(2)
d['b'].append(4)
print(d)
#defaultdict(, {'a': [1, 2], 'b': [4]})
也可以用dict本身的方法setdefault()来取代,但是setdefault()有点不自然每次调用时都会创建一个初始值的新实例.
原则上构建一个一键多值字典是很容易的.但是如果试着自己对一个值做初始化操作,这就会变得很杂乱.
例:
from collections import defaultdict
d={}
for key,value in pairs:
if key not in d:
d[key]=[]
d[key].append(value)
#在使用defaultdict后会清晰一些
d=defaultdict(list)
for key,value in pairs:
d[key].append(value)
1.7 让字典保持有序
问题 创建一个字典,同时当对字典做迭代或序列化操作时,也能控制其中元素的顺序.
解决方案:
要控制字典中元素的顺序,可以使用collections 中的OrderedDict类.当对字典做迭代时,它会严格按照元素添加的顺序进行.
from collections import OrderedDict
d=OrderedDict()
d['foo']=1
d['bar']=2
d['spam']=3
d['grok']=4
for key in d:
print(key,d[key])
#foo 1
#bar 2
#spam 3
#grok 4
当想构建一个映射结构以便稍后对其做序列化操作或编码成为另一种格式时,OrderedDict 就显得有用.例如在进行JSON编码时精确控制各字段的顺序,那么只要首先在OrderedDict中构建数据就行了.
from collections import OrderedDict
d=OrderedDict()
d['foo']=1
d['bar']=2
d['spam']=3
d['grok']=4
# for key in d:
# print(key,d[key])
import json
x=json.dumps(d)
print(x)
#{"foo": 1, "bar": 2, "spam": 3, "grok": 4}
OrderedDict内部维护了一个双向链表,会根据元素加入的顺序来排列键的位置.第一个新加入的元素排列在链表的尾部.接下来对已经存在的键做重新赋值不会改变键的顺序,但是OrderedDict的大小是普通字典的2倍多.是因为链表结构导致的.
1.8 与字典有关的计算问题
问题 对字典上的数据执行各种计算(求最大最小,排序)
解决方案
prices={
'ACME':45.23,
'AAPL':612.78,
'IBM':205.55,
'HPQ':37.20,
'FB':10.75
}
min_price=min(zip(prices.values(),prices.keys()))
print(min_price)
max_price=max(zip(prices.values(),prices.keys()))
print(max_price)
#同样排序只要使用zip()然后配合sorted()就可以了
prices_sorted=sorted(zip(prices.values(),prices.keys()))
print(prices_sorted)
#当计算时,zip()建立了一个迭代器,内容只能被消费一次.
#以下就会报错
prices_and_name=zip(prices.values(),prices.keys())
print(max(prices_and_name))
print(min(prices_and_name))
'''Output'''
# (10.75, 'FB')
# (612.78, 'AAPL')
# [(10.75, 'FB'), (37.2, 'HPQ'), (45.23, 'ACME'), (205.55, 'IBM'), (612.78, 'AAPL')]
# (612.78, 'AAPL')
# Traceback (most recent call last):
# File "G:/Desktop/test/learn_py/struct_angl.py", line 144, in
# print(min(prices_and_name))
# ValueError: min() arg is an empty sequence
如果尝试在字典上执行常见的数据操作,将会发现它们只会处理键,而不是值.
例:
min(prices)#return 'AAPL'
max(prices)#return 'IBM'
并不是我们所期望的,因为实际上我们是尝试对字典的值做计算.
min(prices.values())#return '10.75'
max(prices.valuse())#return '612.78'
但是这也不是我们所期望的,我们需要一个对应关系什么的计算的对应的值和键.
min(prices,key=lambda k:prices[k]) #return 'FB'
max(prices,key=lambda k:prices[k]) #return 'AAPL'
#如果要得到最小值的话,还要额外执行一次查找.
min_value=prices[min(prices,key=lambda k:prices[k])]
#利用zip()的解决方法是通过键值对反转实现的这样的元组比较时是先比较值然后比较键符合我们的预期
应当注意的是,当涉及(value,key) 的对比结果时比较key的大小.
prices={'AAA':45.33,'ZZZ':45.33}
t1=min(zip(prices.values(),prices.keys()))
t2=max(zip(prices.values(),prices.keys()))
print(t1)
print(t2)
#(45.33, 'AAA')
#(45.33, 'ZZZ')
1.9 在两个字典中寻找相同点
问题 有两个字典,找出它们中间可能相同的地方(key,value)
解决方法
a={
'x':1,
'y':2,
'z':3
}
b={
'w':10,
'x':11,
'y':2
}
#要找出两个字典的相同之处,只要通过keys()或者items()的方法执行集合的操作
#Find keys in common
print(a.keys() & b.keys())#('x','y')
#Find keys in a that not in b
print(a.keys()-b.keys())
#{'z'}
#Find (key ,value) pairs in common
print(a.items()&b.items())#{('y', 2)}
#这些内型的操作也可以过滤掉字典中的一些内容.
#example del some keys
#Make a new dictionary with certain keys removed
c={key:a[key] for key in a.keys()-{'z','w'}}
#c is {'x':1,'y':2}
字典就是一系例键和值的映射集合.字典的keys()方法会返回keys-view对象,其中,暴露了所有键.关于字典也支持集合的操作 如 求并集,交集,差集.因此,如果要对字典的键做集合的操作,不必转化为set() 直接使用keys-view对象. items()方法返回items-view 对象支持集合操作. 但是字典的value()方法不支持集合操作,是因为字典中的键和值是不同的不能保证所有值都是唯一的.所以可以先把values 转化为 set 来实现集合操作.
1.10 从序列中移除重复项但是保持顺序不变
问题 去除序列中重复的元素,但是保持剩下的元素顺序不变.
解决方法
def dedupe(items):
seen =set()
for item in items:
if item not in seen:
yield item
seen.add(item)
a=[1,5,2,1,9,1,5,10]
print(list(dedupe(a)))
#只有当序列中的元素是可哈希的时候才能这样做,在不可哈希对象如列表中如以下
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)
#这里参数key的目的是指定一个函数用来将序列中的元素转化为可哈希的类型是为了检测重复项
a=[{'x':1,'y':2},{'x':1,'y':3},{'x':1,'y':2},{'x':2,'y':4}]
print(list(dedupe_(a,key=lambda d:(d['x'],d['y']))))
print(list(dedupe_(a,key=lambda d:(d['x']))))
#如果希望在一个复杂的数据结构中,只根据对象的某个字段或属性来去除重复项,拉莫最后一种解决方法同样
#能完美的完成工作
如果只要求去除重复项,简单的办法就是构建一个集合.
如:
x=[1,2,3456,123,12,1,1,1,1,1,1,1]
print(set(x))
#{3456, 1, 2, 12, 123} 可见顺序被改变了.
对生成器的使用反应了一个事实,我们可能会希望这个函数尽可能通用-----不必绑定在只能对列表进行处理.例如
#想读取一个文件,去除其中重复的文本行可以这样处理
with open(somefile,'r') as f:
for line in dedupe_(f):
...
1.11 对切片命名
问题 当代码繁杂无法阅读时想要把切片定义成宏之类的东西时.
解决方案
record='...........100............513.25'
x=record[11:14]
y=record[26:32]
print(x,y)
#与其这样做不如命名
SHARES=slice(11,14)
PRICE=slice(26,32)
print(x,y)
#100 513.25
#100 513.25
#可以避免一些神秘难懂的编码引索,使代码变得清晰.
作为一条基本准则,代码如果有过多的编码引索,会使可读性和维护性降低.
一般的来说内置的slice()函数会创建一个切片对象,可以用在任何允许使用切片的地方.
>>>items=[1,2,3,4,5,6]
>>>a=slice(2,4)
>>>items(a)
Traceback (most recent call last):
File "", line 1, in
TypeError: 'list' object is not callable
>>>items[a]
[3, 4]
>>>items[a]=[1,2]
>>>items
[1, 2, 1, 2, 5, 6]
>>>del items[a]
>>>items
[1, 2, 5, 6]
如果有一个slice对象的实例,可以通过s.start,s.stop以及s.step属性来得到关于改对象的信息.
#承接上一个控制台
>>>items
[1, 2, 5, 6]
>>>a.start
2
>>>a.step
>>>a.stop
4
此外,可以通过使用indices(size)的方法将切片映射到特定大小的序列上.会返回一个(start,stop,step)元组,所有的值都已经恰当的限制在边界以内(当做引索操作时可以避免异常)
例如:
>>>s='HelloWorld'
>>>a.indices(len(s))
(2, 4, 1)
>>>for i in range(*a.indices(len(s))):
... print(s[i])
...
l
l
1.12 找出序列中出现次数最多的元素
问题 统计
解决方案
collections 模块中有一个Counter类正式为此类问题设计的.有一个非常方便的most_common()方法可以直接告诉我们答案.
words=[1 for _ in range(3)]+[2 for _ in range(4) ]+[0 for _ in range(6) ]+[3 for _ in range(10) ]
from collections import Counter
word_counts=Counter(words)
top_three=word_counts.most_common(3)
print(top_three)
#[(3, 10), (0, 6), (2, 4)]
可以给Counter对象提供任何可哈希的对象序列作为输入,在底层实现中,Counter 是一个字典,元素和它们之前出现的次数做了映射.
print(word_counts[1])
print(word_counts[2])
# 3
# 4
如果想增加计数可以改变映射值
如
word_counts[1]=1
#或则另一种方法
word_counts.update(morewords)
Counter类可以轻松的同各类数学运算操作结合起来.
a=Counter(words)
b=Counter(word_counts)
print(a,b)
print(a+b)
print(a-b)
#Counter({3: 10, 0: 6, 2: 4, 1: 3}) Counter({3: 10, 0: 6, 2: 4, 1: 3})
#Counter({3: 20, 0: 12, 2: 8, 1: 6})
#Counter()
当面对任何需要数据制表或计数的问题时,Counter对象都是一种很好的选择.
1.13 通过公共键对字典列表排序
问题 有一个字典列表,想根据一个或多个字典中的值来对列表排序
解决方法
利用operator模块中的itemgetter函数对这类结构进行简单的排序.
假设通过查询数据库表获取网上的成员列表得到以下的数据结构.
rows=[
{'fname':'1','lname':'1','uid':'1'},
{'fname':'2','lname':'2','uid':'2'},
{'fname':'1','lname':'2','uid':'3'},
{'fname':'3','lname':'11','uid':'4'},
{'fname':'4','lname':'11','uid':'4'}
]
from operator import itemgetter
rows_by_fname=sorted(rows,key=itemgetter('fname'))
rows_by_uid=sorted(rows,key=itemgetter('uid'))
print(rows_by_fname)
print(rows_by_uid)
# [{'fname': '1', 'lname': '1', 'uid': '1'},
# {'fname': '1', 'lname': '2', 'uid': '3'},
# {'fname': '2', 'lname': '2', 'uid': '2'},
# {'fname': '3', 'lname': '11', 'uid': '4'},
# {'fname': '4', 'lname': '11', 'uid': '4'}]
# [{'fname': '1', 'lname': '1', 'uid': '1'},
# {'fname': '2', 'lname': '2', 'uid': '2'},
# {'fname': '1', 'lname': '2', 'uid': '3'},
# {'fname': '3', 'lname': '11', 'uid': '4'},
# {'fname': '4', 'lname': '11', 'uid': '4'}]
itemgetter()函数可以接受多个键.例如:
rows_by_lfname=sorted(rows,key=itemgetter('lname','fname'))
print(rows_by_lfname)
#[{'fname': '1', 'lname': '1', 'uid': '1'},
#{'fname': '3', 'lname': '11', 'uid': '4'},
#{'fname': '4', 'lname': '11', 'uid': '4'},
#{'fname': '1', 'lname': '2', 'uid': '3'},
#{'fname': '2', 'lname': '2', 'uid': '2'}]
这个例子中rows被传递给内建的sorted()函数,该函数接受一个关键字参数key.这个参数应该代表一个可调用对象(callable),该对象从rows中接受一个单独的元素作为输入并返回一个用来排序依据的值.item getter()函数建立的就是这样一个可调用对象.
函数operator.itemgetter()接受的参数是可以作为查询的标记,用来从rows的记录中提取出所需要的值,可以是字典键的名称,用数字表示的列表元素或是任何可以传给对象的____getitem____() 方法的值如果传多个标记给itemgetter(),那么它产生的可调用对象将返回一个包含所有元素在内的元组,然后sorted()将根据元组排序的结果来排序输出的结果。如果同时针对多个字段排序这是非常有用的。
有时会用lambda函数来代替itemgetter()函数。
rows_by_fname=sorted(rows,key=lambda r:r['fname'])
rows_by_lfname=sorted(rows,key=lambda r:(r['fname'],r['lname']))
也可以正常运行但是itemgetter()通常会更快一些。如果要考虑性能的话还是itemgetter()。同样也适用于max(),min().
max(__iterable: Iterable[_T1],
*,
key: (_T1) -> SupportsLessThanT,
default: _T2) -> Union[_T1, _T2]
min(__iterable: Iterable[_T1],
*,
key: (_T1) -> SupportsLessThanT,
default: _T2) -> Union[_T1, _T2]
1.14 对不原生支持比较的操作对象排序
问题 想在同一个类的实例中做排序,但是它们并不原生支持比较操作
解决方法
内建的sorted()函数可以接受一个用来传递可调用对象(callable)的参数key,而该可调用对象会返回待排序对象中的某一些值,sorted则利用这些值来比较对象。
例如有一些User对象实例,想通过user.id属性来比较它们的排序,则可以提供一个可调用对象User实例作为输入然后返回user——id.
class User:
def __init__(self,user_id):
self.user_id=user_id
def __repr__(self):
return 'User({})'.format(self.user_id)
users=[User(23),User(3),User(99)]
print(users)
x=sorted(users,key=lambda u:u.user_id)
print(x)
# [User(23), User(3), User(99)]
# [User(3), User(23), User(99)]
#除了lambda表达式之外还可以使用operator。attrgetter
from operator import attrgetter
print(sorted(users,key=attrgetter('user_id')))
#[User(3), User(23), User(99)]
lambda 和attrgetter都可以但是attrgeter要更快一些,且具有允许同时提取多个字段值的能力。同1.13中的itemgetter().
1.15 根据字段将记录分组
问题 有一系列的字典或对象实例,我们想根据某个特定的字段(比如日期)来分组迭代数据。
解决方案
itertools.groupby()函数在对数据进行分组时特别有用.为了说明其用途,假设下面的字典.
rows=[
{'address': '5412 N CLARK','date':'07/01/2012'},
{'address': '5413 N CLARK', 'date': '07/04/2012'},
{'address': '5411 E 58TH', 'date': '07/02/2012'},
{'address': '5414 N CLARK', 'date': '07/03/2012'},
{'address': '5415 N RAVENSWOOD', 'date': '07/04/2012'},
{'address': '5416 N ADDISON', 'date': '07/12/2012'},
{'address': '5417 W CLARK', 'date': '07/09/2012'},
{'address': '5418 N BROADWAY', 'date': '08/01/2012'},
{'address': '5419 W GRANVILLE', 'date': '02/01/2012'}
]
#现在想根据日期分组方式迭代数据.要做到这些,首先以目标字段(date)来对序列排序,然后使用itertools.groupby()
from operator import itemgetter
from itertools import groupby
# Sort by the desired field first
rows.sort(key=itemgetter('date'))
#Iterate in groups
for date,items in groupby(rows,key=itemgetter('date')):
print(date)
for i in items:
print(' ',i)
#02 / 01 / 2012
#{'address': '5419 W GRANVILLE', 'date': '02/01/2012'}
#07 / 01 / 2012
#{'address': '5412 N CLARK', 'date': '07/01/2012'}
# 07 / 02 / 2012
# {'address': '5411 E 58TH', 'date': '07/02/2012'}
# 07 / 03 / 2012
# {'address': '5414 N CLARK', 'date': '07/03/2012'}
# 07 / 04 / 2012
# {'address': '5413 N CLARK', 'date': '07/04/2012'}
# {'address': '5415 N RAVENSWOOD', 'date': '07/04/2012'}
# 07 / 09 / 2012
# {'address': '5417 W CLARK', 'date': '07/09/2012'}
# 07 / 12 / 2012
# {'address': '5416 N ADDISON', 'date': '07/12/2012'}
# 08 / 01 / 2012
# {'address': '5418 N BROADWAY', 'date': '08/01/2012'}
函数groupby()通过扫描序列找出拥有相同值(或是由参数key指定的函数所返回的值)的序列,并将它们分组.groupby()创建了一个迭代器,而且在每次迭代的时候都会返回一个值(value)和一个子迭代器(sub_iterator),这个子迭代器可以产生所有在改分组内具有该值的项.
在这里重要的首先要根据感兴趣的字段对数据进行排序.因为groupby()只能检查连续的项,不首先排序的话,将无法按所想的方式来对记录分组.
如果只是简单的根据日期分组到一起放进一个大的数据结构中以允许进行随机访问,那么利用defaultdict()构建一个一键多值字典(multidict(),)可能会更好
例如:
from collections import defaultdict
rows_by_date = defaultdict(list)
for row in rows:
rows_by_date[row['date']].append(row)
#这使得我们可以方便的访问每个星期的日期的记录
for r in rows_by_date['07/01/2012']:
print(r)
# {'address': '5412 N CLARK', 'date': '07/01/2012'}
对于后面这个例子我们不需要先对记录做排序,因此如果不考虑内存方面的影响这种方式比先排序然后用groupby()要迭代的更快.
1.16 筛选序列中的元素
问题 序列中含有一些数据,我们需要提取其中的值或根据某一些标准对序列做删减.
解决方案
列表推导式( list comprehension).
mylist=[1,4,-1,10,24,12,512]
x_p=[n for n in mylist if n>0]
x_n=[n for n in mylist if n<0]
使用列表推导式的一个潜在缺点就是如果原始输入非常大的话,会产生一个很大的结果,如果这是要考虑的问题可以用使用生成器表达式通过迭代的方式产生筛选的结果.
mylist=[1,4,-1,10,24,12,512]
x_p=(n for n in mylist if n>0)
x_n=(n for n in mylist if n<0)
print(x_p,x_n)
for i in x_n:
print(i)
for i in x_p:
print(i)
# at 0x0000019B7DB0E580> at 0x0000019B7DB0E4A0>
# -1
# 1
# 4
# 10
# 24
# 12
# 512
有时候筛选的标准没法简单的表示在列表推导式或生成器表达式中.比如,假设筛选过程涉及异常处理或其他一些复杂的细节.基于此,可以将处理筛选的代码放在单独的函数中,然后使用内建的filter()函数处理.如下:
values=['1','2','3','4','5','-','N/A','5']
def is_int(val):
try:
x=int(val)
return True
except ValueError:
return False
ivals=list(filter(is_int,values))
print(ivals)
# ['1', '2', '3', '4', '5', '5']
filter()创建了一个迭代器,因此如果我们想要的是列表形式的结果,请确保加上了list.
还有对数据的转化能力
mylist=[1,4,-5,10,-7,2,3,-1]
import math
x=[math.sqrt(x) for x in mylist if x>0]
print(x)
# [1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
关于数据筛选有可能是用新值换掉其他值而不是丢弃掉它们
mylist=[1,4,-5,10,-7,2,3,-1]
# import math
# x=[math.sqrt(x) for x in mylist if x>0]
# print(x)
# # [1.0, 2.0, 3.1622776601683795, 1.4142135623730951, 1.7320508075688772]
clip_neq=[n if n>0 else 0 for n in mylist]
clip_pos=[n if n<0 else 0 for n in mylist]
print(clip_neq)
print(clip_pos)
# [1, 4, 0, 10, 0, 2, 3, 0]
# [0, 0, -5, 0, -7, 0, 0, -1]
另一个工具是itertools.compress() 它接受一个可迭代对象以及一个布尔选择器序列对象作为输入,输出时他会给出所有在相应真的对象元素,如果想把一个序列筛选的结果施加到另一个相关序列上时,则将会非常有用.
rows=[
{'address': '5412 N CLARK','date':'07/01/2012'},
{'address': '5413 N CLARK', 'date': '07/04/2012'},
{'address': '5411 E 58TH', 'date': '07/02/2012'},
{'address': '5414 N CLARK', 'date': '07/03/2012'},
{'address': '5415 N RAVENSWOOD', 'date': '07/04/2012'},
{'address': '5416 N ADDISON', 'date': '07/12/2012'},
{'address': '5417 W CLARK', 'date': '07/09/2012'},
{'address': '5418 N BROADWAY', 'date': '08/01/2012'},
{'address': '5419 W GRANVILLE', 'date': '02/01/2012'}
]
addresses=[i['address'] for i in rows]
# for i in rows:
# print(i['address'])
counts=[0,3,10,4,1,7,6,1]
from itertools import compress
more5=[n>5 for n in counts]
print(more5)
print(list(compress(addresses,more5)))
# [False, False, True, False, False, True, True, False]
# ['5411 E 58TH', '5416 N ADDISON', '5417 W CLARK']
这里的关键在布尔序列对另一个序列做的映射
同filter()函数一样,正常情况下compress()会返回一个迭代器.因此如果需要的话,得使用list()将结果转化为列表.
1.17 从字典中提取子集
问题 创建是另一个字典子集的字典
解决方案
利用字典推导式
prices={'ACME':45.23,'YYDS':11,'YSXANBEI':114514,'AAAAA':13341}
#make a dict of all price over 200
p1={key:value for key,value in prices.items() if value>200}
#make a dict of tech stocks
tech_names=['ACME','YYDS']
p2={key:value for key,value in prices.items() if key in tech_names}
print(p1)
print(p2)
# {'YSXANBEI': 114514, 'AAAAA': 13341}
# {'ACME': 45.23, 'YYDS': 11}
大部分字典推导式解决的问题也可以通过创建元组序列然后传给dict()解决
p1=dict((key,value) for key ,value in prices.items() if value >200)
但是字典推导方案更加清晰,运行效率也要快得多(本例2倍多).
tech_names={'A','B','C','D'}
p2 ={key:prices[key] for key in prices.keys() &tech_names}
但是要慢一些.
1.18 将名称映射到序列的元素中
问题 代码是通过位置来访问列表或者元组的,有时候会让代码难以阅读.希望用名称访问元素来减少对位置坐标的依赖性.
解决方案
相比普通的元组,collections.namedtuple()(命名元组)只增加了极小的开销就提供了这些便利.实际上collections.namedtuple()是一个工厂方法,它返回的是python中的标准元组的子类.提供一个类型名称和相应的字段,就返回一个可实例话的类.
Returns a new subclass of tuple with named fields.
Point = namedtuple('Point', ['x', 'y'])
Point.doc # docstring for the new class
'Point(x, y)'
p = Point(11, y=22) # instantiate with positional args or keywords
p[0] + p[1] # indexable like a plain tuple
33
x, y = p # unpack like a regular tuple
x, y
(11, 22)
p.x + p.y # fields also accessible by name
33
d = p._asdict() # convert to a dictionary
d['x']
11
Point(**d) # convert from a dictionary
Point(x=11, y=22)
p._replace(x=100) # _replace() is like str.replace() but targets named fields
Point(x=100, y=22)
from collections import namedtuple
Subscriber = namedtuple('Subscriber',['addr','joined'])
sub =Subscriber('[email protected]','2012-10-19')
print(sub)
print(sub.addr)
print(sub.joined)
# Subscriber(addr='[email protected]', joined='2012-10-19')
# [email protected]
# 2012-10-19
namedtuple的实例与普通元组是可以互换的,而且支持所有普通元组所支持的操作,例如引索(indexing)和分解(unpacking).
print(len(sub))
addr,joined=sub
print(addr,joined)
# 2
# [email protected] 2012-10-19
命名元组的主要作用是在与将代码同它所控制的元素位置间解耦.所以,如果从数据库调用中得到一个大型的元组列表,而且通过元组的位置来访问数据,那么假如在表单中新增一列数据,代码就会崩溃.但是如果首先将返回的元组转型为命名元组,就不会出现问题.
如下:
def compute_cost(records):
total=0.0
for rec in records:
total+=rec[1]*rec[2]
return total
#通过位置应用元组常常使代码的表达能力不够强,而且也很依赖于具体的结构.
from collections import namedtuple
Stock=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
#如果示例中的records列表已经包含了这样的实例,那么可以避免显式的将记录转化为Stock命名元组.
namedtuple 的一种用法是作为字典的代替,dict的存储空间耗费更大,因此,如果要构建涉及更大的数据结构,namedtuple会更高效,但是namedtuple is tuple so,namedtuple is immutable.
s=Stock('ACME',100,123.45)
print(s)
s.price=12
# Stock(name='ACME', shares=100, price=123.45)
# Traceback (most recent call last):
# File "G:/Desktop/test/learn_py/struct_angl.py", line 443, in
# s.price=12
# AttributeError: can't set attribute
如果需要修改任何属性,可以通过namedtuple的实例_replace()方法来实现.该方法创建一个全新的命名元组,并且相对应的值做替换.
s=s._replace(shares=12)
print(s)
#Stock(name='ACME', shares=12, price=123.45)
_replace()可以作为一种简单的方法填充具有可选或缺失字段的命名元组,做到这点,首先要用__replace()创建一个新的实例,把相应的值换掉.
from collections import namedtuple
Stock=namedtuple('Stock',['name','shares','prices','date','time'])
#Create a prototype instance
stock_protype = Stock('',0,0.0,None,None)
#Function to convert a dictionary to a stock
def dict_to_stock(s):
return stock_protype._replace(**s)
a={'name':'CSX','shares':100,'prices':123.45}
a_=dict_to_stock(a)
print(a_)
b={'name':'ZSQ','shares':520,'prices':13.14,'date':'4/8/2015'}
b_=dict_to_stock(b)
print(b_)
# Stock(name='CSX', shares=100, prices=123.45, date=None, time=None)
# Stock(name='ZSQ', shares=520, prices=13.14, date='4/8/2015', time=None)
最后,相当重要的是要注意我们的目标是定义一个高效的数据结构,而且将会修改各种实例属性,那么使用namedtuple不是最佳的选择,相反可以考虑定义一个使用slots属性的类.
1.19 同时对数据做转换和换算
问题 调用一个换算(reduction)函数(sum,min,max)但是首先要对数据筛选
解决方法
使用生成器表达式.
nums=[i for i in range(5)]
s=sum(x*x for x in nums)
#Determine if any .py file exist in a directory
import os
files=os.listdir('dirname')
if any(name.endswith('.py') for name in files):
print('there be py')
else:
print('no py')
#output a tuple as csv
s=('ACM',50,123.45)
print(','.join(str(i) for i in s))
#Data reduction across fields of a data strcture
portfolio=[
{'name':'A','shares':50},
{'name': 'B', 'shares': 52},
{'name': 'V', 'shares': 51},
{'name': 'S', 'shares': 1},
{'name': 'x', 'shares': 5}
]
min_shares=min(s['shares'] for s in portfolio)
把生成器作为函数表达式的单独参数时在语法上的一些微妙之处(不必重复使用括号)sum((x for x in nums))和sum(x for x in nums )是一个意思.
比起创建一个临时列表,生成器更加高效和优雅.
min_shares=min(s['shares'] for s in portfolio)
#Alternative return {'name':'S','shares':1}
min_shares_=min(portfolio,key=lambda s:s['shares'])
1.20 将多个映射合并为单个映射
问题 我们有多个字典,想在逻辑上将它们合并为一个单独的映射结构,以此做一些特定的操作,比如查找值或则键是否存在.
解决方法:
a={'x':1,'z':3}
b={'y':2,'z':4}
from collections import ChainMap
c=ChainMap(a,b)
print(c['x'])#from a
print(c['y'])#from b
print(c['z'])#from a
ChainMap可接受多个映射然后在逻辑上使他们表现为一个单独的映射结构.但是这些映射在字面上并不会合并在一起.ChainMap 只是简单的维护一个记录底层映射的列表,然后重新定义常见的字典操作来扫描这个表.
print(len(c))
print(list(c.keys()))
print(list(c.values()))
# 3
# ['y', 'z', 'x']
# [2, 3, 1]
如果有重复的键,会对应第一个映射采用的值修改操作也是对应第一个映射结构上.
ChainMap与带有作用域的值如编程语言中的变量一起工作时特别有用.
from collections import ChainMap
values=ChainMap()
values['x']=1
#add a new mapping
values =values.new_child()
values['x']=2
#add a new mapping
values=values.new_child()
values['x']=3
print(values)
print(values['x'])
#Discard last mapping
values=values.parents
print(values['x'])
#Discard last mapping
values=values.parents
print(values['x'])
print(values)
# ChainMap({'x': 3}, {'x': 2}, {'x': 1})
# 3
# 2
# 1
# ChainMap({'x': 1})
作为ChainMap的替代方案,利用update()方法将多个字典合并在一起.
a={'x':1,'z':3}
b={'y':2,'z':4}
# merged=dict(b)
# merged.update(a)
# print(merged)
# print(merged['x'])
# print(merged['y'])
# print(merged['z'])
# {'y': 2, 'z': 4, 'x': 1, 'Z': 3}
# 1
# 2
# 4
#行得通但是需要单独构建一个完整的字典对象(或修改其中一个原有的字典,破坏原始数据.如果原始数据修改了不会反应到合并后的字典中
# a['x']=3
# print(merged['x'])
#1
#但是ChainMap使用的就是原始的字典,所以会反应关联
from collections import ChainMap
merged_=ChainMap(a,b)
print(merged_['x'])
a['x']=42
print(merged_['x'])#Notice change to merged dicts
# 1
# 42
chapter 2
2.1 针对任意多的分割符拆分字符串
问题 将字符串拆分成不同的字段,但是分隔符在整个字符串中不一致.
解决方案
字符串对象的 split()方法只能处理非常简单的情况,而且不支持多个分割符,对分割负周围可能存在的空格也无能为力.
则使用re.split()
line='asdf fjdk; afed, fjek,asdf, foo'
import re
print(re.split(r'[;,\s]\s*',line))#分割符可以是;,或空格或空格后跟其他空格,只要找到了对应的模式无论匹配点的两端是什么字段整个字段就成为字段列表
# ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
re.split()可以为分割符指定多个模式.当使用re.split()时,需要小心正则表达式模式中的捕获组是否包含在了括号中.如果用到了捕获组,那么匹配的文本也会包含在最终结果中.
fildes=re.split(r'(;|,|,|\s)\s*',line)
print(fildes)
print("".join(fildes))
# ['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'foo']
# asdf fjdk;afed,fjek,asdf,foo
在特定的上下文中获取到分隔字符也可能是有用的.
values=fildes[::2]
delimiters=fildes[1::2]+['']
print(values)
print(delimiters)
# ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
# [' ', ';', ',', ',', ',', '']
如果在结果中不想看到分割字符,但仍想用括号来对正则表达式模式分组,请确保不是捕获模式,以(?:...)的形式指定.
print(re.split(r'(?:,|;|\s)\s*',line))
# ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']