zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Python程序员鲜为人知但你应该知道的17个问题

Python程序员 问题 知道 应该 17 鲜为人知
2023-06-13 09:15:27 时间

一、不要使用可变对象作为函数默认值

复制代码代码如下:
In[1]:defappend_to_list(value,def_list=[]):
  ...:        def_list.append(value)
  ...:        returndef_list
  ...:

In[2]:my_list=append_to_list(1)

In[3]:my_list
Out[3]:[1]

In[4]:my_other_list=append_to_list(2)

In[5]:my_other_list
Out[5]:[1,2]#看到了吧,其实我们本来只想生成[2]但是却把第一次运行的效果页带了进来

In[6]:importtime

In[7]:defreport_arg(my_default=time.time()):
  ...:        print(my_default)
  ...:

In[8]:report_arg()#第一次执行
1399562371.32

In[9]:time.sleep(2)#隔了2秒

In[10]:report_arg()
1399562371.32#时间竟然没有变


这2个例子说明了什么?字典,集合,列表等等对象是不适合作为函数默认值的.因为这个默认值实在函数建立的时候就生成了,每次调用都是用了这个对象的”缓存”.我在上段时间的分享python高级编程也说到了这个问题,这个是实际开发遇到的问题,好好检查你学过的代码,也许只是问题没有暴露

可以这样改:

复制代码代码如下:

defappend_to_list(element,to=None):
   iftoisNone:
       to=[]
   to.append(element)
   returnto

二、生成器不保留迭代过后的结果

复制代码代码如下:In[12]:gen=(iforiinrange(5))

In[13]:2ingen
Out[13]:True

In[14]:3ingen
Out[14]:True

In[15]:1ingen
Out[15]:False#1为什么不在gen里面了?因为调用1->2,这个时候1已经不在迭代器里面了,被按需生成过了

In[20]:gen=(iforiinrange(5))

In[21]:a_list=list(gen)#可以转化成列表,当然a_tuple=tuple(gen)也可以

In[22]:2ina_list
Out[22]:True

In[23]:3ina_list
Out[23]:True

In[24]:1ina_list#就算循环过,值还在
Out[24]:True

三、lambda在闭包中会保存局部变量

复制代码代码如下:
In[29]:my_list=[lambda:iforiinrange(5)]

In[30]:forlinmy_list:
  ....:        print(l())
  ....:
4
4
4
4
4


这个问题还是上面说的python高级编程中说过具体原因.其实就是当我赋值给my_list的时候,lambda表达式就执行了i会循环,直到i=4,i会保留

但是可以用生成器

复制代码代码如下:
In[31]:my_gen=(lambda:nforninrange(5))

In[32]:forlinmy_gen:
  ....:        print(l())
  ....:
0
1
2
3
4


也可以坚持用list:
复制代码代码如下:
In[33]:my_list=[lambdax=i:xforiinrange(5)]#看我给每个lambda表达式赋了默认值

In[34]:forlinmy_list:
  ....:        print(l())
  ....:
0
1
2
3
4


有点不好懂是吧,在看看python的另外一个魔法:
复制代码代码如下:
In[35]:defgroupby(items,size):
  ....:    returnzip(*[iter(items)]*size)
  ....:

In[36]:groupby(range(9),3)
Out[36]:[(0,1,2),(3,4,5),(6,7,8)]


一个分组的函数,看起来很不好懂,对吧?我们来解析下这里
复制代码代码如下:
In[39]:[iter(items)]*3
Out[39]:
[<listiteratorat0x10e155fd0>,
 <listiteratorat0x10e155fd0>,
 <listiteratorat0x10e155fd0>]#看到了吧,其实就是把items变成可迭代的,重复三回(同一个对象哦),但是别忘了,每次都.next(),所以起到了分组的作用
 In[40]:[lambdax=i:xforiinrange(5)]
Out[40]:
[<function__main__.<lambda>>,
 <function__main__.<lambda>>,
 <function__main__.<lambda>>,
 <function__main__.<lambda>>,
 <function__main__.<lambda>>]#看懂了吗?


四、在循环中修改列表项

复制代码代码如下:In[44]:a=[1,2,3,4,5]

In[45]:foriina:
  ....:    ifnoti%2:
  ....:        a.remove(i)
  ....:

In[46]:a
Out[46]:[1,3,5]#没有问题

In[50]:b=[2,4,5,6]

In[51]:foriinb:
  ....:     ifnoti%2:
  ....:         b.remove(i)
  ....:

In[52]:b
Out[52]:[4,5]#本来我想要的结果应该是去除偶数的列表


思考一下,为什么?是因为你对列表的remove,影响了它的index
复制代码代码如下:
In[53]:b=[2,4,5,6]

In[54]:forindex,iteminenumerate(b):
  ....:    print(index,item)
  ....:    ifnotitem%2:
  ....:        b.remove(item)
  ....:
(0,2)#这里没有问题2被删除了
(1,5)#因为2被删除目前的列表是[4,5,6],所以索引list[1]直接去找5,忽略了4
(2,6)

五、IndexError-列表取值超出了他的索引数

复制代码代码如下:
In[55]:my_list=[1,2,3,4,5]

In[56]:my_list[5]#根本没有这个元素
---------------------------------------------------------------------------
IndexError                               Traceback(mostrecentcalllast)
<ipython-input-56-037d00de8360>in<module>()
---->1my_list[5]

IndexError:listindexoutofrange#抛异常了

In[57]:my_list[5:]#但是可以这样,一定要注意,用好了是trick,用错了就是坑啊
Out[57]:[]

六、重用全局变量

复制代码代码如下:
In[58]:defmy_func():
  ....:        print(var)#我可以先调用一个未定义的变量
  ....:

In[59]:var="global"#后赋值

In[60]:my_func()#反正只要调用函数时候变量被定义了就可以了
global

In[61]:defmy_func():
  ....:    var="locallychanged"
  ....:

In[62]:var="global"

In[63]:my_func()

In[64]:print(var)

global#局部变量没有影响到全局变量

In[65]:defmy_func():
  ....:        print(var)#虽然你全局设置这个变量,但是局部变量有同名的,python以为你忘了定义本地变量了
  ....:        var="locallychanged"
  ....:

In[66]:var="global"

In[67]:my_func()
---------------------------------------------------------------------------
UnboundLocalError                        Traceback(mostrecentcalllast)
<ipython-input-67-d82eda95de40>in<module>()
---->1my_func()

<ipython-input-65-0ad11d690936>inmy_func()
     1defmy_func():
---->2        print(var)
     3        var="locallychanged"
     4

UnboundLocalError:localvariable"var"referencedbeforeassignment

In[68]:defmy_func():
  ....:        globalvar#这个时候得加全局了
  ....:        print(var)#这样就能正常使用
  ....:        var="locallychanged"
  ....:

In[69]:var="global"

In[70]:

In[70]:my_func()
global

In[71]:print(var)
locallychanged#但是使用了global就改变了全局变量

七、拷贝可变对象

复制代码代码如下:In[72]:my_list1=[[1,2,3]]*2

In[73]:my_list1
Out[73]:[[1,2,3],[1,2,3]]

In[74]:my_list1[1][0]="a"#我只修改子列表中的一项

In[75]:my_list1
Out[75]:[["a",2,3],["a",2,3]]#但是都影响到了

In[76]:my_list2=[[1,2,3]foriinrange(2)]#用这种循环生成不同对象的方法就不影响了

In[77]:my_list2[1][0]="a"

In[78]:my_list2
Out[78]:[[1,2,3],["a",2,3]]


八、python多继承(C3)

复制代码代码如下:
In[1]:classA(object):
  ...:        deffoo(self):
  ...:                print("classA")
  ...:

In[2]:classB(object):
  ...:        deffoo(self):
  ...:                print("classB")
  ...:

In[3]:classC(A,B):
  ...:        pass
  ...:

In[4]:C().foo()
classA#例子很好懂,C继承了A和B,从左到右,发现A有foo方法,返回了

看起来都是很简单,有次序的从底向上,从前向后找,找到就返回.再看例子:

复制代码代码如下:
In[5]:classA(object):
  ...:       deffoo(self):
  ...:              print("classA")
  ...:

In[6]:classB(A):
  ...:       pass
  ...:

In[7]:classC(A):
  ...:       deffoo(self):
  ...:              print("classC")
  ...:

In[8]:classD(B,C):
  ...:       pass
  ...:

In[9]:D().foo()
classC#?按道理,顺序是D->B->A,为什么找到了C哪去了


这也就涉及了MRO(MethodResolutionOrder):
复制代码代码如下:
In[10]:D.__mro__
Out[10]:(__main__.D,__main__.B,__main__.C,__main__.A,object)
简单的理解其实就是新式类是广度优先了,D->B,但是发现C也是继承A,就先找C,最后再去找A

九、列表的+和+=,append和extend

复制代码代码如下:In[17]:print("ID:",id(a_list))
("ID:",4481323592)

In[18]:a_list+=[1]

In[19]:print("ID(+=):",id(a_list))
("ID(+=):",4481323592)#使用+=还是在原来的列表上操作

In[20]:a_list=a_list+[2]

In[21]:print("ID(list=list+...):",id(a_list))
("ID(list=list+...):",4481293056)#简单的+其实已经改变了原有列表
In[28]:a_list=[]

In[29]:id(a_list)
Out[29]:4481326976

In[30]:a_list.append(1)

In[31]:id(a_list)
Out[31]:4481326976#append是在原有列表添加

In[32]:a_list.extend([2])

In[33]:id(a_list)
Out[33]:4481326976#extend也是在原有列表上添加

十、datetime也有布尔值
这是一个坑

复制代码代码如下:
In[34]:importdatetime

In[35]:print(""datetime.time(0,0,0)"(Midnight)->",bool(datetime.time(0,0,0)))
(""datetime.time(0,0,0)"(Midnight)->",False)

In[36]:print(""datetime.time(1,0,0)"(1am)->",bool(datetime.time(1,0,0)))
(""datetime.time(1,0,0)"(1am)->",True)


十一、"=="和is的区别
我的理解是”is”是判断2个对象的身份,==是判断2个对象的值

复制代码代码如下:
In[37]:a=1

In[38]:b=1

In[39]:print("aisb",bool(aisb))
("aisb",True)

In[40]:c=999

In[41]:d=999

In[42]:print("cisd",bool(cisd))
("cisd",False)#原因是python的内存管理,缓存了-5-256的对象

In[43]:print("256is257-1",256is257-1)
("256is257-1",True)

In[44]:print("257is258-1",257is258-1)
("257is258-1",False)

In[45]:print("-5is-6+1",-5is-6+1)
("-5is-6+1",True)

In[46]:print("-7is-6-1",-7is-6-1)
("-7is-6-1",False)
In[47]:a="helloworld!"

In[48]:b="helloworld!"

In[49]:print("aisb,",aisb)
("aisb,",False)#很明显他们没有被缓存,这是2个字段串的对象

In[50]:print("a==b,",a==b)
("a==b,",True)#但他们的值相同
#But,有个特例
In[51]:a=float("nan")

In[52]:print("aisa,",aisa)
("aisa,",True)

In[53]:print("a==a,",a==a)
("a==a,",False)#亮瞎我眼睛了~


十二、浅拷贝和深拷贝
我们在实际开发中都可以向对某列表的对象做修改,但是可能不希望改动原来的列表.浅拷贝只拷贝父对象,深拷贝还会拷贝对象的内部的子对象

复制代码代码如下:
In[65]:list1=[1,2]

In[66]:list2=list1#就是个引用,你操作list2,其实list1的结果也会变

In[67]:list3=list1[:]

In[69]:importcopy

In[70]:list4=copy.copy(list1)#他和list3一样都是浅拷贝

In[71]:id(list1),id(list2),id(list3),id(list4)
Out[71]:(4480620232,4480620232,4479667880,4494894720)

In[72]:list2[0]=3

In[73]:print("list1:",list1)
("list1:",[3,2])

In[74]:list3[0]=4

In[75]:list4[1]=4

In[76]:print("list1:",list1)
("list1:",[3,2])#对list3和list4操作都没有对list1有影响

#再看看深拷贝和浅拷贝的区别

In[88]:fromcopyimportcopy,deepcopy

In[89]:list1=[[1],[2]]

In[90]:list2=copy(list1)#还是浅拷贝

In[91]:list3=deepcopy(list1)#深拷贝

In[92]:id(list1),id(list2),id(list3)
Out[92]:(4494896592,4495349160,4494896088)

In[93]:list2[0][0]=3

In[94]:print("list1:",list1)
("list1:",[[3],[2]])#看到了吧假如你操作其子对象还是和引用一样影响了源

In[95]:list3[0][0]=5

In[96]:print("list1:",list1)
("list1:",[[3],[2]])#深拷贝就不会影响

十三、bool其实是int的子类

复制代码代码如下:
In[97]:isinstance(True,int)
Out[97]:True

In[98]:True+True
Out[98]:2

In[99]:3*True+True
Out[99]:4

In[100]:3*True-False
Out[100]:3

In[104]:True<<10
Out[104]:1024

十五、元组是不是真的不可变?

复制代码代码如下:In[111]:tup=([],)

In[112]:tup[0]+=[1]
---------------------------------------------------------------------------
TypeError                                Traceback(mostrecentcalllast)
<ipython-input-112-d4f292cf35de>in<module>()
---->1tup[0]+=[1]

TypeError:"tuple"objectdoesnotsupportitemassignment

In[113]:tup
Out[113]:([1],)#我靠又是亮瞎我眼睛,明明抛了异常还能修改?

In[114]:tup=([],)

In[115]:tup[0].extend([1])

In[116]:tup[0]
Out[116]:[1]#好吧,我有点看明白了,虽然我不能直接操作元组,但是不能阻止我操作元组中可变的子对象(list)

这里有个不错的解释Python"s+=IsWeird,PartII:

复制代码代码如下:
In[117]:my_tup=(1,)

In[118]:my_tup+=(4,)

In[119]:my_tup=my_tup+(5,)

In[120]:my_tup
Out[120]:(1,4,5)#?嗯不是不能操作元组嘛?

In[121]:my_tup=(1,)

In[122]:print(id(my_tup))
4481317904

In[123]:my_tup+=(4,)

In[124]:print(id(my_tup))
4480606864#操作的不是原来的元组所以可以

In[125]:my_tup=my_tup+(5,)

In[126]:print(id(my_tup))
4474234912

十六、python没有私有方法/变量?但是可以有”伪”的

复制代码代码如下:
In[127]:classmy_class(object^E):
  .....:    defpublic_method(self):
  .....:        print("Hellopublicworld!")
  .....:    def__private_method(self):#私有以双下划线开头
  .....:        print("Helloprivateworld!")
  .....:    defcall_private_method_in_class(self):
  .....:        self.__private_method()

In[132]:my_instance=my_class()

In[133]:my_instance.public_method()
Hellopublicworld!#普通方法

In[134]:my_instance._my_class__private_method()
Helloprivateworld!#私有的可以加"_+类名字+私有方法名字”

In[135]:my_instance.call_private_method_in_class()
Helloprivateworld!#还可以通过类提供的公有接口内部访问

In[136]:my_instance._my_class__private_variable
Out[136]:1

十七、异常处理加else

复制代码代码如下:
In[150]:try:
  .....:    print("thirdelement:",a_list[2])
  .....:exceptIndexError:
  .....:    print("raisedIndexError")
  .....:else:
  .....:    print("noerrorintry-block")#只有在try里面没有异常的时候才会执行else里面的表达式
  .....:
raisedIndexError#抛异常了没完全完成
In[153]:i=0

In[154]:whilei<2:
  .....:    print(i)
  .....:    i+=1
  .....:else:
  .....:    print("inelse")
  .....:
0
1
inelse#while也支持哦~
In[155]:i=0

In[156]:whilei<2:
  .....:        print(i)
  .....:        i+=1
  .....:        break
  .....:else:
  .....:        print("completedwhile-loop")
  .....:
0#被break了没有完全执行完就不执行else里面的了
In[158]:foriinrange(2):
  .....:        print(i)
  .....:else:
  .....:        print("completedfor-loop")
  .....:
0
1
completedfor-loop

In[159]:foriinrange(2):
  .....:        print(i)
  .....:        break
  .....:else:
  .....:        print("completedfor-loop")
  .....:
0#也是因为break了