Python - 我不该做的事?
我有一些关于Python最佳实践的问题。之前我写代码的时候,常常会这样做:
...
junk_block = "".join(open("foo.txt","rb").read().split())
...
现在我不这样做了,因为我发现这样会让代码更难读。不过,如果我把语句分开写,像这样:
f_obj = open("foo.txt", "rb")
f_data = f_obj.read()
f_data_list = f_data.split()
junk_block = "".join(f_data_list)
我还注意到,在一个函数里面是可以进行'import'导入的,那这样做有什么特别的理由吗?
6 个回答
来自PEP 8(反正值得一读)
导入的内容总是放在文件的最上面,紧接着模块的注释和文档字符串,然后再是模块的全局变量和常量。
首先,其实没有什么理由让你不使用第一个例子——它的写法很简洁,容易理解。既然只是简单的函数调用组合,就没有必要把它拆开。
其次,在一个函数内部使用import
是很有用的,特别是当你只在这个函数里需要某个库的功能时。因为导入的内容只在你导入的那个代码块内有效,如果你只用到一次,就可以在需要的地方导入,这样就不用担心在其他函数中会有名字冲突。这种做法在使用from X import Y
时特别方便,因为Y不会带上它所在模块的名字,这样就可能避免和其他模块中同名的函数发生冲突。
只要你在一个函数里面(而不是在模块的顶层),把中间结果赋值给局部变量的成本几乎可以忽略不计(在模块顶层赋值给“局部”变量意味着要操作一个字典——模块的 __dict__
——这比在函数内部要贵得多;解决办法就是不要在模块顶层写“实质性”的代码……总是把实质性的代码放在函数里!)。
Python的一般理念是“扁平化比嵌套更好”——这也包括那些高度“嵌套”的表达式。看看你最初的例子……:
junk_block = "".join(open("foo.txt","rb").read().split())
还有一个重要的问题:那个文件什么时候会被关闭?在今天的CPython中,你不需要担心——引用计数实际上能确保及时关闭。但是大多数其他Python实现(比如在JVM上的Jython,在.NET上的IronPython,各种后端上的PyPy,Parrot上的pynie,LLVM上的Unladen Swallow,如果它按照发布的路线图成熟的话……)都不保证使用引用计数——可能会涉及许多垃圾回收策略,还有其他各种好处。
如果没有任何引用计数的保证(即使在CPython中,这也一直被认为是实现的细节,而不是语言的语义!),你可能会因为在紧密循环中执行这种“打开但不关闭”的代码而耗尽资源——垃圾回收是因为内存不足而触发的,并不考虑其他有限资源,比如文件描述符。从2.6版本开始(2.5版本也可以通过“从未来导入”来实现),Python通过RAII(“资源获取即初始化”)的方法提供了一个很好的解决方案,这个方法是通过 with
语句来支持的:
with open("foo.txt","rb") as f:
junk_block = "".join(f.read().split())
这是确保在所有符合标准的Python版本中及时关闭文件的最“扁平”的方式。更强的语义使它更可取。
除了确保正确和谨慎的语义外,嵌套和扁平版本的表达式之间没有太多选择。考虑到任务是“从文件内容中移除所有空白字符”,我会倾向于基于 re
和字符串的 .translate
方法来基准测试不同的方法(后者,尤其是在Python 2.*中,通常是删除某个字符集合中所有字符的最快方式!),如果“分割和重新连接”的方法证明更快,我会选择它——但这其实是一个相当不同的问题;-)。