深入理解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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • http://python.jobbole.com/85231/ 关于专业技能写完项目接着写写一名3年工作经验的J...
    燕京博士阅读 7,484评论 1 118
  • 个人笔记,方便自己查阅使用 Py.LangSpec.Contents Refs Built-in Closure ...
    freenik阅读 67,641评论 0 5
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • # Python 资源大全中文版 我想很多程序员应该记得 GitHub 上有一个 Awesome - XXX 系列...
    aimaile阅读 26,298评论 6 428
  • 构图中的拍摄点基本包括方向与高度的选取,那么在拍摄时,人物的美姿动作设计应该如何与拍摄点相互作用,我们要从正面、斜...
    未什么来_52cc阅读 368评论 0 1