我有一些关于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)
我还注意到,没有什么可以阻止你在功能块中进行“导入”,有什么理由我应该这样做吗?
答案 0 :(得分:21)
只要您在函数内部(不在模块顶层),将中间结果分配给本地裸名称的成本基本上可以忽略不计(在模块顶层,分配给“本地” “裸名称意味着在一个字典上翻腾 - 模块的__dict__
- 并且比一个函数内的成本要高得多;补救措施是永远不要在模块顶层拥有”实质性“代码......总是存在大量代码函数中的代码! - )。
Python的一般哲学包括“扁平比嵌套更好” - 并且包括高度“嵌套”表达式。看看你原来的例子......:
junk_block = "".join(open("foo.txt","rb").read().split())
提出了另一个重要问题:该文件何时关闭?在今天的CPython中,你不必担心 - 在实践中引用计数确保及时关闭。但是大多数其他Python实现(JVM上的Jython,.NET上的IronPython,各种后端的PyPy,Parrot上的pynie,LLVM上的Unladen Swallow,如果它按照已发布的路线图成熟,......)做 not 保证使用引用计数 - 可能涉及许多垃圾收集策略,具有各种其他优点。
没有任何保证引用计数(甚至在CPython中它总是被认为是一个实现工件,不是语言语义的一部分!),你可能会耗尽资源,执行这样的“开放但是没有关闭“紧密循环中的代码 - 垃圾收集是由内存稀缺触发的,并没有考虑其他有限的资源,如文件描述符。从2.6(和2.5,带有“从未来导入”)开始,Python通过with
语句支持的RAII(“资源获取是初始化”)方法提供了一个很好的解决方案:
with open("foo.txt","rb") as f:
junk_block = "".join(f.read().split())
是最少“无法使用”的方式,可确保在所有兼容的Python版本中及时关闭文件。更强的语义使它更受欢迎。
除了确保正确,谨慎;-),语义之外,在这样的表达式的嵌套和扁平版本之间没有太多选择。鉴于任务“从文件的内容中删除所有空白行”,我很想基于re
和.translate
字符串方法(后者,特别是Python 2)中的替代方法进行基准测试。*,通常是从某个集合中删除所有字符的最快方法!),在确定“拆分并重新加入”方法之前,如果事实证明它更快 - 但这确实是一个相当不同的问题; - )。
答案 1 :(得分:4)
首先,你不应该使用第一个例子的原因并不是真的 - 它很可读,因为它简洁明了它的作用。没有理由将它分解,因为它只是一个线性的呼叫组合。
其次,如果在该函数中只需要一个特定的库函数,函数块中的import
是有用的 - 因为导入符号的范围只是导入它的块,如果你只是曾经使用过一次,您可以在需要的地方导入它,而不必担心其他功能中的名称冲突。这对于from X import Y
语句尤其方便,因为Y不会被其包含的模块名限定,因此可能与其他地方使用的其他模块中的类似命名函数冲突。
答案 2 :(得分:3)
来自PEP 8(无论如何都值得一读)
导入总是放在文件的顶部,就在任何模块注释和文档字符串之后,以及模块全局变量和常量之前
答案 3 :(得分:2)
该行与此结果相同:
junk_block = open(“foo.txt”,“rb”)。read()。replace('','')
在您的示例中,您将文本的单词拆分为单词列表,然后将它们连接在一起,没有空格。上面的例子使用了str.replace()方法。
差异:
您将文件对象构建到内存中,通过读取将字符串构建到内存中,通过拆分字符串将列表构建到内存中,通过加入列表来构建新字符串。
我将一个文件对象构建到内存中,通过读取它将字符串构建到内存中,通过替换空格在内存中构建一个新字符串。
您可以看到新变体中使用的RAM少一些,但使用的处理器更多。 RAM在某些情况下更有价值,因此当可以避免时,内存浪费是不受欢迎的。
大部分内存将立即被垃圾收集,但同时多个用户将占用内存。
答案 4 :(得分:0)
如果您想知道您的第二个代码片段是否较慢,快速查找方法就是使用timeit。我不希望有那么大的差别,因为它们看起来非常相同。
您还应该询问性能差异在相关代码中是否真正重要。通常,可读性比性能更有价值。
我无法想到在函数中导入模块的任何好理由,但有时你只是不知道在看到问题之前你需要做些什么。我必须把它留给其他人指出一个有建设性的例子,如果存在的话。
答案 5 :(得分:0)
我认为这两个代码是可读的。我(这只是一个个人风格的问题)可能会使用第一个,添加一个coment行,例如:“打开文件并将数据转换成列表”
此外,有时我使用第二个,也许不是那么分开,但是像
f_data = open("foo.txt", "rb").read()
f_data_list = f_data.split()
junk_block = "".join(f_data_list)
但是后来我给每个操作提供了更多实体,这在代码流程中可能很重要。我认为重要的是你很舒服,不要认为代码很难在将来理解。
明确地说,代码不会(至少,很多)慢,因为您所做的唯一“重载”是将结果转换为值。