Python陷阱

本文整理了一些平常使用python时碰到的陷阱或错误

全局变量v.s.局部变量

global定义位置

global a
a=1
def func():
    a+=1
    print(a)

func()
a+=1
print(a)

File "XXX", line 9, in func()
File "XXX", line 6, in func a+=1
UnboundLocalError: local variable 'a' referenced before assignment

原因:当已经在函数体外的变量已经被定义为全局变量后,在函数体内又被重新赋值一遍,这时的变量在函数体内又被定义为局部变量,只在函数体内才会有效。在函数体外的时候恢复之前未在函数体内定义的状态。[1]

修改

a=1
def func():
    global a
    a+=1
    print(a)

func()
a+=1
print(a)

输出:
2
3

对比

global a
a=1
def func():
    b=a+5
    print(b)

func()
a+=1
print(a)

输出:
6
2

  ## 跨模块使用global 跨模块使用global会更容易带来复杂且难以理解的陷阱。该节参考的是punchagan的博文[^punchagan]以及对应的译文[^punchagan译文]。
• Python 的全局变量是模块 (module) 级别的 • 每个python函数拥有对应的`__globals__`字典,该字典与函数所属模块的`__dict__`字典完全相同。函数的全局变量也会从这个字典中获取。 • 避免全局变量将使得程序更容易被调试,同时也能提升程序的可读性
模块1--foo.py|模块2--bar.py -|-

def f():
  print(a)
def main():
  global a
  a = 5
  f()
 main()

|

from foo import f
 def main():
  global a
  a=4
  f()
 main()

运行--foo.py|运行--bar.py 输出正常结果5|报错:NameError
>File "XXX", line 13, in main() >File "XXX", line 9, in main f() >File "XXX", line 5, in f print(a) >NameError: global name 'a' is not defined

这是因为a被定义在foo.pymain()函数中,而当导入f()函数时,foo.pymain函数并未被运行,所以a也没有被定义。

反汇编f()

import dis
from foo import f

dis.dis(f)

输出:(f()函数的字节码)

行号 - -
2 0 LOAD_GLOBAL 0 (print)
- 2 LOAD_GLOBAL 1 (a)
- 4 CALL_FUNCTION 1
- 6 POP_TOP -
- 8 LOAD_CONST 0 (None)
- 10 RETURN_VALUE -

从反汇编可以看出变量a被认为是全局变量。Python中的每一个函数都拥有一个__globals__字典变量,该变量实际是函数所属模块的__dict__变量的引用。所以在bar.py中我们想在bar.main函数中将全局变量a赋值为4,实际改变的是bar.py__dict__字典变量 (注:而不是定义ffoo.py__dict__字典变量)

mutable对象

用mutable对象作默认参数

本节参考博文:程序员必知的Python陷阱与缺陷列表[2]。Python和其他很多语言一样,提供了默认参数,默认参数确实是个好东西,可以让函数调用者忽略一些细节(比如GUI编程,Tkinter,QT),对于lambda表达式也非常有用。但是如果使用了可变对象作为默认参数,那么事情就不那么愉快了。
这个问题是我在做LeetCode的TwoSum时候发现的。我采用一遍哈希表+尾递归的解法,发现总是思路没问题但结果一直出错(具体解题思路可看我的另一篇博文Two Sum):

One-pass Hash Table + tail recursion

class Solution(object):
    def twoSum(self, nums, target,i=0,dict={}):
        if dict.get(target-nums[i]) is not None:
            return dict.get(target - nums[i]),i
        else:
            dict[nums[i]] = i
            i+=1
            return self.twoSum(nums,target,i,dict)
if __name__ == '__main__':
    nums = [3,3]
    target = 6
    sol=Solution()
    ans=sol.twoSum(nums,target)
    print(ans)
    ans2=sol.twoSum(nums,target)
    print(ans2)

输出:
[0,1]
[0,0]

一开始以为是形参和实参的问题,后来男票提醒才知道是mutable对象的问题。python中一切都是对象,函数也不列外,默认参数只是函数的一个属性。而默认参数在函数定义的时候已经赋值了。

Default parameter values are evaluated when the function definition is executed.

stackoverflow上有一个更适当的例子来说明默认参数是在定义的时候赋值,而不是调用的时候。

stackoverflow example

import time
def report(when=time.time()):
    return when
report()
report()

输出:

1500113234.487932
1500113234.487932

python docoment 给出了标准的解决办法:

A way around this is to use None as the default, and explicitly test for it in the body of the function

mutable object solution

import time
def report(when=None):
    if when is None:
        when = time.time()
    return when
report()
report()

输出:

1500113234.487932
1500113448.552873

  ## mutable对象和(x+=y v.s. x=x+y) 待更新。。。

Python的float除法和整除法

不同版本的python对整数/整数的处理不同:
教老版本的python:整数/整数---整除法
较新版本的python:整数/整数---float除法

最保险的做法应明确告诉编译器你想要的结果,即:
整除法:用//,e.g.数//数
float除法:用/且除数与被除数中有一个数是浮点数,e.g.float/数数/float

参考


  1. Python中局部变量与全局变量的解释 ↩︎

  2. 程序员必知的Python陷阱与缺陷列表 ↩︎

posted @ 2019-08-19 00:29  维夏十四  阅读(197)  评论(0编辑  收藏  举报