Python 列表推导及其他更好实践

3 投票
3 回答
3487 浏览
提问于 2025-04-16 03:28

这段内容是关于一个项目,目的是把一个用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 个回答

1

有时候在你的数据结构中产生合适的递归层级可能会有点复杂,不过我觉得在你的情况下应该相对简单。为了测试,我们需要一份示例数据,比如:

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

1

关于复制的部分,可以看看copy模块。Python在创建第一个对象后,后面的“复制”其实只是引用,也就是说如果你在其他“复制”上做了改动,这些改动会影响到原始对象。不过,copy模块可以真正地复制对象,而且你可以选择不同的复制方式。

6

byAxB= [item for sublist in response for item in sublist] 这段代码能不能有人解释一下为什么要这样写呢?

我相信A.M.会给你一个很好的解释。在他出现之前,我来试着解释一下。

我会从左到右来理解这段代码。看看这四个词:

for sublist in response

希望你能看到这和普通的 for 循环有点像。这四个词在为对每个 sublistresponse 中执行某个操作做准备。看起来 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》有一个专门讲解列表推导的部分。还有这个不错的教程可以阅读。

更新

我忘了说一件事。列表推导是另一种实现传统上使用mapfilter的方式。如果你想提高对列表推导的理解,了解 mapfilter 是个好主意。

撰写回答