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的博文2以及对应的译文3

• 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陷阱与缺陷列表4。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)

待更新。。。

本文版权归作者AmyZhuang(博文地址: https://www.cnblogs.com/weixia14/) 所有,欢迎转载和商用,请在文章页面明显位置给出原文链接并保留此段声明,否则保留追究法律责任的权利,其他事项,可留言咨询。

参考


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

  2. Python's globals

  3. Python's globals译文

  4. 程序员必知的Python陷阱与缺陷列表

猜你喜欢

转载自www.cnblogs.com/weixia14/p/11374754.html