[Python]list.append()在for循环中每次添加的都是最后的一个元素

先贴出源码吧,这段代码我想返回一个list,list中的元素由N个dict组成, dict中会包含目录下文件的名称,大小和最后修改时间(ps.大小和最后修改时间没有贴出来)

import os

def listDirectory(path):
    pathlist = []
    info = {}
    if os.path.isdir(path):
        for f in os.listdir(path):
            if not f.startswith('.'): #排除隐藏文件
                info['name'] = f
                pathlist.append(info) #将dict添加进list中
        return pathlist,len(pathlist)
    else:
        return -1,-1

尝试去使用这个函数查询某个文件夹下的内容时,目录文件是如下所示:

lishuo@DELL:~$ ls
connect.sh  github  nohup.out  rjsupplicant  tasks  下载  

然而,在我调用这个函数去查询某个目录下的文件时,返回的list中只会显示这个文件夹下的最后一个文件,结果如下:

In [1]: from disp_list import listDirectory

In [2]: listDirectory('/home/lishuo')
Out[2]: 
([{'name': 'nohup.out'},
  {'name': 'nohup.out'},
  {'name': 'nohup.out'},
  {'name': 'nohup.out'},
  {'name': 'nohup.out'},
  {'name': 'nohup.out'}],
 6)

我感觉问题应该是和python的闭包有关,但是这段代码不知道该从何入手修改?请高手指导,感谢所有花时间阅读我的问题的人^_^


我来补充一点,有点进展,但是不知道为什么?

import os

def listDirectory(path):
    pathlist = []
    if os.path.isdir(path):
        for f in os.listdir(path):
            info = {}
            if not f.startswith('.'):
                info['name'] = f
                pathlist.append(info)
        return pathlist,len(pathlist)
    else:
        return -1,-1

结果:

In [1]: from disp_list import listDirectory

In [2]: listDirectory('/home/lishuo')
Out[2]: 
([{'name': 'github'},
  {'name': 'connect.sh'},
  {'name': 'tasks'},
  {'name': '\xe4\xb8\x8b\xe8\xbd\xbd'},
  {'name': 'rjsupplicant'},
  {'name': 'nohup.out'}],
 6)

把info字典的定义放在for循环的里面,结果显示就正常了,百度知道查到的但是不知道为什么?字典是可变对象,初始化一定不能放在for循环内部,请问这是为什么?

阅读 16.3k
2 个回答

首先得知道三点。
1、程序的运行是需要去内存中申请地址的。
2、赋值操作只是对于内存中某一块地址的引用。
3、Python 内置的 id()函数。 该函数从概念上可以理解为得到当前生命下的内存地址。

id(object)
Return the “identity” of an object. This is an integer which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same id() value.

CPython implementation detail: This is the address of the object in memory.

由此我们可以得到以下结果:

a = 1
b = 1
c = a
d = b

print(id(1))                                       # value x
print(id(a))                                       # value x
print(id(b))                                       # value x
print(id(c))                                       # value x
print(id(d))                                       # value x

print(id(1) == id(a) == id(b) == id(c) == id(d))   # True

在此基础上去看 字典/dict :

当声明一个字典 info = {} 的操作时候,该字典就已经在内存中获取了某一块地址。
对该字典进行操作时,如 info['name'] = 'github' 的时候,这个字典依旧是之前所占用的地址。
可通过id 函数跟踪得到以下代码:

info = {}                       
print(id(info))                 # value y

info['name'] = 'github'
print(id(info))                 # value y

因此,对于你改进前的代码
pathlist.append(info)添加进去的始终是同一个info,准确的说,始终是同一块地址,而这个info内容在不停的修改。
参考以下代码:

info = {'name': 'github'}
pathlist = [info,]

print(id(info))                 # value z
print(id(pathlist[0]))          # value z

然后,对于改进后的代码
info = {} 的操作放在了循环内,结果就是每一次循环都申请使用一段新的地址,只不过依旧用info来引用。
可由一下代码对比:

info = {}
print(id(info))                 # value m

info = {}
print(id(info))                 # value n

两次打印的值是不等的。

另外
第一段代码中的
pathlist.append(info) #将dict添加进list中
这个注释, 了。

希望能帮到你。

原始代码

listDirectory最终返回的是

[info, info, info...]

而info每次循环都会更新,但最终必为

{'name': last_loop_item}

所以会有那样的结果

更新代码

更新后的代码就比较直观了,
每次info都是新生成的dict,append的是每次循环产生的结果

推荐问题
宣传栏