Python 列表推导及其他更好实践
这段内容是关于一个项目,目的是把一个用SAS写的双向方差分析程序转换成Python。
我大概是从星期四开始学习Python的,所以我知道自己还有很多需要提高的地方。如果我漏掉了什么明显的东西,请告诉我。目前我还没有安装Sage和numpy,所以现在用的都是比较基础的Python 2.6.1(便携版)。
主要问题是:我需要一组好的列表推导式,能够根据因素A和因素B提取样本数据,按整体和每个因素A和B的水平分组(AxB)来提取。
经过一些工作,数据现在是这样的形式(有三层嵌套列表):
response[a][b][n]
(意思是 [a1 [b1 [n1, ... ,nN] ...[bB [n1, ...nN]]], ... ,[aA [b1 [n1, ... ,nN] ...[bB [n1, ...nN]]] 希望这样说清楚了。)
在我的例子中,因素的水平是:A=3(0-2),B=8(0-7),N=8(0-7)
byA= [[a[i] for i in range(b)] for a[b] in response]
(有人能解释一下为什么这个语法有效吗?我是在尝试看看解析器能接受什么时偶然发现的。我在其他地方没见过这种语法和这种行为,但它真的很不错。如果有好的链接或者书籍推荐,感谢!编辑:变量在运行之间的持久性解释了这个奇怪的现象。它并不有效。)
byB=lstcrunch([[Bs[i] for i in range(len(Bs)) ]for Bs in response])
(值得注意的是,zip(*response)
几乎能做到我想要的。上面的版本实际上并没有工作,我记得。我还没有经过仔细测试。)
byAxB= [item for sublist in response for item in sublist]
(这是从Alex Martelli在这个网站上的一个回答中借来的。再问一次,有人能解释一下为什么吗?我读的书里对列表推导式的语法解释得不是很好。)
ByO= [item for sublist in byAxB for item in sublist]
(显然,我只是重复使用了之前的推导式,因为它满足了我的需求。编辑:)
我希望这些最终能得到相同的数据类型,至少在通过相关因素循环时,这样就可以应用和使用相同的平均值/总和/平方和等函数。
这可以很容易地被更简洁的方式替代:
def lstcrunch(Dlist):
"""Returns a list containing the entire
contents of whatever is imported,
reduced by one level.
If a rectangular array, it reduces a dimension by one.
lstcrunch(DataSet[a][b]) -> DataOutput[a]
[[1, 2], [[2, 3], [2, 4]]] -> [1, 2, [2, 3], [2, 4]]
"""
flat=[]
if islist(Dlist):#1D top level list
for i in Dlist:
if islist(i):
flat+= i
else:
flat.append(i)
return flat
else:
return [Dlist]
哦,既然提到这个,识别一个变量为列表的最佳方法是什么?我一直在使用:
def islist(a):
"Returns 'True' if input is a list and 'False' otherwise"
return type(a)==type([])
最后一个问题:有没有办法明确地强制浅拷贝转换为深拷贝?或者,当复制到一个变量时,有没有办法声明这个赋值也要替换指针,而不仅仅是值?(这样赋值就不会传播到其他的浅拷贝)类似地,使用这个在某些时候也可能很有用,所以能够控制何时发生或不发生听起来真的不错。
(当我准备插入表格时,我搞得一团糟,调用了: response=[[[0]*N]*B]*A)
编辑:进一步调查后发现大部分内容都能正常工作。我已经创建了类并进行了测试,效果很好。我会保留列表推导式的形式以供参考。
def byB(array_a_b_c):
y=range(len(array_a_b_c))
x=range(len(array_a_b_c[0]))
return [[array_a_b_c[i][j][k]
for k in range(len(array_a_b_c[0][0]))
for i in y]
for j in x]
def byA(array_a_b_c):
return [[repn for rowB in rowA for repn in rowB]
for rowA in array_a_b_c]
def byAxB(array_a_b_c):
return [rowB for rowA in array_a_b_c
for rowB in rowA]
def byO(array_a_b_c):
return [rep
for rowA in array_a_b_c
for rowB in rowA
for rep in rowB]
def gen3d(row, col, inner):
"""Produces a 3d nested array without any naughty shallow copies.
[row[col[inner]] named s.t. the outer can be split on, per lprn for easy display"""
return [[[k for k in range(inner)]
for i in range(col)]
for j in range(row)]
def lprn(X):
"""This prints a list by lines.
Not fancy, but works"""
if isiterable(X):
for line in X: print line
else:
print x
def isiterable(a):
return hasattr(a, "__iter__")
感谢所有回复的人。由于我的知识有所提高,代码质量已经有了明显的改善。当然,进一步的想法仍然很受欢迎。
3 个回答
有时候在你的数据结构中产生合适的递归层级可能会有点复杂,不过我觉得在你的情况下应该相对简单。为了测试,我们需要一份示例数据,比如:
data = [ [a,
[b,
range(1,9)]]
for b in range(8)
for a in range(3)]
print 'Origin'
print(data)
print 'Flat'
## from this we see how to produce the c data flat
print([(a,b,c) for a,[b,c] in data])
print "Sum of data in third level = %f" % sum(point for point in c for a,[b,c] in data)
print "Sum of all data = %f" % sum(a+b+sum(c) for a,[b,c] in data)
关于类型检查,通常你应该避免这样做,但如果你必须这么做,比如当你不想在字符串中进行递归时,可以这样处理:
if not isinstance(data, basestring) : ....
如果你需要把结构“扁平化”,你可以在Python文档中找到有用的代码(另一种表达方式是 chain(*listOfLists))
,还有用列表推导式 [ d for sublist in listOfLists for d in sublist ]
):
from itertools import flat.chain
def flatten(listOfLists):
"Flatten one level of nesting"
return chain.from_iterable(listOfLists)
不过,如果你的数据深度不同,这种方法就不管用了。想要更强大的扁平化工具,可以看看这个链接:http://www.python.org/workshops/1994-11/flatten.py,
关于复制的部分,可以看看copy模块。Python在创建第一个对象后,后面的“复制”其实只是引用,也就是说如果你在其他“复制”上做了改动,这些改动会影响到原始对象。不过,copy模块可以真正地复制对象,而且你可以选择不同的复制方式。
byAxB= [item for sublist in response for item in sublist]
这段代码能不能有人解释一下为什么要这样写呢?
我相信A.M.会给你一个很好的解释。在他出现之前,我来试着解释一下。
我会从左到右来理解这段代码。看看这四个词:
for sublist in response
希望你能看到这和普通的 for
循环有点像。这四个词在为对每个 sublist
在 response
中执行某个操作做准备。看起来 response
是一个包含多个列表的列表。在这种情况下,sublist
就是每次遍历 response
时的一个列表。
for item in sublist
这又是一个正在形成的 for
循环。因为我们在前面的“循环”中第一次提到 sublist
,这表明我们现在正在逐个遍历 sublist
中的每个 item
。如果我把这些循环写出来,而不使用列表推导,它看起来会像这样:
for sublist in response:
for item in sublist:
接下来,我们来看剩下的词。[
、item
和 ]
。这实际上意味着,把 item
收集到一个列表中,并返回这个结果列表。
每当你在创建或理解列表迭代时遇到困难,可以把相关的 for
循环写出来,然后再压缩成一行:
result = []
for sublist in response:
for item in sublist:
result.append(item)
这将压缩成:
[
item
for sublist in response
for item in sublist
]
我在阅读的书籍中,列表推导的语法没有得到很好的解释。
《Dive Into Python》有一个专门讲解列表推导的部分。还有这个不错的教程可以阅读。
更新
我忘了说一件事。列表推导是另一种实现传统上使用map
和filter
的方式。如果你想提高对列表推导的理解,了解 map
和 filter
是个好主意。