内容简介: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.py
和 tarfile.py
源码,发现 shutil.py
都引用了 tarfile.py
,而 tarfile.py
也都 import copy
- https://hg.python.org/cpython/file/2.7/Lib/shutil.py
- https://github.com/python/cpython/blob/3.6/Lib/shutil.py
- https://hg.python.org/cpython/file/2.7/Lib/tarfile.py
- https://github.com/python/cpython/blob/3.6/Lib/tarfile.py
那就不可能是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) 是为 某些东西 提供的给定名称。在你命名标识符时,你需要遵守以下规则:
)。
)、数字(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
是一个不会和常见的库的前缀重名的字符串,就能保证项目里面不会再发生这样的名称冲突了。
参考资料
- https://stackoverflow.com/questions/123198/how-do-i-copy-a-file-in-python
- https://stackoverflow.com/questions/22131139/attributeerror-module-object-has-no-attribute-x
- https://docs.python.org/2/library/shutil.html
- https://stackoverflow.com/questions/8350853/how-to-import-module-when-module-name-has-a-dash-or-hyphen-in-it
- https://stackoverflow.com/questions/6031584/importing-from-builtin-library-when-module-with-same-name-exists
- https://bop.molun.net/07.basics.html
- https://bop.molun.net/11.modules.html
突然发现,我只是想实现一个文件拷贝,咋就绕了这么远呢:sob:
以上所述就是小编给大家介绍的《python错误:module ‘shutil’ has no attribute ‘copy’》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Golang学习笔记之错误处理error、panic (抛出错误),recover(捕获错误)
- c – 构建PBRT v2错误 – 错误1错误U1077:’if’:返回代码’0x1′
- !错误!在 Android 下这么用 ShowModal 是错误的!
- Google开源ClusterFuzz:使得查找错误并修复错误变得异常简单
- 脚本错误量极致优化-定位压缩且无 SourceMap 文件的脚本错误
- php – 解析错误:语法错误,意外’未设置'(T_UNSET)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。