学堂 学堂 学堂公众号手机端

Python开发使用中有哪些要注意的,正确的用法是什么

lewis 6年前 (2019-10-30) 阅读数 5 #技术
这篇文章主要介绍“Python开发使用中有哪些要注意的,正确的用法是什么”,有一些人在Python开发使用中有哪些要注意的,正确的用法是什么的问题上存在疑惑,接下来小编就给大家来介绍一下相关的内容,希望对大家解答有帮助,有这个方面学习需要的朋友就继续往下看吧。


最近使用Python的过程中遇到了一些坑,例如用datetime.datetime.now()这个可变对象作为函数的默认参数,模块循环依赖等等。

在此记录一下,方便以后查询和补充。


避免可变对象作为默认参数

在使用函数的过程中,经常会涉及默认参数。在Python中,当使用可变对象作为默认参数的时候,就可能产生非预期的结果。

下面看一个例子:

defappend_item(a=1,b=[]):
b.append(a)
printb

append_item(a=1)
append_item(a=3)
append_item(a=5)

结果为:

[1]
[1,3]
[1,3,5]

从结果中可以看到,当后面两次调用append_item函数的时候,函数参数b并没有被初始化为[],而是保持了前面函数调用的值。

之所以得到这个结果,是因为在Python中,一个函数参数的默认值,仅仅在该函数定义的时候,被初始化一次。

下面看一个例子证明Python的这个特性:

classTest(object):
def__init__(self):
print("InitTest")

defarg_init(a,b=Test()):
print(a)
arg_init(1)
arg_init(3)
arg_init(5)

结果为:

InitTest
1
3
5

从这个例子的结果就可以看到,Test类仅仅被实例化了一次,也就是说默认参数跟函数调用次数无关,仅仅在函数定义的时候被初始化一次。

可变默认参数的正确使用

对于可变的默认参数,我们可以使用下面的模式来避免上面的非预期结果:

defappend_item(a=1,b=None):
ifbisNone:
b=[]
b.append(a)
printb

append_item(a=1)
append_item(a=3)
append_item(a=5)

结果为:

[1]
[3]
[5]

Python中的作用域

Python的作用域解析顺序为Local、Enclosing、Global、Built-in,也就是说Python解释器会根据这个顺序解析变量。


看一个简单的例子:

global_var=0
defouter_func():
outer_var=1

definner_func():
inner_var=2

print"global_varis:",global_var
print"outer_varis:",outer_var
print"inner_varis:",inner_var

inner_func()

outer_func()

结果为:

global_varis:0
outer_varis:1
inner_varis:2

在Python中,关于作用域有一点需要注意的是,在一个作用域里面给一个变量赋值的时候,Python会认为这个变量是当前作用域的本地变量。


对于这一点也是比较容易理解的,对于下面代码var_func中给num变量进行了赋值,所以此处的num就是var_func作用域的本地变量。

num=0
defvar_func():
num=1
print"numis:",num

var_func()

问题一

但是,当我们通过下面的方式使用变量的时候,就会产生问题了:

num=0
defvar_func():
print"numis:",num
num=1

var_func()

结果如下:

UnboundLocalError:localvariable'num'referencedbeforeassignment

之所以产生这个错误,就是因为我们在var_func中给num变量进行了赋值,所以Python解释器会认为num是var_func作用域的本地变量,但是当代码执行到print "num is :", num语句的时候,num还是未定义。


问题二

上面的错误还是比较明显的,还有一种比较隐蔽的错误形式如下:

li=[1,2,3]
deffoo():
li.append(4)
printli
foo()
defbar():
li+=[5]
printli
bar()

代码的结果为:

[1,2,3,4]
UnboundLocalError:localvariable'li'referencedbeforeassignment

在foo函数中,根据Python的作用域解析顺序,该函数中使用了全局的li变量;但是在bar函数中,对li变量进行了赋值,所以li会被当作bar作用域中的变量。


对于bar函数的这个问题,可以通过global关键字。

li=[1,2,3]
deffoo():
li.append(4)
printli

foo()
defbar():
globalli
li+=[5]
printli

bar()

类属性隐藏

在Python中,有类属性和实例属性。类属性是属于类本身的,被所有的类实例共享。

类属性可以通过类名访问和修改,也可以通过类实例进行访问和修改。但是,当实例定义了跟类同名的属性后,类属性就被隐藏了。


看下面这个例子:

classStudent(object):
books=["Python","JavaScript","CSS"]
def__init__(self,name,age):
self.name=name
self.age=age
pass

wilber=Student("Wilber",27)
print"%sis%dyearsold"%(wilber.name,wilber.age)
printStudent.books
printwilber.books
wilber.books=["HTML","AngularJS"]
printStudent.books
printwilber.books
delwilber.books
printStudent.books
printwilber.books

代码的结果如下,起初wilber实例可以直接访问类的books属性,但是当实例wilber定义了名称为books的实例属性之后,wilber实例的books属性就“隐藏”了类的books属性;当删除了wilber实例的books属性之后,wilber.books就又对应类的books属性了。

Wilberis27yearsold
['Python','JavaScript','CSS']
['Python','JavaScript','CSS']
['Python','JavaScript','CSS']
['HTML','AngularJS']
['Python','JavaScript','CSS']
['Python','JavaScript','CSS']

当在Python值使用继承的时候,也要注意类属性的隐藏。对于一个类,可以通过类的__dict__属性来查看所有的类属性。


当通过类名来访问一个类属性的时候,会首先查找类的__dict__属性,如果没有找到类属性,就会继续查找父类。但是,如果子类定义了跟父类同名的类属性后,子类的类属性就会隐藏父类的类属性。


看一个例子:

classA(object):
count=1

classB(A):
pass

classC(A):
pass

printA.count,B.count,C.count
B.count=2
printA.count,B.count,C.count
A.count=3
printA.count,B.count,C.count
printB.__dict__
printC.__dict__

结果如下,当类B定义了count这个类属性之后,就会隐藏父类的count属性:

111
121
323
{'count':2,'__module__':'__main__','__doc__':None}
{'__module__':'__main__','__doc__':None}

tuple是“可变的”

在Python中,tuple是不可变对象,但是这里的不可变指的是tuple这个容器总的元素不可变(确切的说是元素的id),但是元素的值是可以改变的。

tpl=(1,2,3,[4,5,6])
printid(tpl)
printid(tpl[3])
tpl[3].extend([7,8])
printtpl
printid(tpl)
printid(tpl[3])

代码结果如下,对于tpl对象,它的每个元素都是不可变的,但是tpl[3]是一个list对象。也就是说,对于这个tpl对象,id(tpl[3])是不可变的,但是tpl[3]确是可变的。

36764576
38639896
(1,2,3,[4,5,6,7,8])
36764576
38639896

Python的深浅拷贝

在对Python对象进行赋值的操作中,一定要注意对象的深浅拷贝,一不小心就可能踩坑了。


当使用下面的操作的时候,会产生浅拷贝的效果:


使用切片[:]操作

使用工厂函数(如list/dir/set)

使用copy模块中的copy()函数

使用copy模块里面的浅拷贝函数copy():

importcopy
will=["Will",28,["Python","C#","JavaScript"]]
wilber=copy.copy(will)
printid(will)
printwill
print[id(ele)foreleinwill]
printid(wilber)
printwilber
print[id(ele)foreleinwilber]
will[0]="Wilber"
will[2].append("CSS")
printid(will)
printwill
print[id(ele)foreleinwill]
printid(wilber)
printwilber
print[id(ele)foreleinwilber]

使用copy模块里面的深拷贝函数deepcopy():

importcopy
will=["Will",28,["Python","C#","JavaScript"]]
wilber=copy.deepcopy(will)
printid(will)
printwill
print[id(ele)foreleinwill]
printid(wilber)
printwilber
print[id(ele)foreleinwilber]
will[0]="Wilber"
will[2].append("CSS")
printid(will)
printwill
print[id(ele)foreleinwill]
printid(wilber)
printwilber
print[id(ele)foreleinwilber]

模块循环依赖

在Python中使用import导入模块的时候,有的时候会产生模块循环依赖,例如下面的例子,module_x模块和module_y模块相互依赖,运行module_y.py的时候就会产生错误。

#module_x.py
importmodule_y

definc_count():
module_y.count+=1
printmodule_y.count


#module_y.py
importmodule_x
count=10
defrun():
module_x.inc_count()

run()


其实,在编码的过程中就应当避免循环依赖的情况,或者代码重构的过程中消除循环依赖。


当然,上面的问题也是可以解决的,常用的解决办法就是把引用关系搞清楚,让某个模块在真正需要的时候再导入(一般放到函数里面)。


对于上面的例子,就可以把module_x.py修改为如下形式,在函数内部导入module_y:

#module_x.py
definc_count():
importmodule_y
module_y.count+=1

现在大家对于Python开发使用中有哪些要注意的,正确的用法是什么的内容应该都有一定的认识了吧,希望这篇能对大家有所帮助。最后,想要了解更多,欢迎关注博信,博信将为大家推送更多相关的文章。
版权声明

本文仅代表作者观点,不代表博信信息网立场。

热门