effective python 90_《Effective Python》59个有效方法(今日到25)

chapter1:用Pythonic方式来思考

第1条:确认自己所用的Python

shijianzhongdeMacBook-Pro:~ shijianzhong$ python -V

Python 2.7.16

shijianzhongdeMacBook-Pro:~ shijianzhong$ python --version

Python 2.7.16

shijianzhongdeMacBook-Pro:~ shijianzhong$ python3 --version

Python 3.7.4

shijianzhongdeMacBook-Pro:~ shijianzhong$ python3 -V

Python 3.7.4

shijianzhongdeMacBook-Pro:~ shijianzhong$

In [48]: import sys

In [49]: sys.version_info

Out[49]: sys.version_info(major=3, minor=7, micro=4, releaselevel='final', serial=0)

In [50]: sys.version

Out[50]: '3.7.4 (default, Jul 9 2019, 18:13:23) \n[Clang 10.0.1 (clang-1001.0.46.4)]'

In [51]:

以后重点使用py3,2已经冻结,只会bug修复

第2条:遵循PEP8风格指南

《Python Enhancement Proposal#8》又叫PEP8,它是针对Python代码格式而编订的风格指南。

空白:

使用space(空格)来表示缩进,而不要用tab(制表符)。

和语法相关的每一层缩进都用4个空格来表示

每行的字符数不应超过79,学习笔记说,现在可以100了

对于占据多行的长表达式来说,除了首行之外的其余各行都应该在通常的缩进级别之上再加4个空格(pycharm是6个空格)

文件中函数跟类应该用两个空行分开

在同一个类中,各方法之间应该用一个空行隔开

在使用下标来获取列表元素、调用函数或给关键字参数赋值的时候,不要在两旁添加空格

为变量赋值的时候,赋值符号的左侧和右侧应该各自写上一个空格,而且只写一个就好。

命名:

函数、变量和属性应该用小写字母来拼写,各单词之间以下划线相连。

受保护的实例属性,应该以单个下划线开头,例如_xxx_xx

私有的实例属性,应该以两个下划线开头,例如__double_xxx

类与异常,应该以每个单词首字母大写的形式来命名

模块级别的常量,应该全部采用大写字母来拼写,个单词之间以下划线相连,如ALL_CAPS

实例方法首字母(self)

类方法首字母(cls)

表达式语句

采用内联形式的否定词,而不是把否定词放在整个表达式的前面,列如  if a is not b 而不是 if not a is b

不要通过len一个序列去判断空,直接用对象就可以了。

文件中的那些import语句应该按照顺序划分三个部分,分别表示标准版模块、第三方模块以及自用模块。在每一个部分之中,各import语句应该按模块的字母顺序来排序.

第3条:了解bytes、str与unicode的区别

Python3有两种表示字符序列的类型:bytes和str。前者实例包含原始的8位值;或者的实例包含Unicode字符

Python2也有两种表示字符序列的类型,分别叫做str和unicode。与Python3不同的是,str的实例包含原始的8位值;而unicode的实例,则包含Unicode字符。

也就是说,py3的bytes与py2中的str数据格式相等

另外写的比较简单,py2默认的打开方式是2进制模式打开,py3是以文件的形式打开,如果向文件对象进行二进制操作,可以用'rb'与'wb'。

第4条:用赋值函数来取代复杂的表达式。

书中主要用到了or操作符左侧的子表达式估值为False,那么整个表达式的值就是or操作符右侧那个子表达式的值,和三元操作符。

最后告诉我们要适当使用Python语法特性。

In [58]: my_values = parse_qs('red=5&blue=0&green=',keep_blank_values=True)

In [59]: my_values

Out[59]: {'red': ['5'], 'blue': ['0'], 'green': ['']}

In [60]: red = my_values.get('red',[''])[0] or 0

In [61]: blue = my_values.get('blue',[''])[0] or 0

In [62]: green = my_values.get('green',[''])[0] or 0

In [64]: red;blue;green

Out[64]: 0

In [65]: red

Out[65]: '5'

In [66]: blue

Out[66]: '0'

In [67]: green

Out[67]: 0

In [68]:

上面就用到了or操作符的使用,但如果还要对参数进行数字加减,还要使用Int

blue = int(my_values.get('blue',[''])[0] or 0 )

这样有点麻烦,就写成三元表达式

In [68]: red = my_values.get('red',[''])

In [69]: red = int(red[0]) if red[0] else 0

In [70]:

如果是一个参数还好,如果频发使用到的话,可以写一个辅助函数。

In [70]: def get_first_int(values,key,defalut=0):

...: found = values.get(key, [''])

...: if found[0]:

...: found = int(found[0])

...: else:

...: found = defalut

...: return found

...:

In [71]: get_first_int(my_values,'red')

Out[71]: 5

In [72]: get_first_int(my_values,'blue')

Out[72]: 0

In [73]:

好无聊的一个章节,这个书有点买亏了

第5条:了解切割序列的方法。

切片赋值属于浅拷贝,切片数值[a:b]取头不取尾,-1取最后一个数值,a就是头的下标不能越界。

后面讲了对切片进行赋值

In [91]: a = list(range(9))

In [92]: a

Out[92]: [0, 1, 2, 3, 4, 5, 6, 7, 8]

In [93]: a[2:5] = ['a','b']

In [94]: a

Out[94]: [0, 1, 'a', 'b', 5, 6, 7, 8]

In [95]:

都是一个套路,很多书中以前就有过介绍。

第6条 在单词切片中,不要同时指定start、end、stride

In [95]: a = list(range(10))

In [96]: a

Out[96]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

In [97]: a[::3]

Out[97]: [0, 3, 6, 9]

In [98]: b = 'hello'

In [99]: b[::-1]

Out[99]: 'olleh'

In [100]:

书中主要说了,如果有切片了,就不要写步进,分开好比较好,我觉的都无所谓。

第7条:用列表推导取代map和filter

列表推导式的强大,确实可以完全取代map与filter,reduce还是有点意思的。

In [100]: from functools import reduce

In [101]: reduce(lambda x,y:x+y, range(10))

Out[101]: 45

In [102]: map(lambda x: x**2, range(10))

Out[102]:

In [103]: list(map(lambda x: x**2, range(10)))

Out[103]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [104]: [x**2 for x in range(10)]

Out[104]: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [105]: list(filter(lambda x: x%2, range(10)))

Out[105]: [1, 3, 5, 7, 9]

In [107]: [x for x in range(10) if x%2!=0]

Out[107]: [1, 3, 5, 7, 9]

In [108]:

字典,集合都支持列表推导式的结构。

第8条:不要使用含有两个以上表达式的列表推导

双for的列表推导式,可以将矩阵(即二维列表)简化成维列表,也可以改变二维列表内的元素

In [113]: matrix = [[1,2,3],[4,5,6],[7,8,9]]

In [114]: flat = [x for row in matrix for x in row]

In [115]: flat

Out[115]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In [116]:

跟for循环的顺序一样,第一次循环写前面,第二次循环写后面

下面是改变矩阵的数据

In [118]: squared = [[x**2 for x in row] for row in matrix]

In [119]: squared

Out[119]: [[1, 4, 9], [16, 25, 36], [49, 64, 81]]

In [120]:

但如果层数太多的话,就不建议使用列表推导式,应该会让人看过去很头痛。

列表推导式还支持多个if条件。

In [120]: a = list(range(1,11))

In [121]: a

Out[121]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

In [122]: b = [x for x in a if x > 4 if x%2==0]

In [123]: c = [x for x in a if x > 4 and x%2==0]

In [124]: b

Out[124]: [6, 8, 10]

In [125]: b==c

Out[125]: True

In [126]:

可以看出两个答案是等价的。

每一层循环的for表达式后面都可以指定条件。

In [127]: filtered = [[x for x in row if x%3==0] for row in matrix if sum(row) >=10]

In [128]: filtered

Out[128]: [[6], [9]]

In [129]:

这种写法还是很骚包的

第9条:用生成器表达式来改写数据量较大的列表推导

假如读取一份文件并返回每行的字符数,若采用列表推导来做,则需要文件每一行的长度度保存在内存中。

如果文件非常大,那可能会内存溢出或者运行很慢,可以采取生成器表达式,就是把[]换成()

it = (x for x in open('/tmp/file.txt'))

可以通过next函数对数据进行读取

使用生成器还有一个好处,就是可以互相组合。

In [129]: generatpr = (i for i in range(10))

In [130]: generatpr

Out[130]: at 0x1144f0dd0>

In [131]: roots = ((x,x**2) for x in generatpr)

In [132]: next(roots)

Out[132]: (0, 0)

In [133]: next(roots)

Out[133]: (1, 1)

In [134]: next(roots)

Out[134]: (2, 4)

In [135]:

外围的迭代器每次前进时,都会推动内部那个迭代器,这就产生了连锁效应,使得执行循环、评估条件表达式、对接输入和输出等逻辑度组合在了一期。

第10条: 尽量用enumerate取代range

本章主要讲了enumerate可以将一个迭代器包装成一个生成器。而且包装的对象会自动加上索引。

In [7]: a = enumerate('0123')

In [8]: next(a)

Out[8]: (0, '0')

In [9]: next(a)

Out[9]: (1, '1')

In [10]: next(a)

Out[10]: (2, '2')

In [11]: next(a)

Out[11]: (3, '3')

In [12]: next(a)

---------------------------------------------------------------------------

StopIteration Traceback (most recent call last)

in

----> 1 next(a)

StopIteration:

In [13]: b = enumerate('123',1)

In [14]: next(b)

Out[14]: (1, '1')

In [15]: next(b)

Out[15]: (2, '2')

In [16]:

感觉还是挺不错的,第二个参数是开始计数使用的值(默认为0)

第11条:用zip函数同事遍历两个迭代器

zip可以平行的遍历多个可迭代对象。

In [16]: z = zip('abc',[1,2,3])

In [17]: for chart, index in z:

...: print(chart, index)

...:

a 1

b 2

c 3

In [18]: for o in z:

...: print(o)

...:

...:

In [19]: z = zip('abc',[1,2,3])

In [20]: for o in z:

...: print(o)

...:

...:

('a', 1)

('b', 2)

('c', 3)

In [21]:

如果提供的可迭代对象长度不等,则以短的为准

In [21]: z = zip('a',range(3))

In [22]: next(z)

Out[22]: ('a', 0)

In [23]: next(z)

---------------------------------------------------------------------------

StopIteration Traceback (most recent call last)

in

----> 1 next(z)

StopIteration:

In [24]:

如果想不在长度是否相等,可以用itertools.zip_longest

第12条:不要在for和while循环后面写else块

这个我个人其实觉的蛮好用的,很装逼,但很多进阶的书都说这种功能不是太好。

书中主要通过return的方式打断返回,或者在循环外部定义一个变量,break之前修改变量。

我感觉其实都不是太好,还for或者while+else好。

主要记住,正常循环完毕会执行else条件,如果中间打断了,就不执行else条件。

第13条:合理利用try/except/else/finally结构中的每个代码块

try:编写可能会报错的逻辑

except:捕获错误的信息

esle:写需要返回的结果情况,与try中可能存在的错误分离

finaly:写肯定会执行的逻辑

try,可以配合finaly直接使用

使用了else必须带上except

Chapter2

函数

第14条:尽量用异常来表示特殊请, 而不要返回None

编写工具函数的时候,有些程序员喜欢将错误捕获,直接返货None

def divide(a, b):

try:

return a / b

except ZeroDivisionError:

return None

这样会存在一些问题,很多时候,我们拿到函数运行,会直接把返回值拿到if条件中。

None的效果与0,空字符串,等效果相等,所以当分子是的0,分母不为0的时候,就会出现问题。

解决的方法应该不反悔None,而是把异常抛给上一级,使得调用者必须应对它。下面就是把ZeroDivisionError转换成ValueError,表示输入无效

def divide(a, b):

try:

return a / b

except ZeroDivisionError as e:

raise ValueError('Invalid inputs') from e

if __name__ == '__main__':

x, y = 5, 2

try:

res = divide(x, y)

except ValueError:

print('Invalid inputs')

else:

print('Result is %.1f' % res)

这个再写工具函数的时候确实一个不错的选择,错误的捕获可以上浮正确的错误形式,让上级去处理。

第15条: 了解如何在闭包里使用外围作用域中的变量

先来一个特定要求的排序,再特定组里面的数组排队优先,写的很棒

def sort_priority(values, group):

def helper(x):

if x in group:

return (0, x)

return (1, x)

values.sort(key=helper)

这是我两个月来见过写的最有意思的函数。

使用了闭包,内部函数引用了外包函数的变量,而且排序的时候,使用的多元素比较,第一元素比好以后,比第二元素。

后面书中讲了闭包的作用域问题LEGB,已经内部作用域可以引用外部作用域变量,可以修改外部作用域的可变对象。

但内部作用域与外部作用域变量名同名时,想修改外部作用域变量不能操作。因为赋值操作,当前作用域没有这个变量,会新建这么一个变量,有的话就修改。

书中也介绍了nolocal语句,来表明该变量名,可以修改外部变量的指向。但这个只能用再较短的函数,对于一些比较长的函数。使用class,内部建议__call__,到时候实例一个可调用的对象,对象内保存状态

def sort_priority(values, group):

found = False

def helper(x):

if x in group:

nonlocal found

found = True

return (0, x)

return (1, x)

values.sort(key=helper)

return found

nolocal的用法

def sort_priority(values, group):

sort_priority.found = False

def helper(x):

if x in group:

sort_priority.found = True

return (0, x)

return (1, x)

values.sort(key=helper)

return sort_priority.found

我自己想的通过函数对象赋值一个属性。

class Sorter:

def __init__(self, group):

self.group = group

self.found = False

def __call__(self, x):

if x in self.group:

self.found = True

return (0, x)

return (1, x)

上面时书上面特意写了一个用于被排序用的可调用对象。

在Python2中没有nolocal,可以在闭包的参数设置为可变参数,这样内部的函数就可以改变该参数的内部属性。

这个章节的一些内容以前看过,就当再理解一下,那个排序的写法,第一次看到,真的让我眼前一亮。

第16条: 考虑用生成器来改写直接返回列表的函数

def index_words(text):

result = []

if text:

result.append(0)

for index, letter in enumerate(text, 1):

if letter == ' ':

result.append(index)

return result

这个一个标准版的返回各个单词首字母索引的函数。书中说明,这个函数有两个不好的地方

1:函数内定义了一个result的列表,最后返回这么一个列表,显的很啰嗦

2:假如这个text很大,那这个列表也会输入量很大,那么程序就有可能耗尽内存并奔溃。

def index_words_iter(text):

if text:

yield 0

for index, letter in enumerate(text, 1):

if letter == ' ':

yield index

这种生成器的写法就可以避免这样的问题发生,而且用生成器表达更加清晰。

最后记录一下,书中一个处理文件的生成器函数

def index_file(handle):

offset = 0

for line in handle:

if line:

yield offset

# 这时真正函数运行消耗内存的地方,需要读取一行数据

for letter in line:

offset += 1

if letter == ' ':

yield offset

表名了处理文档,函数真正消耗内存的地方

第17条:在参数上面迭代时,要多加小心

一个函数如果对一个迭代器进行操作,务必要小心,因为迭代器保存一种状态,一但被读取完以后,就不能返回开始的状态。

def normalize(numbers):

total = sum(numbers)

result = []

for value in numbers:

percent = 100 * value / total

result.append(percent)

return result

visits = [15, 35, 80]

percentages = normalize(visits)

print(percentages)

这是一个函数,处理每个数据的百分比,当传入的是一个容器对象的时候就没事。

但传入一个迭代器的时候,就出问题了,sum函数会把迭代器里面的内容读取完毕,后面的for循环去读取numbers的时候,里面是没有数据的。

def normalize(numbers):

numbers = list(numbers)

total = sum(numbers)

result = []

for value in numbers:

percent = 100 * value / total

result.append(percent)

return result

这样是最简单偷懒的方式,但这样函数这样处理大数据的时候就会出问题,还有一个方式就是参数接收另外一个函数,那个函数调用完都会返回一个新的迭代器

def normalize_func(get_iter):

total = sum(get_iter())

result = []

for value in get_iter():

percent = 100 * value / total

result.append(percent)

return result

# 传入一个lambda函数,后面的是一个返回生成器的函数

percentahes = normalize_func(lambda : read_bisits(path))

这样看过去比较不舒服,那就自己定一个可迭代的容器。

class ReadVisits:

def __init__(self, data_path):

self.data_path = data_path

def __iter__(self):

with open(self.data_path) as f:

for line in f:

yield int(line)

visits = ReadVisits('/user/haha.txt')

percentages = normalize(visits)

通过自己定义的类,实例化以后的对象,当需要对这个容器进行操作的时候,都会调用__iter__方式返回的迭代器。比如sum,for,max,min等

迭代器协议有这样的约定:如果把迭代器对象传给内置的iter函数,那么此函数会把该迭代器返回,也就是返回自身,反之,如果传给iter函数的是个容器类型对象,那么iter函数则每次返回新的迭代器对象。

由于我们前面定义的函数需要处理容器对象,所以对进来的参数可以通过前面的方式进行过滤

def normalize_defensive(numbers):

# 判断是否是容器对象

if iter(numbers) is iter(numbers):

raise TypeError('Must suplly a container')

total = sum(numbers)

result = []

for value in numbers:

percent = 100 * value / total

result.append(percent)

return result

本章节我主要很好的学到了自定义容器对象,以及容器对象每次iter返回的都是不同内存地址的迭代器,也就是不同的对象。但迭代器iter返回的是自身。

而且可以看出来容器对象与迭代器没有啥必然的关系,两种也是各自为政的对象.

第18条:用数量可变的未知参数减少视觉杂讯

本条主要讲了*args接收不定长参数的使用。

def log(message, *value):

if not value:

print(message)

else:

value_str = ', '.join(str(x) for x in value)

print('%s: %s' % (message, value_str))

上面的例子中*value可以接收任意长度的参数,在函数内部value会转化成元祖

书中还有一个就是通过解包的方式传递参数

如果传递进去的是一个序列

l = [1,2,3,4]

可以在传递的时候通过*l的方式解包,里面的每一个元祖都会被视为位置参数,如果解包的是一个迭代器,如果数据过大就会消耗大量的内存。

第19条:用关键字参数来表达可选的行为。

def remainder(number, divisor):

return number % divisor

assert remainder(20, 7) == 6

上面的传参形式就是标准的位置传参。

可以通过下面的形式传参

remainder(20, divisor=7)

remainder(numver=20, divisor=7)

remainder(divisor=7, number=20)

Python中有规定,位置参数必须在关键字参数之前,每个参数只能被指定一次【也就是说,位置参数传参过以后,不能再通过关键字传参】

remainer(number=20, 7)

assert remainder(number=7, 20) == 6

^

SyntaxError: positional argument follows keyword argument

语法错误

emainder(20, number=7) == 6

assert remainder(20, number=7) == 6

TypeError: remainder() got multiple values for argument 'number'

类型错误

书中简单说明了关键字传参的好处,能让人了解,提供默认值,扩展函数参数的有效性。

第20条:用None和文档字符串来秒速具有默认值的参数。

import time

from datetime import datetime

def log(message, when=datetime.now()):

print('%s: %s' % (message, when))

log('Hi there !')

time.sleep(0.1)

log('Hi, again')

print(log.__defaults__)

输出

Hi there !: 2020-09-13 13:06:55.770035

Hi, again: 2020-09-13 13:06:55.770035

(datetime.datetime(2020, 9, 13, 13, 6, 55, 770035),)

流畅的Python中介绍,以及本人的理解,默认参数会成为函数对象的属性。在函数进行初始化的时候进行运行一次。

后面就不会再重新运行加载,所以会出现前面的情况。

import time

from datetime import datetime

def log(message, when=None):

when = datetime.now() if when is None else when

print('%s: %s' % (message, when))

log('Hi there !')

time.sleep(0.1)

log('Hi, again')

print(log.__defaults__)

改成上面这样

输出

Hi there !: 2020-09-13 13:10:11.295604

Hi, again: 2020-09-13 13:10:11.400005

(None,)

后面介绍了,传参给了一个可变对象,多人调用这个函数修改这个参数。

这个在流畅的Python中也有详细的介绍:

书中原文【在Python中,函数得到参数的副本,但是参数始终是引用。因此,如果参数引用的是可变对象,那么对象可能被修改,但是参数的表示不变】

第21条:用只能以关键字形式指定的参数来确保代码明晰

这里主要讲诉了如果传参必须要关键字传参的操作,

在Python3中可以通过*操作,

def demo(x,y,* must_show):

....

在Python2中没有*操作,只能通过**kwargs的方式接收关键字传参的不定长参数

然后在函数体内进行逻辑判断。

第三章 类与继承

第22条:尽量用辅助类来维护程序的状态,而不要用字典和元祖。

书中前面讲了,如果通过一个类保存一个复杂的数据结构状态。

如果保存程序的数据结构一旦变得过于复杂,就应该将其拆解为类,以便提供更为明确的接口。并更好的封装数据。这样做也能够在接口和具体实现之间创建抽象层。

后面都为本人的理解

最为对象层级最底层的grade(成绩),书中了用了nametuple进行了对象封装。

成绩对象的封装写法

import collections

# 一个权重,一个成绩

Grade = collections.namedtuple('Grade', ('score', 'weight'))

然后每一次成绩称为科目的对象的元素之一,科目有一个属性_grades的列表保存每一次成绩

class Subject:

def __init__(self):

self._grades = []

# 在科目对象装入成绩对象

def report_grade(self, score, weight):

self._grades.append(Grade(score, weight))

# 求出这门功课的平均分

def average_grade(self):

total, total_weight = 0, 0

for grade in self._grades:

total += grade.score * grade.weight

total_weight += grade.weight

return total / total_weight

科目可以称为学生的属性,一个学生会有很多科目。学生的科目用的是dictionaries保存

class Student:

def __init__(self):

self._subject = {}

# 写入一门功课

def subject(self, name):

return self._subject.setdefault(name, Subject())

# 求出一个学生所有功课的平均分

def average_grade(self):

total, count = 0, 0

for subject in self._subject.values():

total += subject.average_grade()

count += 1

return total / count

学生又是班级花名册里面的一个对象,所有最后定一个花名册

#!/usr/bin/env python3

# -*- coding: UTF-8 -*-

import collections

# 一个权重,一个成绩

Grade = collections.namedtuple('Grade', ('score', 'weight'))

class Subject:

def __init__(self):

self._grades = []

# 在科目对象装入成绩对象

def report_grade(self, score, weight):

self._grades.append(Grade(score, weight))

# 求出这门功课的平均分

def average_grade(self):

total, total_weight = 0, 0

for grade in self._grades:

total += grade.score * grade.weight

total_weight += grade.weight

return total / total_weight

class Student:

def __init__(self):

self._subject = {}

# 写入一门功课

def subject(self, name):

return self._subject.setdefault(name, Subject())

# 求出一个学生所有功课的平均分

def average_grade(self):

total, count = 0, 0

for subject in self._subject.values():

total += subject.average_grade()

count += 1

return total / count

class Gradebook:

def __init__(self):

self._student = {}

def student(self, name):

return self._student.setdefault(name, Student())

if __name__ == '__main__':

book = Gradebook()

# 学生

albert = book.student('Albert Einstein')

# 学生添加功课

math = albert.subject('Math')

# 功课添加成绩与权重

math.report_grade(90,0.3)

math.report_grade(95, 0.5)

print(book.student('Albert Einstein').average_grade())

上面直接上了,整体代码,首先定义一个成绩对象,需要几个属性就写几个,用了nametuple,然后成绩方位具体课程的对象中,由于一个课程可以有多门成绩,所以用列表来保存所有的成绩。

还在课程对象中直接定义了,该课程求出平均成绩的方法。

然后课程可以作为学生对象的元素,由于每个课程的都是一个独立的对象,考虑要后期需要取出该课程信息,所有用了dictionaries来保存,key是课程的名称,value是课程对象

后面同样的方式,把学生对象放入花名册。

这个其实主要考虑的是一个抽象的思考能力,在模型复杂的过程中,层层剥离对象,使一个对象称为另一个对象的属性。

书中的案例,是先写好最小的对象,层层方法,一个对象是另外一个的属性,这也是非常不错的思考方式,值的我学习,借鉴。

第23条:简单的接口应该接收函数,而不是类的实例。

def increment_with_report(current, increments):

added_count = 0

# 定义一个回调函数

def missing():

nonlocal added_count

added_count += 1

return 0

# 使用defaultdict调用missing函数

result = defaultdict(missing, current)

for key, amount in increments:

result[key] += amount

return result, added_count

result, count = increment_with_report(current, increments)

print(result)

print(count)

运行输出:

defaultdict(.missing at 0x118403268>, {'green': 12, 'blue': 20, 'red': 5, 'orange': 9})

2

上面通过了使用了闭包,内部函数调用了外部的非全局变量。内部定义一个函数给当做回调函数。

通过闭包来保存一种状态也可以,当当过辅助类是一种相对更加好理解的方式

class BetterCountMissing:

def __init__(self):

self.added = 0

# 实例化以后, 称为一个可调用对象。

def __call__(self, *args, **kwargs):

self.added += 1

return 0

count = BetterCountMissing()

result = defaultdict(count, current)

for key, amount in increments:

result[key] += amount

print(count.added)

__call__方法强烈地暗示了该类的用途,它告诉我们,这个类的功能就相当与一个带有状态的闭包。

第24条:用@classmethod形式的多态去通用地构建对象。

在这一个章节,书中主要介绍了通过装饰器@classmethod,类方式去初始化一些特定的对象。还有就是介绍了一个有趣的模块 tempfile.TemporaryDirectory,建立一个临时目录,当对象删除的时候,目录也没了。

书中展示了一个输入处理类,和一个工作类的衔接。

#!/usr/bin/env python3

# -*- coding: UTF-8 -*-

import abc

import os

from threading import Thread

from tempfile import TemporaryDirectory

class InputData:

@abc.abstractmethod

def read(self):

raise NotImplementedError

# 处理输入的

class PathInputData(InputData):

def __init__(self, path):

super(PathInputData, self).__init__()

self.path = path

def read(self):

return open(self.path).read()

# 工作线程基类

class Worker:

def __init__(self, input_data):

self.input_data = input_data

self.result = None

@abc.abstractmethod

def map(self):

raise NotImplementedError

@abc.abstractmethod

def reduce(self, other):

raise NotImplementedError

# 处理有几行的工作类

class LineCountWorker(Worker):

def map(self):

data = self.input_data.read()

self.result = data.count('\n')

def reduce(self, other):

self.result += other.result

# 返回文件夹下面所有文件的处理对象生成器

def generate_inputs(data_dir):

for name in os.listdir(data_dir):

yield PathInputData(os.path.join(data_dir, name))

# 返回的处理文档对象,建立工作实例对象列表

def create_workers(input_list):

workers = []

for input_data in input_list:

workers.append(LineCountWorker(input_data))

return workers

# 最后面多线程执行工作对象

def execute(workers):

threads = [Thread(target=w.map) for w in workers]

for thread in threads: thread.start()

for thread in threads: thread.join()

first, rest = workers[0], workers[1:]

for worker in rest:

first.reduce(worker)

return first.result

# 然后把前面的打包成一个函数

def mapreduce(data_dir):

inputs = generate_inputs(data_dir)

workers = create_workers(inputs)

return execute(workers)

def write_test_files(tmpdir):

for i in 'abcde':

abs_path = os.path.join(tmpdir, i)

with open(abs_path, 'w') as f:

f.writelines(['2', '\n', '3','\n'])

with TemporaryDirectory() as tmpdir:

print(tmpdir)

write_test_files(tmpdir)

result = mapreduce(tmpdir)

print('There are', result, 'lines')

书中通过对象的调用,函数式的方式完成了任务

但是这个写法有个大问题,那就是mapreduce函数不够通用。如果要编写其它的InputData或Woker子类,那就要重写generate_inpurs, create_workers, mapreduce函数,以便与之匹配。

书中不知道是翻译的僵硬,还是我的理解能力不够,主要介绍了,编写基类的方式,通过类方法的继承实现类方法的多态,返回所需要的要求

# 基类

class GenericInputData:

def read(self):

raise NotImplementedError

@classmethod

def generate_inputs(cls, config):

raise NotImplementedError

class PathInputData(GenericInputData):

def __init__(self, path):

self.path = path

def read(self):

return open(self.path).read()

# 类方法直接返回一个生成器

@classmethod

def generate_inputs(cls, config):

data_dir = config['data_dir']

for name in os.listdir(data_dir):

yield cls(os.path.join(data_dir, name))

# 工作的基类

class GenericWorker:

def __init__(self, input_data):

self.input_data = input_data

self.result = None

def map(self):

raise NotImplementedError

def reduce(self, other):

raise NotImplementedError

# 返回需要处理的的workers容器列表

@classmethod

def create_workers(cls, input_class, config):

workers = []

# 调用前面的类方法,产生的生成器

for input_data in input_class.generate_inputs(config):

workers.append(cls(input_data))

return workers

class LineCountWorker(GenericWorker):

def map(self):

data = self.input_data.read()

self.result = data.count('\n')

def reduce(self, other):

self.result += other.result

def mapreduce(worker_class, input_class, config):

workers = worker_class.create_workers(input_class, config)

return execute(workers)

with TemporaryDirectory() as tmpdir:

write_test_files(tmpdir)

config = {'data_dir': tmpdir}

result = mapreduce(LineCountWorker, PathInputData, config)

print(result)

这是书中通过类方法的方式写的,通过定义通用接口。

在Python中,类只有一个构造器函数__init__,可以通过@classmethod的方式来丰富创建对象的形式。按照就可以直接通过类方法,返回列表,生成器等。__init__可是没有返回值的。

第25条 用super初始化父类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值