python错误:module ‘shutil’ has no attribute ‘copy’

栏目: Python · 发布时间: 7年前

内容简介:python错误:module ‘shutil’ has no attribute ‘copy’

字符:5706

平移阅读时长:7分钟

今天在写一个小脚本来把自己写的一个html批量复制并在保持后缀不变的情况下随机命名。

先打算实现文件的拷贝。

Google了一下,发现了一个 shutil 的库(这个库的名字纠结了发音老半天,后面才理解是sh和util的结合,也就是shell-util)

这个库提供了与 shell 命令操作文件等同的所有方法: copy , copytree , rmtree , move

代码实现

有了这个库,代码很容易就实现了:

import shutil

src = 'sa-jumper.html'

randomStr = 'ad239sdfasdl2asdf9adsfi23dfladf'
strLen = len(randomStr)

shutil.copy(src, 'tmp/aa.html')

关于随机的部分,就只是提前做了准备。先用的 shutil.copy 来测试文件拷贝功能。

然后我先用的 python3.6 执行了这个文件,结果报错:

Traceback (most recent call last):
  File "copy.py", line 2, in <module>
    import shutil
  File "/usr/local/Cellar/python3/3.6.0_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/shutil.py", line 13, in <module>
    import tarfile
  File "/usr/local/Cellar/python3/3.6.0_1/Frameworks/Python.framework/Versions/3.6/lib/python3.6/tarfile.py", line 49, in <module>
    import copy
  File "/Library/WebServer/Documents/xxx/copy.py", line 13, in <module>
    shutil.copy(src, 'tmp/aa.html')
AttributeError: module 'shutil' has no attribute 'copy'

后面改用 python2.7 执行,顺利通过。

debug

打开 Python 的解释器提示符,执行上述代码,也是成功的。

这下我就没辙了,感觉个人的Python经验只能到此为止,启用Google大法。

在StackOverflow的一篇文章上,发现有人遇到类似的问题,链接如下:

https://stackoverflow.com/questions/22131139/attributeerror-module-object-has-no-attribute-x

在上述链接里面,最终的错误是因为提问的人,python脚本的名称是 org.py 跟系统的一个文件重名,导致了一个错误的引用。

所以很自然的,我就联想到了我的文件,名字是 copy.py ,而看上面的错误信息里面,是用一个 import copy ,估计也是同样错误的把我的脚本当作官方库给错误引用了。

修复

把文件名改成了 copy-file.py ,再用 python3.6 执行,一切顺利。

分析

从这个错误里面想到几个问题:

  • 为啥跟官方库里面的文件同名会导致引用错误
  • 为啥python2.7没报错
  • 如何避免这样的问题

关于引用错误

重新翻了一下之前看的《Python简明教程》

关于模块的索引

如果它不是一个已编译好的模块,即用 Python 编写的模块,那么 Python 解释器将从它的 sys.path 变量所提供的目录中进行搜索。如果找到了对应模块,则该模块中的语句将在开始运行,并能够为你所使用。在这里需要注意的是,初始化工作只需在我们第一次导入模块时完成。

关于索引的顺序

sys.path 内包含了导入模块的字典名称列表。你能观察到 sys.path 的第一段字符串是空的——这一空字符串代表当前目录也是 sys.path 的一部分,它与 PYTHONPATH 环境变量等同。这意味着你可以直接导入位于当前目录的模块。否则,你必须将你的模块放置在 sys.path 内所列出的目录中。

从这里可以看到,执行的脚本所在的目录也是在python搜索模块的范围内的,而且是第一位,也就是会优先搜索的,这也就是为啥同名会导致引用错误的原因了。

为啥2.7没错

按照上面StackOverflow那边文章的分析过程,我分别看了python3.6和python2.7版本里面 shutil.pytarfile.py 源码,发现 shutil.py 都引用了 tarfile.py ,而 tarfile.py 也都 import copy

那就不可能是2.7里面没有引用copy了。

然后我又仔细的看了一下各自的实现,终于发现了问题,在2.7里面,不是在初始化的时候就import了tarfile,而是在一个 _make_tarball  的函数里面。

我在这个函数的 import tarfile 前面加了一个 print('import tarfile') ,再去执行刚刚的 copy.py (这块为了测试方便,把 copy-file.py 复制了一份重命名为 copy.py )。

发现没有print任何内容。

而在3.6里面,是在文件头部,一开始就import了tarfile。这也就解释了为啥2.7没问题,3.6有问题了。同时也说明: python的import是按需加载的 。写在函数里面的import,只有在函数被调用的时候才会真实的去import

为了反证这个推断是否有效,我找到了2.7里面shutil的一个会触发 import tarfile 的函数 make_archive

使用了官方提供的example:

import shutil
import os
archive_name = os.path.expanduser(os.path.join('~', 'myarchive'))
root_dir = os.path.expanduser(os.path.join('~', '.ssh'))
shutil.make_archive(archive_name, 'gztar', root_dir)

执行 python copy.py (我的环境里面,默认是2.7)

果然报错了:

import tarfile
import tarfile
Traceback (most recent call last):
  File "copy.py", line 20, in <module>
    shutil.make_archive(archive_name, 'gztar', root_dir)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 561, in make_archive
    filename = func(base_name, base_dir, **kwargs)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 376, in _make_tarball
    import tarfile  # late import so Python build itself doesn't break
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/tarfile.py", line 52, in <module>
    import copy
  File "/Library/WebServer/Documents/xxx/copy.py", line 20, in <module>
    shutil.make_archive(archive_name, 'gztar', root_dir)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 561, in make_archive
    filename = func(base_name, base_dir, **kwargs)
  File "/usr/local/Cellar/python/2.7.13/Frameworks/Python.framework/Versions/2.7/lib/python2.7/shutil.py", line 394, in _make_tarball
    tar = tarfile.open(archive_name, 'w|%s' % tar_compression[compress])
AttributeError: 'module' object has no attribute 'open'

然后我尝试把文件名修改为 copy-file.py ,再执行,顺利通过。

看来2.7和3.6都是一样的索引模块的方式,只是因为 shutil 模块里面引用 tarfile 的方式不一样,才造成一开始的执行结果不一样。

如何避免这样的问题

我还是一个python新手,不知道python在命名的时候有没有提供一些合理的规范,比如不能使用的单词。

然后我又一次看了《简明Python教程》,里面关于标识符命名这块:

量是标识符的一个例子。标识符(Identifiers) 是为 某些东西 提供的给定名称。在你命名标识符时,你需要遵守以下规则:

• 第一个字符必须是字母表中的字母(大写 ASCII 字符或小写 ASCII 字符或 Unicode 字符)或下划线(

)。

)、数字(0~9)组成。

• 标识符名称区分大小写。例如,myname 和 myName 并不等同。要注意到前者是小写字母 n 而后者是大写字母 N。

• 有效 的标识符名称可以是 i 或 name_2_3 ,无效 的标识符名称可能是 2things,this is spaced out,my-name 和 >a1b2_c3。

我想,如果我以非这个规范的方式定义文件名是不是就可以了。

然而用类似 import copy-file 的方式引用自己的模块,会报错, SyntaxError

也有看到有相关的解决方式:

  • python2.7 execfile('foo-bar.py')
  • python3.x exec(open(fn).read())

不过这些方法感觉都不是很自然,还是放弃这样的尝试了。

只能使用另一种方式了,就是统一文件名的前缀。如果你的项目是 foo ,那么可以给所有项目的文件定义以这个为前缀的名称: foo__copy.py , foo_shutil.py 等等。

这样只要 foo 是一个不会和常见的库的前缀重名的字符串,就能保证项目里面不会再发生这样的名称冲突了。

参考资料

突然发现,我只是想实现一个文件拷贝,咋就绕了这么远呢:sob:


以上所述就是小编给大家介绍的《python错误:module ‘shutil’ has no attribute ‘copy’》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

了不起的Node.js

了不起的Node.js

劳奇 (Guillermo Rauch) / 赵静 / 电子工业出版社 / 2014-1 / 79.00元

本书是一本经典的 Learning by Doing的书籍。它由 Node社区著名的 Socket.IO作者—— Guillermo Rauch,通过大量的实践案例撰写,并由 Node社区非常活跃的开发者—— Goddy Zhao翻译而成。 本书内容主要由对五大部分的介绍组成: Node核心设计理念、 Node核心模块 API、Web开发、数据库以及测试。从前到后、由表及里地对使用 Node......一起来看看 《了不起的Node.js》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

URL 编码/解码
URL 编码/解码

URL 编码/解码