您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息
免费发信息
三六零分类信息网 > 镇江分类信息网,免费分类信息发布

Python程序员最常犯的10个错误,你中招了吗?

2024/2/7 0:39:30发布16次查看
大数据文摘作品
编译:什锦甜、gao ning、小鱼
python简介
python是一种具有动态语义的、面向对象的解释型高级编程语言。因其内置了高级数据结构,并支持动态类型和动态绑定,使用python进行快速应用程序开发十分便利。同时作为一门脚本语言,它兼容部分现有的组件和服务。python还支持模块和各种库的扩展,有助于实现模块化编程和提高代码复用率。
关于本文
刚接触这门语言的新手可能会对python简洁灵活的语法有些不适应,或是低估了python强大的性能。鉴于此,本文列出了python开发人员常犯的10个小错误,资深程序猿也难免会中招哦。
本文供python高级开发人员参考,python小白可以参考下面这篇文章:

常见错误1:滥用表达式作为函数参数的默认值
python允许开发者指定函数参数的默认值,这也是python的一大特色,但当默认值可变时,可能会给开发者带来一些困扰。例如下面定义的函数:
>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified... bar.append(baz) # but this line could be problematic, as we'll see...... return bar
看出bug了吗?那就是在每次调用函数前没有对可变参数进行赋值,而认为该参数就是默认值。比如上面的代码,有人可能期望在反复调用foo()时返回'baz',以为每次调用foo()时,bar的值都为[],即一个空列表。
但是,让我们来看看代码运行结果:
>>> foo()[baz]>>> foo()[baz, baz]>>> foo()[baz, baz, baz]
嗯?为什么每次调用foo()后会不断把baz添加到已有的列表,而不是新建一个新列表呢?答案就是,函数参数的默认值仅在定义函数时执行一次。因此,仅在第一次定义foo()时,bar初始化为默认值(即空列表),此后,每次调用foo()函数时,参数bar都是第一次初始化时生成的列表。
常见的解决方案:
>>> def foo(bar=none):... if bar is none: # or if not bar:... bar = []... bar.append(baz)... return bar...>>> foo()[baz]>>> foo()[baz]>>>foo()[baz]
常见错误2:错误地使用类变量
代码示例:
>>> class a(object):... x = 1...>>> class b(a):... pass...>>> class c(a):... pass...>>> print a.x, b.x, c.x1 1 1
运行结果没问题。
>>> b.x = 2>>> print a.x, b.x, c.x1 2 1
结果也正确。
>>> a.x = 3>>> print a.x, b.x, c.x3 2 3
什么鬼?我们只改变了a.x.,为什么c.x 也变了?
在python中,类变量是以字典形式进行内部处理,遵循方法解析顺序(method resolution order ,mro)。因此,在上述代码中,因为在类c中没有找到属性x,它就会从父类中查找x的值(尽管python支持多重继承,但上述代码只存在一个父类a)。换句话说,c没有独立于类a的属于自己的x。因此,c.x实际上指的是a.x。除非处理得当,否则就会导致python出现错误。
如果想更深入了解python的类特性,请戳:
#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value
在使用列表时,python程序员更容易掉入此类陷阱,例如:
>>> lst = [1, 2, 3]>>> def foo1():... lst.append(5) # this works ok......>>> foo1()>>> lst[1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2():... lst += [5] # ... but this bombs!...>>> foo2()traceback (most recent call last):file , line 1, in file , line 2, in foounboundlocalerror: local variable 'lst' referenced before assignment
奇怪,为什么foo1正常运行,而foo2崩溃了呢?
原因和上一个案例中出现的问题相似,但这里的错误更加细微。函数foo1没有对变量lst进行赋值操作,而函数foo2有赋值操作。
首先, lst += [5]是lst = lst + [5]的缩写形式,在函数foo2中试图对变量lst进行赋值操作(python将变量lst默认为本地作用域的变量)。但是,lst += [5]语句是对lst变量自身进行的赋值操作(此时变量lst的作用域是函数foo2),但是在函数foo2中还未声明该变量,所以就报错啦!
常见错误5:在遍历列表时修改列表
下面代码中的错误很明显:
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> for i in range(len(numbers)):... if odd(numbers[i]):... del numbers[i] # bad: deleting item from a list while iterating over it...traceback (most recent call last):file , line 2, in indexerror: list index out of range
有经验的程序员都知道,在python中遍历列表或数组时不应该删除该列表(数组)中的元素。虽然上面代码的错误很明显,但是在编写复杂代码时,资深程序员也难免会犯此类错误。
幸好python集成了大量经典的编程范式,如果运用得当,可以大大简化代码并提高编程效率。简单的代码会降低出现上述bug的几率。列表解析式(list comprehensions)就是利器之一,它将完美避开上述bug,解决方案如下:
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all>>> numbers[0, 2, 4, 6, 8]
更多有关列表解析式的详细内容,请戳:#tut-listcomps
常见错误6:不理解python闭包中的变量绑定
代码示例:
>>> def create_multipliers():... return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():... print multiplier(2)...
你以为运行结果会是:
02468
但实际输出结果是:8
8888
惊不惊喜!
这种情况是由于python延迟绑定(late binding)机制造成的,也就是说只有在内部函数被调用时才会搜索闭包中变量的值。所以在上述代码中,每次调用create_multipliers()函数中的return函数时,会在附近作用域中查询变量i的值。(此时,return中循环已结束,所以i值为4)。
常见解决方案:
>>> def create_multipliers():... return [lambda x, i=i : i * x for i in range(5)]...>>> for multiplier in create_multipliers():... print multiplier(2)...02468
没错!我们利用了匿名函数lambda的默认参数来生成结果序列。有人觉得这种用法很简洁,有人会说它很巧妙,还有人会觉得晦涩难懂。如果你是python开发人员,那么深刻理解上述语法对你而言非常重要。
常见错误7:模块之间出现循环依赖
假设你有两个文件,分别是a.py和b.py,两者相互导入,如下所示:
a.py模块中的代码:
import bdef f():return b.xprint f()
b.py模块中的代码:
import ax = 1def g():print a.f()
首先,我们尝试导入a.py:
>>> import a1
运行结果正确!这似乎有点出人意料,因为我们在这里进行循环导入,应该会报错呀!
答案是,在python中如果仅存在一个循环导入,程序不会报错。如果一个模块已经被导入,python会自动识别而不会再次导入。但是如果每个模块试图访问其他模块不同位置的函数或变量时,那么error又双叒叕出现了。
回到上面的示例中,当导入a.py模块时,程序可以正常导入b.py模块,因为此时b.py模块未访问a.py中定义任何的变量或函数。b.py模块仅引用了a.py模中的a.f()函数。调用的a.f()函数隶属于g()函数,而a.py或b.py模块中并没有调用g()函数。所以程序没有报错。
但是,如果我们在未导入a.py模块之前先导入b.py模块,结果会怎样?
>>> import btraceback (most recent call last):file , line 1, in file b.py, line 1, in import a file a.py, line 6, in print f() file a.py, line 4, in f return b.xattributeerror: 'module' object has no attribute 'x'
报错了!问题在于,在导入b.py的过程中,它试图导入a.py模块,而a.py模块会调用f()函数,f()函数又试图访问b.x变量。但此时,还未对变量b.x进行定义,所以出现了attributeerror异常。
稍微修改下b.py,即在g()函数内部导入a.py就可以解决上述问题。
修改后的b.py:
x = 1def g():
import a # this will be evaluated only when g() is calledprint a.f()
现在我们再导入b.py模块,就不会报错啦!
>>> import b>>> b.g()1 # printed a first time since module 'a' calls 'print f()' at the end1 # printed a second time, this one is our call to 'g'
常见错误8:文件命名与python标准库模块的名称冲突
python的优势之一就是其集成了丰富的标准库。正因为如此,稍不留神就会在为自己的文件命名时与python自带标准库模块重名。例如,如果你的代码中有一个名为email.py的模块,恰好就和python标准库中email.py模块重名了。)
上述问题比较复杂。举个例子,在导入模块a的时候,假如该模块a试图导入python标准库中的模块b,但你已经定义了一个同名模块b,模块a会错误导入你自定义的模块b,而不是python标准库中的模块b。这种错误很糟糕,因为程序员很难察觉到是因为命名冲突而导致的。
因此,python程序员要注意避免与python标准库模块的命名冲突。毕竟,修改自己模块的名称比修改标准库的名称要容易的多!当然你也可以写一份python改善建议书(python enhancement proposal,pep)提议修改标准库的名称。
常见错误9:不熟悉python2和python3之间的差异
先来看看foo.py文件中的代码:
import sysdef bar(i):if i == 1: raise keyerror(1) if i == 2: raise valueerror(2)def bad(): e = none try: bar(int(sys.argv[1])) except keyerror as e: print('key error') except valueerror as e: print('value error') print(e)bad()
在python 2中,上述代码运行正常
$ python foo.py 1key error1$ python foo.py 2value error2
但是在python 3中运行时:
$ python3 foo.py 1key errortraceback (most recent call last):file foo.py, line 19, in bad() file foo.py, line 17, in bad print(e)unboundlocalerror: local variable 'e' referenced before assignment
什么情况?原来,在python 3中,在except代码块作用域外无法访问异常对象。(原因是,python 3会将内存堆栈中的循环引用进行保留,直到垃圾回收...
镇江分类信息网,免费分类信息发布

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录