当前位置: 移动技术网 > IT编程>脚本编程>Python > Python程序员鲜为人知但你应该知道的17个问题

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

2019年03月30日  | 移动技术网IT编程  | 我要评论

人民搜索,今日美元兑人民币汇率,朔州汽车站

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

复制代码 代码如下:
in [1]: def append_to_list(value, def_list=[]):
   ...:         def_list.append(value)
   ...:         return def_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]: import time

in [7]: def report_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高级编程也说到了这个问题,这个是实际开发遇到的问题,好好检查你学过的代码, 也许只是问题没有暴露

可以这样改:

复制代码 代码如下:

def append_to_list(element, to=none):
    if to is none:
        to = []
    to.append(element)
    return to

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

复制代码 代码如下:
in [12]: gen = (i for i in range(5))

in [13]: 2 in gen
out[13]: true

in [14]: 3 in gen
out[14]: true

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

in [20]: gen = (i for i in range(5))

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

in [22]: 2 in a_list
out[22]: true

in [23]: 3 in a_list
out[23]: true

in [24]: 1 in a_list # 就算循环过,值还在
out[24]: true

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

复制代码 代码如下:

in [29]: my_list = [lambda: i for i in range(5)]

in [30]: for l in my_list:
   ....:         print(l())
   ....:
4
4
4
4
4


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

但是可以用生成器

复制代码 代码如下:

in [31]: my_gen = (lambda: n for n in range(5))

in [32]: for l in my_gen:
   ....:         print(l())
   ....:
0
1
2
3
4


也可以坚持用list:
复制代码 代码如下:

in [33]: my_list = [lambda x=i: x for i in range(5)] # 看我给每个lambda表达式赋了默认值

in [34]: for l in my_list:
   ....:         print(l())
   ....:
0
1
2
3
4


有点不好懂是吧,在看看python的另外一个魔法:
复制代码 代码如下:

in [35]: def groupby(items, size):
   ....:     return zip(*[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]:
[<listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>,
 <listiterator at 0x10e155fd0>] # 看到了吧, 其实就是把items变成可迭代的, 重复三回(同一个对象哦), 但是别忘了,每次都.next(), 所以起到了分组的作用
 in [40]: [lambda x=i: x for i in range(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]: for i in a:
   ....:     if not i % 2:
   ....:         a.remove(i)
   ....:

in [46]: a
out[46]: [1, 3, 5] # 没有问题

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

in [51]: for i in b:
   ....:      if not i % 2:
   ....:          b.remove(i)
   ....:

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


思考一下,为什么 – 是因为你对列表的remove,影响了它的index
复制代码 代码如下:

in [53]: b = [2, 4, 5, 6]

in [54]: for index, item in enumerate(b):
   ....:     print(index, item)
   ....:     if not item % 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 (most recent call last)
<ipython-input-56-037d00de8360> in <module>()
----> 1 my_list[5]

indexerror: list index out of range # 抛异常了

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

六、重用全局变量

复制代码 代码如下:

in [58]: def my_func():
   ....:         print(var) # 我可以先调用一个未定义的变量
   ....:

in [59]: var = 'global' # 后赋值

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

in [61]: def my_func():
   ....:     var = 'locally changed'
   ....:

in [62]: var = 'global'

in [63]: my_func()

in [64]: print(var)

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

in [65]: def my_func():
   ....:         print(var) # 虽然你全局设置这个变量, 但是局部变量有同名的, python以为你忘了定义本地变量了
   ....:         var = 'locally changed'
   ....:

in [66]: var = 'global'

in [67]: my_func()
---------------------------------------------------------------------------
unboundlocalerror                         traceback (most recent call last)
<ipython-input-67-d82eda95de40> in <module>()
----> 1 my_func()

<ipython-input-65-0ad11d690936> in my_func()
      1 def my_func():
----> 2         print(var)
      3         var = 'locally changed'
      4

unboundlocalerror: local variable 'var' referenced before assignment

in [68]: def my_func():
   ....:         global var # 这个时候得加全局了
   ....:         print(var) # 这样就能正常使用
   ....:         var = 'locally changed'
   ....:

in [69]: var = 'global'

in [70]:

in [70]: my_func()
global

in [71]: print(var)
locally changed # 但是使用了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] for i in range(2)] # 用这种循环生成不同对象的方法就不影响了

in [77]: my_list2[1][0] = 'a'

in [78]: my_list2
out[78]: [[1, 2, 3], ['a', 2, 3]]


八、python多继承(c3)

复制代码 代码如下:

in [1]: class a(object):
   ...:         def foo(self):
   ...:                 print("class a")
   ...:

in [2]: class b(object):
   ...:         def foo(self):
   ...:                 print("class b")
   ...:

in [3]: class c(a, b):
   ...:         pass
   ...:

in [4]: c().foo()
class a # 例子很好懂, c继承了a和b,从左到右,发现a有foo方法,返回了

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

复制代码 代码如下:

in [5]: class a(object):
   ...:        def foo(self):
   ...:               print("class a")
   ...:

in [6]: class b(a):
   ...:        pass
   ...:

in [7]: class c(a):
   ...:        def foo(self):
   ...:               print("class c")
   ...:

in [8]: class d(b,c):
   ...:        pass
   ...:

in [9]: d().foo()
class c # ? 按道理, 顺序是 d->b->a,为什么找到了c哪去了


这也就涉及了mro(method resolution order):
复制代码 代码如下:

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]: import datetime

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)" (1 am) ->', bool(datetime.time(1,0,0)))
('"datetime.time(1,0,0)" (1 am) ->', true)


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

复制代码 代码如下:

in [37]: a = 1

in [38]: b = 1

in [39]: print('a is b', bool(a is b))
('a is b', true)

in [40]: c = 999

in [41]: d = 999

in [42]: print('c is d', bool(c is d))
('c is d', false) # 原因是python的内存管理,缓存了-5 - 256的对象

in [43]: print('256 is 257-1', 256 is 257-1)
('256 is 257-1', true)

in [44]: print('257 is 258-1', 257 is 258 - 1)
('257 is 258-1', false)

in [45]: print('-5 is -6+1', -5 is -6+1)
('-5 is -6+1', true)

in [46]: print('-7 is -6-1', -7 is -6-1)
('-7 is -6-1', false)
in [47]: a = 'hello world!'

in [48]: b = 'hello world!'

in [49]: print('a is b,', a is b)
('a is b,', false) # 很明显 他们没有被缓存,这是2个字段串的对象

in [50]: print('a == b,', a == b)
('a == b,', true) # 但他们的值相同
# but, 有个特例
in [51]: a = float('nan')

in [52]: print('a is a,', a is a)
('a is a,', 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]: import copy

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]: from copy import copy, 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 (most recent call last)
<ipython-input-112-d4f292cf35de> in <module>()
----> 1 tup[0] += [1]

typeerror: 'tuple' object does not support item assignment

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 += is weird, part ii :

复制代码 代码如下:

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]: class my_class(object^e):
   .....:     def public_method(self):
   .....:         print('hello public world!')
   .....:     def __private_method(self): # 私有以双下划线开头
   .....:         print('hello private world!')
   .....:     def call_private_method_in_class(self):
   .....:         self.__private_method()

in [132]: my_instance = my_class()

in [133]: my_instance.public_method()
hello public world! # 普通方法

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

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

in [136]: my_instance._my_class__private_variable
out[136]: 1

十七、异常处理加else

复制代码 代码如下:

in [150]: try:
   .....:     print('third element:', a_list[2])
   .....: except indexerror:
   .....:     print('raised indexerror')
   .....: else:
   .....:     print('no error in try-block') # 只有在try里面没有异常的时候才会执行else里面的表达式
   .....:
raised indexerror # 抛异常了 没完全完成
in [153]: i = 0

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

in [156]: while i < 2:
   .....:         print(i)
   .....:         i += 1
   .....:         break
   .....: else:
   .....:         print('completed while-loop')
   .....:
0 # 被break了 没有完全执行完 就不执行else里面的了
in [158]: for i in range(2):
   .....:         print(i)
   .....: else:
   .....:         print('completed for-loop')
   .....:
0
1
completed for-loop

in [159]: for i in range(2):
   .....:         print(i)
   .....:         break
   .....: else:
   .....:         print('completed for-loop')
   .....:
0 # 也是因为break了

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网