深入理解Python(一)

译文 原文地址

1. 多重类继承的C3解决方案

假设现在有一个类C继承自两个父类A, B

class A(object):
    def foo(self):
        print('class A')

class B(object):
    def foo(self):
        print('class B')


class C(A, B):
    pass

C().foo()
class A

上面的过程到底发生了什么?C在寻找方法foo的时候,首先在父类A中找到了foo。
我们来看一个稍微复杂点的例子

class A(object):
    def foo(self):
        print('class A')

class B(A):
    pass

class C(A):
    def foo(self):
        print('class C')
        
class D(B, C):
    pass
    
D().foo()

class C

这里D首先搜索B(继承了A,这里注意C也继承了A,但是C有它自己的foo函数),因此它们的搜索顺序是:D -> B -> C -> A

思考题(1),下面的程序输出啥?文章底部查看答案


class A(object):
    def foo(self):
        print('class A')

class B(A):
    pass

class C(object):
    def foo(self):
        print('class C')
        
class D(B, C):
    pass
    
D().foo()

2. 赋值操作符和列表 — 使用 + 和 += 的区别

我们都知道python list是可变对象,因此我们在对列表S使用 ‘+=’ 的时候,我们是直接更扩展了列表S本身。

但是,如果我们使用赋值操作符’=’,a_list = a_list + …,这就创建了一个新的列表,我们用下面的代码说明:

a_list = []
print('ID: ', id(a_list))

a_list += [1]
print('ID with "+=": ', id(a_list))

a_list = a_list + [2]
print('ID with "+": ', id(a_list))

listappendextends 方法也是在原有列表的基础上操作的。

3. 对日期使用bool转换会怎样?

程序猿在发现半夜(datetime.time(0, 0, 0))是False的时候通常会非常吃惊。实际上在python-ideas的邮件列表里有一个非常长的讨论,虽然很让人吃惊,但是这种行为确实所期望的—至少一部分人。

译者注:python2 和 python3 的行为是不一致的,这里描述的只是 python2 的行为,python3 都会输出True !!!

import datetime

print('"datetime.time(0, 0, 0)" (Midnight) ->', bool(datetime.time(0, 0, 0)))

print('"datetime.time(1, 0, 0)" (I am) ->', bool(datetime.time(1, 0, 0)))
# python2
('"datetime.time(0, 0, 0)" (Midnight) ->', False)
('"datetime.time(1, 0, 0)" (I am) ->', True)
# python3
"datetime.time(0, 0, 0)" (Midnight) -> True
"datetime.time(1, 0, 0)" (I am) -> True

tips

这里大家注意到了没有,python2和python3的字符串输出的格式也不一样

o |||

4. Python 对小整数进行复用,使用 ‘==‘来比较相等,用 is 来检查id

这种奇怪的行为是因为python有一个用于存储小整数的数组[-5, 256]

a = 1
b = 1
print('a is b', a is b)

c = 999
d = 999
print('c is d', c is d)

因此比较大小应该始终使用==而不是isis只用来比较id。
这里有一篇非常好的文章,比较了C语言中的”boxes”和python中的”name tags”

下面的例子说明小整数的范围确实是[-5, 256]

print('256 is 257-1', 256 is 257-1)
print('257 is 258-1', 257 is 258 - 1)
print('-5 is -6+1', -5 is -6+1)
print('-7 is -6-1', -7 is -6-1)
256 is 257-1 True
257 is 258-1 False
-5 is -6+1 True
-7 is -6-1 False

我们用字符串测试一下 ‘==’ 和 ‘is’ 的不同功能

a = 'hello world!'
b = 'hello world!'
print('a is b', a is b)
print('a == b', a == b)
a is b False
a == b True

大家可能认为标识符相等一定对象相等,其实不然,我们看下面一个例子

a = float('nan')
print('a is a,', a is a)
print('a == a,', a == a)
a is a, True
a == a, False

5. 列表的浅拷贝/深拷贝

浅拷贝

如果我们使用复制操作符‘=’将一个列表赋值给一个变量,我们只是创建了一个对原始列表的拷贝。如果我们想要新的列表对象,我们必须创建原始列表的一个拷贝。可以通过 a_list[:]或者 a_list.copy()来创建拷贝。

list1 = [1, 2]
list2 = list1               # 引用
list3 = list1[:]            # 浅拷贝
list4 = list1.copy()    # 浅拷贝

print('IDs\nlist1: {}\nlist2: {}\nlist3: {}\nlist4: {}\n'.format(
    id(list1), id(list2), id(list3), id(list4)))
    
list2[0] = 3
print('list1: ', list1)

list3[0] = 4
list4[1] = 4
print('list1: ', list1)
IDs
list1: 4370426312
list2: 4370426312
list3: 4370426120
list4: 4367353864

list1:  [3, 2]
list1:  [3, 2]

深拷贝

像我们上面看到的那样,如果我们想要独立的修改一个列表中的内容,我们使用浅拷贝,可以工作的很好。

但是,如果我们使用了符合对象(如一个列表中包含了其他列表),上面的方法就不行了。

在符合对象中,一个浅拷贝会创建一个新的符合对象,但是只是在新的列表中插入了原始列表的引用。和浅拷贝不同,深拷贝却会走的更远,它会为每一个列表中的对象创建一个新的对象,而不是仅仅只是一个浅拷贝。我们来看下面的代码

from copy import deepcopy
list1 = [[1], [2]]
list2 = list1.copy()
list3 = deepcopy(list1)

print('IDs\nlist1: {}\nlist2: {}\nlist3: {}\n'.format(
    id(list1), id(list2), id(list3)))
    
list2[0][0] = 3
print('list1: {}'.format(list1))

list3[0][0] = 5
print('list1: {}'.format(list1))
IDs
list1: 4357544008
list2: 4369385224
list3: 4368187464

list1: [[3], [2]]
list1: [[3], [2]]

6. 利用逻辑 and 和 逻辑 or 选择真值

a or b: 如果a, b都为真,返回第一个值,也就是a。and 表达式会返回第二个值b。
这也叫做短路电流法 — or 的第一个参数为 True, 那么整个表达式就为 True,这样就可以不用计算第二个表达式

res = (2 or 3) * (5 and 7)
print('2 * 7 =', res)
2 * 7 = 14

7. 不要给函数传递可变对象作为默认值

如果你使用一个空的列表作为默认值,你可能希望你每次创建时,该函数都会为你创建一个新的列表,但实际情况却不是这样的。Python会为默认参数为可变对象的函数,在首次创建时,创建一次可变对象。看下面的代码

def append_to_list(value, def_list=[]):
    def_list.append(value)
    return def_list
    
my_list = append_to_list(1)
print(my_list)

my_other_list = append_to_list(2)
print(my_other_list)    
[1]
[1, 2]

另外一个好的例子展示了可变的默认参数在函数被创建时创建(而不是在调用时)

import time
def report_arg(my_default=time.time()):
    print(my_default)
    
report_arg()

time.sleep(3)

report_arg()
1504260900.206054
1504260900.206054

8. 注意消耗型的生成器

注意’in’和生成器一起使用时的行为,因为它们不会每一次都从起始位置开始求值

gen = (i for i in range(5))
print('2 in gen', 2 in gen)
print('3 in gen', 3 in gen)
print('1 in gen', 1 in gen)
2 in gen True
3 in gen True
1 in gen False

这种行为违背了大多数的情况下使用生成器的目的,我们可以将其转换为列表来解决这种问题

gen = (i for i in range(5))
a_list = list(gen)
print('2 in l,', 2 in a_list)
print('3 in l,', 3 in a_list)
print('1 in l,', 1 in a_list)
2 in l, True
3 in l, True
1 in l, True

9. bool 是 int 的子类

先有鸡还是先有蛋?在Python的历史上,bool值是由0、1来实现的(和C语言中的一样),为了避免在老的python代码中出错,bool类在Python2.3的时候被作为int类的子类

print('isinstance(True, int): ', isinstance(True, int))
print('True + True = ', True + True)
print('3 * True + True = ', 3 * True + True)
print('3 * True - False = ', 3 * True - False)
isinstance(True, int):  True
True + True =  2
3 * True + True =  4
3 * True - False =  3

10. 在闭包循环中使用 lambda 表达式

还记得“消耗型的生成器”那一章吗?这里这个例子和之前那个多少有点联系,然而结果仍然不是我们所期望的

在下面的第一个例子中,我们在一个列表生成器中调用lambda函数,值 i 是在我们调用lambda函数的时候才回去解引用,而且是在列表生成器的范围内。因为列表生成器已经在运行for循环执行时已经构建和求值了,而在闭包范围内的变量 i 已经被设置成了 4

my_list = [lambda: i for i in range(5)]
for l in my_list:
    print(l())
4
4
4
4
4

但是我们在使用生成器表达式的时候,却可以确保每一步都是单步执行的(注意值还是从闭包中来的,只不过闭包中的求值是分布执行的)

my_gen = (lambda: n for n in range(5))
for l in my_gen:
    print(l())
0
1
2
3
4

如果你是list强迫症患者,可以使用下面的代码

my_list = [lambda x=i: x for i in range(5)]
for l in my_list:
    print(l())

答案

  1. 思考题(1)
class A
    原文作者:lovetianyats
    原文地址: https://www.jianshu.com/p/84f7b783837f
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞