常见错误 #7: 创建循环依赖模块

让我们假设你有两个文件,a.py 和 b.py,他们之间相互引用,如下所示:

a.py:

  1. import b  
  2.  
  3. def f():  
  4.     return b.x  
  5.       
  6. print f()  

b.py:

  1. import a  
  2.  
  3. x = 1 
  4.  
  5. def g():  
  6.     print a.f()  

首先,让我们尝试引入 a.py:

  1. >>> import a  
  2. 1 

可以正常工作。这也许是你感到很奇怪。毕竟,我们确实在这里引入了一个循环依赖的模块,我们推测这样会出问题的,不是吗?

答案就是在Python中,仅仅引入一个循环依赖的模块是没有问题的。如果一个模块已经被引入了,Python并不会去再次引入它。但是,根据每个模块要访问其他模块中的函数和变量位置的不同,就很可能会遇到问题。

所以,回到我们这个例子,当我们引入 a.py 时,再引入 b.py 不会产生任何问题,因为当引入的时候,b.py 不需要 a.py 中定义任何东西。b.py 中唯一引用 a.py 中的东西是调用 a.f()。 但是那个调用是发生在g() 中的,并且 a.py 和 b.py 中都没有调用 g()。所以运行正常。

但是,如果我们尝试去引入b.py 会发生什么呢?在这之前不引入a.py),如下所示:

  1. >>> import b  
  2. Traceback (most recent call last):  
  3.         File "<stdin>", line 1in <module>  
  4.         File "b.py", line 1in <module>  
  5.     import a  
  6.         File "a.py", line 6in <module>  
  7.     print f()  
  8.         File "a.py", line 4in f  
  9.     return b.x  
  10. AttributeError: 'module' object has no attribute 'x' 

啊哦。 出问题了!此处的问题是,在引入b.py的过程中,Python尝试去引入 a.py,但是a.py 要调用f(),而f() 有尝试去访问 b.x。但是此时 b.x 还没有被定义呢。所以发生了 AttributeError 异常。

至少,解决这个问题很简单,只需修改b.py,使其在g()中引入 a.py:

  1. x = 1 
  2.  
  3. def g():  
  4.     import a    # 只有当g()被调用的时候才会引入a  
  5.     print a.f()  

现在,当我们再引入b,没有任何问题:

  1. >>> import b  
  2. >>> b.g()  
  3. 1    # Printed a first time since module 'a' calls 'print f()' at the end  
  4. 1    # Printed a second time, this one is our call to 'g'  

常见错误 #8: 与Python标准库中的模块命名冲突
 

Python一个令人称赞的地方是它有丰富的模块可供我们“开箱即用”。但是,如果你没有有意识的注意的话,就很容易出现你写的模块和Python自带的标准库的模块之间发生命名冲突的问题如,你也许有一个叫 email.py 的模块,但这会和标准库中的同名模块冲突)。

这可能会导致很怪的问题,例如,你引入了另一个模块,但这个模块要引入一个Python标准库中的模块,由于你定义了一个同名的模块,就会使该模块错误的引入了你的模块,而不是 stdlib 中的模块。这就会出问题了。

因此,我们必须要注意这个问题,以避免使用和Python标准库中相同的模块名。修改你包中的模块名要比通过 Python Enhancement Proposal (PEP) 给Python提建议来修改标准库的模块名容易多了。

常见错误 #9: 未能解决Python 2和Python 3之间的差异

请看下面这个 filefoo.py:

  1. import sys  
  2.  
  3. def bar(i):  
  4.     if i == 1:  
  5.         raise KeyError(1)  
  6.     if i == 2:  
  7.         raise ValueError(2)  
  8.  
  9. def bad():  
  10.     e = None 
  11.     try:  
  12.         bar(int(sys.argv[1]))  
  13.     except KeyError as e:  
  14.         print('key error')  
  15.     except ValueError as e:  
  16.         print('value error')  
  17.     print(e)  
  18.  
  19. bad()  

在Python 2中运行正常:

  1. $ python foo.py 1 
  2. key error  
  3. 1 
  4. $ python foo.py 2 
  5. value error  
  6. 2 

但是,现在让我们把它在Python 3中运行一下:

  1. $ python3 foo.py 1 
  2. key error  
  3. Traceback (most recent call last):  
  4.   File "foo.py", line 19in <module>  
  5.     bad()  
  6.   File "foo.py", line 17in bad  
  7.     print(e)  
  8. UnboundLocalError: local variable 'e' referenced before assignment  

出什么问题了? “问题”就是,在 Python 3 中,异常的对象在 except 代码块之外是不可见的。这样做的原因是,它将保存一个对内存中堆栈帧的引用周期,直到垃圾回收器运行并且从内存中清除掉引用。了解更多技术细节请参考这里) 。

一种解决办法是在 except 代码块的外部作用域中定义一个对异常对象的引用,以便访问。下面的例子使用了该方法,因此最后的代码可以在Python 2 和 Python 3中运行良好。

  1. import sys  
  2. def bar(i):  
  3.     if i == 1:  
  4.         raise KeyError(1)  
  5.     if i == 2:  
  6.         raise ValueError(2)  
  7. def good():  
  8.     exception = None 
  9.     try:  
  10.         bar(int(sys.argv[1]))  
  11.     except KeyError as e:  
  12.         exception = e  
  13.         print('key error')  
  14.     except ValueError as e:  
  15.         exception = e  
  16.         print('value error')  
  17.     print(exception)  
  18.  
  19. good()  

在Py3k中运行:

  1. $ python3 foo.py 1 
  2. key error  
  3. 1 
  4. $ python3 foo.py 2 
  5. value error  
  6. 2 

正常!

(顺便提一下, 我们的 Python Hiring Guide 讨论了当我们把代码从Python 2 迁移到 Python 3时的其他一些需要知道的重要差异。)

常见错误 #10: 误用__del__方法

假设你有一个名为 calledmod.py 的文件:

  1. import foo  
  2.  
  3. class Bar(object):  
  4.            ...  
  5.     def __del__(self):  
  6.         foo.cleanup(self.myhandle)  

并且有一个名为 another_mod.py 的文件:

  1. import mod  
  2. mybar = mod.Bar()  

你会得到一个 AttributeError 的异常。

为什么呢?因为,正如这里所说,当解释器退出的时候,模块中的全局变量都被设置成了 None。所以,在上面这个例子中,当 __del__ 被调用时,foo 已经被设置成了None。

解决方法是使用 atexit.register() 代替。用这种方式,当你的程序结束执行时意思是正常退出),你注册的处理程序会在解释器退出之前执行。

了解了这些,我们可以将上面 mod.py 的代码修改成下面的这样:

  1. import foo  
  2. import atexit  
  3.  
  4. def cleanup(handle):  
  5.     foo.cleanup(handle)  
  6.  
  7.  
  8. class Bar(object):  
  9.     def __init__(self):  
  10.         ...  
  11.         atexit.register(cleanup, self.myhandle)  

这种实现方式提供了一个整洁并且可信赖的方法用来在程序退出之前做一些清理工作。很显然,它是由foo.cleanup 来决定对绑定在 self.myhandle 上对象做些什么处理工作的,但是这就是你想要的。

总结

Python是一门强大的并且很灵活的语言,它有很多机制和语言规范来显著的提高你的生产力。和其他任何一门语言或软件一样,如果对它能力的了解有限,这很可能会给你带来阻碍,而不是好处。正如一句谚语所说的那样 “knowing enough to be dangerous”译者注:意思是自以为已经了解足够了,可以做某事了,但其实不是)。

熟悉Python的一些关键的细微之处,像本文中所提到的那些但不限于这些),可以帮助我们更好的去使用语言,从而避免一些常见的陷阱。

你可以查看“Python 面试官指南” 来获得一些关于如何辨别一个开发者是否是Python专家的建议。

我们希望你在这篇文章中找到了一些对你有帮助的东西,并希望你得到你的反馈。

英文原文:Top 10 Mistakes that Python Programmers Make

译文链接:http://www.oschina.net/translate/top-10-mistakes-that-python-programmers-make


评论关闭