利用IPython提高代码开发效率的几点提示

为了在IPython中开发、调试代码,并充分发挥其交互优势,许多用户都需要转换一下工作模式。像编码风格以及一些操作细节可能需要做一些调整。

就这点来说,本节的内容更像是艺术而非科学,你需要有一些编程经验才好判断其能否提高你的工作效率。总之,你得让你的代码结构更易于交互且结果更易于查看。我发现通过IPython设计的软件要比独立的命令行应用程序好用。当你执行自己或别人在几个月甚至几年前编写的代码时出现了错误,想找出问题所在时,IPython的交互性就会变得非常重要。

重新加载模块依赖项

在Python中,当你输入import some_lib时,some_lib中的代码就会被执行,且其中所有的变量、函数和引入项都会被保存在一个新建的some_lib模块命名空间中。下次你再输入import some_lib时,就会得到这个模块命名空间的一个引用。而这对于IPython的交互式代码开发模式就会有一个问题,比如说,用%run执行的某段脚本中牵扯到了某个刚刚做了修改的模块。假设我们有一个test_script.py文件,其中有下列代码:

  1. import some_lib
  2.  
  3. x = 5
  4. y = [1, 2, 3, 4]
  5. result = some_lib.get_answer(x, y)

如果在执行了%run test_script.py之后又对some_lib.py进行了修改,下次再执行%run test_script.py时将仍然会使用老版的some_lib。其原因就是Python的“一次加载”模块系统。这个行为不同于其他一些数据分析环境(如MATLAB,它会自动应用代码修改注1)。为了解决这个问题,你有两个办法可用。第一个办法是使用Python内置的reload函数。将test_script.py修改成下面这个样子:

  1. import some_lib
  2. reload(some_lib)
  3.  
  4. x = 5
  5. y = [1, 2, 3, 4]
  6. result = some_lib.get_answer(x, y)

这样就保证每次执行test_script.py时都能用上最新版的some_lib了。显然,当依赖变得更强时,就需要在很多地方插入很多的reload。对于这个问题,IPython提供了一个特殊的dreload函数(非魔术函数)来解决模块的“深度”(递归)重加载。如果执行import some_lib之后再输入dreload(some_lib),则它会尝试重新加载some_lib及其所有的依赖项。遗憾的是,这个办法也不是万灵丹,但是如果真的不行了,重启IPython就行了。

代码设计提示

这个问题不太好讲,但我在日常工作中确实发现了一些高层次的原则。

保留有意义的对象和数据

人们一般不会在命令行上编写下面这样的程序:

  1. from my_functions import g
  2.  
  3. def f(x, y):
  4. return g(x + y)
  5.  
  6. def main():
  7. x = 6
  8. y = 7.5
  9. result = x + y
  10.  
  11. if __name__ == '__main__':
  12. main()

如果我们在IPython中执行这段代码的话会出现什么问题?我们在IPython shell中将访问不到任何结果以及main函数中定义的对象。好点的办法是直接在该模块的全局命名空间中执行main中的代码(如果你希望该模块是可引入的,也可以将这些代码放在if name=='main':块中)。这样,当你%run这段代码时,就能看到main中定义的所有变量了。对这个简单的例子而言,这个原则意义不大,但对本书后面将要介绍的那些针对大数据集的复杂数据分析问题而言就很重要了。

扁平结构要比嵌套结构好

深度嵌套的代码让我想到了洋葱。在测试或调试函数时,你要把这个洋葱剥多少层才能找到感兴趣的代码?“扁平结构要比嵌套结构好”的思想来自"Zen of Python"译注18,它对交互式的代码开发模式同样有效。编写函数和类时应尽量注意低耦合和模块化,这样可以使它们更易于测试(如果你编写单元测试的话)、调试和交互式使用。

无惧大文件

如果曾经学过Java(或其他类似的语言),可能会有人告诉你要“尽量保持文件的小型化”。在许多语言中,这都是一个不错的建议。长度太长通常是一种不好的“臭代码”,意味着需要重构或重组。然而在IPython中开发代码时,处理10个小的(但互相关联的)文件(比如都低于100行)可能会让你更为头疼,还不如直接一个大文件或两三个大点的文件来得痛快。更少的文件意味着需要重新加载的模块更少,编辑时需要在各个文件之间的跳转次数也更少。我发现维护更大的(具有高内聚度的)模块会更实用也更具有Python特点。在解决完问题之后,有时将大文件拆分成小文件会更好。

显然,我并不建议将此原则极端化,那可能会让你将所有代码都放到一个巨大的文件里面。对一个大型代码库而言,要找到一种合乎逻辑的模块/包架构需要花点工夫,但这对团队工作非常重要。每个模块都应该具有足够高的内聚度,而且要能足够直观地找到对应各种功能的函数和类。

注1:由于一个模块或包可能会在一个程序中的不同位置多次引入,所以Python会在第一次引入这些模块时对其进行缓存,而不是每次都执行模块中的代码。否则,应用程序的模块化和良好的代码组织等手段就达不到高效的目的了。

译注18:这是Tim Peters 2004年写的一首“诗”,执行"import this"就能看到。有网民将其翻译成三字经的形式(又名“蛇宗三字经”)。另外,有兴趣的话,可以看看this的源代码。