python记录
前记
使用python风格写更好的代码
记录能提升编写代码能力的方法,以及其他一些小方法,方便日后查看了解
Effective Python部分
由于书是在图书馆借的,不能划很难受,所以记下来
EP9 用生成器表达式来改写数据量较大的列表推导(EP16考虑用生成器来改写直接返回列表的函数)
当数据量不确定或者过大时,为了避免程序消耗大量内存而崩溃,可以使用生成器
1 |
|
功能类似于
1 |
|
EP10 用enumerate取代range
当需要获取到列表的下表时,可以改用这种写法
1 |
|
EP15 获取闭包内的数据
有些场合我们需要用到闭包,而在闭包里面会定义一个标志变量,返回调用者。python的语言设计防止函数中的局部变量污染函数外面的模块,此时应使用nonlocal语句
1 |
|
EP22 尽量用辅助类来维护程序状态
这些类的代码量可能比较多,但理解起来要容易许多,也容易拓展
1 |
|
EP23 简单的借口应该接受函数,而不是类的实例
我好想吐槽这个题目- -,看了好久才知道要表达什么
示例是通过挂钩函数来说明,调用类的实例的时候,别人会难以理解这个类的意图,最后使用__call__的方法,使相关对象能够像函数一样得到调用,但是我总觉得如果是简单的功能,直接使用闭包就好了- -
3种方法对比:
- 原始函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17from collections import defaultdict
def log_missing():
print('key added')
return 0
current = {'green':12,'blue':3}
increments = [
('red',5),
('blue',17),
('prange',9),
]
result = defaultdict(log_missing,current)
print('Before:', dict(result))
for key, amount in increments:
result[key] += amount
print('After:', dict(result)) - 修改挂钩函数,通过闭包的方式,总计挂钩函数调用次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14def increment_with_report(current, increments):
added_count = 0
def missing():
nonlocal added_count
added_count += 1
return 0
result = defaultdict(missing,current)
for key, amount in increments:
result[key] += amount
return dict(result), added_count
print(increment_with_report(current, increments)) - 通过__call__方法,使相关对象能够像函数一样得到调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14class BetterCountMissing(object):
def __init__(self):
self.added = 0
def __call__(self):
self.added += 1
return 0
counter = BetterCountMissing()
result = defaultdict(counter, current)
for key, amount in increments:
result[key] += amount
print(dict(result))
assert counter.added ==2EP24 以@classmethod形式的多台去通用地构建对象
手工构建相关对象,并通过辅助函数将这些对象联系起来的方法有一个问题就是函数不够通用,如果要编写其他子类,就得重写函数,在其他语言中,可以通过构造器多态来解决,但是python中只允许名为__init__的构造器方法,也就是每个类只有一个构造器,这时候就可以用@classmethod形式的多台来解决了。它针对的不是整个类,而是从该类构建出来的对象。
以下两种方法中,第二种的方法虽然需要的参数变多了,但是我们在编写子类时不用修改刚才写好的拼接方法了@classmethod方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65#定义一个表示输入的基类
class InputData(object):
def read(self):
raise NotImplementedError #尚未实现的方法返回的异常
#编写InputData类的具体子类,以便从磁盘文件里读取数据
class PathInputData(InputData):
def __init__(self,path):
super().__init__()
self.path = path
def read(self):
return open(self.path).read()
#为工作线程定义一套类似的抽象借口,以便使用标准的方式来处理输入的数据
class Worker(object):
def __init__(self, input_data):
self.input_data = input_data
self.result = None
def map(self):
raise NotImplementedError
def reduce(self, other):
raise NotImplementedError
#定义具体的Worker子类。本例所实现的功能是一个简单的换行符计数器:
class LineCountWorker(Worker):
def map(self):
data = self.input_data.read()
self.result = data.count('\n')
def reduce(self, other):
self.result += other.result
#以下是手工构建相关对象
#列出某个目录的内容,并为该目录下的每个文件创建一个PathInputData实例
def generate_inputs(data_dir):
for name in os.listdir(data_dir):
yield PathInputData(os.path.join(data_dir, name))
#用generate_inputs方法所返回的InputData实例创建LineCountWorket实例
def create_workers(input_list):
workers = []
for input_data in input_list:
workers.append(LineCountWorker(input_data))
return workers
#执行Worket实例,以便将流程中的map的步骤派发到多个线程之中,接下来反复调用reduce方法,将map步骤的结构合并成一个最终值
def execute(workers):
threads = [Thread(target=w.map) for w in workers]
for thead in threads: thread.start()
for thead in threads: thread.join()
first, rest = workers[.],workers[1:]
for worker in rest:
first.reduce(worker)
return first.result
#最后把上面这些代码拼装到函数里面
def mapreduce(data_dir):
inputs = generate_inputs(data_dir)
worker = create_workers(inputs)
return execute(workers)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55#添加的generate_inputs类方法,该方法会根据通用的借口来创建新的InputData实例,
#新添加的generate_inputs方法,接受一份含有配置参数的字典,而具体的GenericInputData子类则可以解读这些参数
class GenericInputData(object):
def read(self):
raise NotImplementedError
@classmethod
def generate_inputs(cls, config):
raise NotImplementedError
#通过config字典来查询输入文件所在的目录
class PathInputData(GenericInputData):
#...
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))
#使用cls()形式的通用构造器,来构造具体的GenericWorker子类实例
class GenericWorker(object):
"""
这段代码的重点,是input_class.generate_inputs,它是个类级别的多态方法。此外,create_workers方法用另外一种方式够造了
GenericWorker对象,它是通过clas形式来构造的,而不是像以前那样,直接使用__init__方法
"""
#...
def map(self):
raise NotImplementedError
def reduce(self, other):
raise NotImplementedError
@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
#重写mapreduce函数
def mapreduce (worker_class, input_class, config):
workers = worker_class.create_workers(input_class, config)
return execute(workers) #调用到上第一个方法的executeEP31 用秒速福来改写需要复用的@property方法
使用@property修饰器,有个明显的缺点,就是不方便复用。像上面这种方法,每添加一门科目,就要多编写多一次@property的方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Homework(object):
def __init__(self):
self._grade = 0
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, value):
if not (0<= value <=100):
raise ValueError('Grade must be between 0 and 100')
self._grade = value
class Exam(object):
def __init__(sefl):
self._writing_grade = 0
self._math_grade = 0
@staticmethod
def _check_grade(value):
if not(0<= value <=100):
raise ValueError('Grade must be between 0 and 100')所以我们可以使用描述符的方法,再用字典来保存每个实例的状态1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18@property
def writing_grade(self):
return self._writing_grade
@writing_grade.setter
def writing_grade(self, value):
self._check_grade(value)
self._writing_grade = value
@property
def math_grade(self):
return self._math_grade
@math_grade.setter
def math_grade(self, value):
self._check_grade(value)
self._math_grade = value
ps 对于__set__方法的每个Exam实例来说,字典都会保存指向该实例的一份引用,这就导致该实例的引用计数无法降为0,从而使垃圾收集器无法将其回收,会引起内存泄露。
使用Python内置的weakref模块可以解决此问题,该模块提供的名为WeakKeyDictionary的特殊字典,特殊之处在于:如果运行期间系统发现这种字典所持有的引用,是整个程序里面指向Exam实例的最后一份引用,那么系统就会自动将该实例从字典的键中移除1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29from weakref import WeakKeyDictionary
class Grade(object):
def __init__(self):
self._values = WeakKeyDictionary()
def __get__(self, instance, instance_type):
if instance is None: return self
return self._values.get(instance, 0)
def __set__(self, instance, value):
if not (0 <= value <=100):
raise ValueErrror('Grade must be between 0 and 100')
self._values[instance] = value
class Exam(object):
math_grade = Grade()
writing_grade = Grade()
science_grade = Grade()
first_exam = Exam()
second_exam = Exam()
first_exam.writing_grade = 82
first_exam.science_grade = 83
second_exam.writing_grade = 92
second_exam.science_grade = 93
print(first_exam.writing_grade,second_exam.science_grade)
>>>
82 93
EP38 在线程中使用Lock来防止数据竞争
使用多线程时,会发生数据竞争现象,通过使用Lock类可以解决
原本的代码
1 |
|
修改为
1 |
|
EP39 用Queue来协调各线程之间的工作
在各种协调方式中,较为高效的一种,则是采用函数管线
工作原理,与制造业中的组装生产线相似。管线分为许多首尾相连的阶段,每个阶段都由一种具体的函数来负。程序总是把待处理的新不见添加到管线的开端。每一种函数都可以在它所负责的那个阶段内,并发地处理位于该阶段的部件。等负责本阶段的那个函数,把某个部件处理好之后,该部件就会传送到管线中的下一个阶段,以此类推,知道全部阶段都经理一遍。涉及阻塞式I/O操作或子进程的工作任务,尤其适用此方法处理,因为这样的任务蛮狠容易分配到多个Python线程或进程之中
在管线中,每个阶段的工作函数,其执行速度可能会有所差别,这就使得前一阶段可能会拖慢后一阶段的进度,从而令整条管线迟滞。后一个阶段会在其循环语句中,反复查询输入队列,以求获取新的任务,而前一个阶段又迟迟不能把任务交过来,于是就令后一个阶段陷入了饥饿。这样做的结果是:工作线程会白白地浪费CPU时间,去执行一些没有用的操作,也就是说,它们会持续地抛出并捕获IndexError异常
此外还有三个问题,也应该避免。首先,为了判断所有的任务是否都彻底处理完毕,我们必须再编写一个循环,持续判断done_queue队列中的任务数量。其次Worker线程的run方法,会一直执行其巡皇。即便到了退出的时候,我们也没有办法通知Worker线程停止这以循环
还有另外一个更严重的问题是,如果管线的某个阶段发送迟滞,那么随时都可能导致程序崩溃,若第一个阶段的处理速度很快,而第二阶段的处理速度较慢,则链接这两个阶段的哪个队列的容量会不断增大。第二阶段始终没有办法跟上第一阶段的节奏,这种现象持续一段时间后,程序就会因为收到大量的输入数据而耗尽内存,进而崩溃
在python中,可以用Queue类来弥补自编队列的缺陷
1 |
|
上面的代码中。Queue类是的工作线程无需再频繁地查询输入队列的状态,因为它的get方法会持续阻塞,直到有新的数据加入,所以会卡在queue.get()那里,调用Queue的put方法,给队列中放入一项任务,方能使queue.get()方法得以返回
1 |
|
Queue类可以通过限定队列中待处理的最大任务数量,使得相邻的两个阶段,通过队列平滑的衔接起来
1 |
|
上面这个代码是通过Queue类的task_done方法来追踪工作进度。有了这个方法,我们就不用再像原理那样。再管线末端的done_queue处进行轮训,而是可以直接判断:管线中的某个阶段,是否将输入对垒中的任务处理完毕
EP40 考虑用concurrent.futures来实现真正的平行计算
最好是符合两个条件。
1/运行的函数不需要与程序中的其他部分共享状态。
2/只需要在主进程与子进程之间传递一小部分数据,就能完成大量的运算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17import time
from concurrent.futures import ProcessPoolExecutor
def gcd(pair):
a, b = pair
low = min(a,b)
for i in range(low, 0, -1):
if a%i ==0 and b% i == 0:
return i
numbers = [(1963309, 2265973), (2030677, 3814172),
(1551645, 2229620), (2039045, 2020802)]
start = time.time()
pool = ProcessPoolExecutor(max_workers=2)
results = list(pool.map(gcd, numbers))
end = time.time()
print(end - start)
在双核电脑上。运行时间接近快一倍
ProcessPoolExecutor操作过程如下:
1.把numbers列表中的每一项数据都传给map
2.用pickle模块对数据进行序列化,将其变成二进制形式
3.通过本地套接字,将序列化之后的数据从主解释器所在的进程,发送到子解释器所在的进程。
4.接下来,在子进程中,用pickle对二进制数据进行反序列化操作,将其还原为python对象。
5.引入包含gcd函数的哪个python模块
6.各条子进程平行地针对各自的输入数据,来运行gcd函数
7.对运行结果进行序列化操作,将其转变为字节
8.将这些字节通过socket复制到主进程之中
9.主进程对这些字节执行反序列化操作,将其还原为python对象
10.最后,把每条子进程所求出的计算结果合并到一份列表之中,并返回给调用者
Class部分
由于class的我有些总记不住,所以写在这里,有空时可以看一看增加记忆
isinstance()函数
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
dir()函数
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法
1 |
|
getattr()、setattr()以及hasattr()
1 |
|
__ slots__
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性
1 |
|
这样就不能添加除了name,age外的属性了
如果有添加其他属性,将得到AttributeError的错误
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的
@property装饰器
使用@property装饰器后既能检查参数,又可以用类似属性这样简单的方式来访问类的变量
1 |
|
这样后就可以直接get或者set了
1 |
|
__ call__
可以让我们直接在实例本身上调用
1 |
|
1 |
|
__ repr__
__ repr__ 是一个专用的方法,在当调用 repr(instance) 时被调用。repr 函数是一个内置函数,它返回一个对象的字符串表示。它可以用在任何对象上,不仅仅是类的实例
__ cmp__
1 |
|
__ cmp__ 在比较类实例时被调用。通常,你可以通过使用 == 比较任意两个 Python 对象,不只是类实例。有一些规则,定义了何时内置数据类型被认为是相等的,例如,字典在有着全部相同的关键字和值时是相等的。对于类实例,你可以定义 __ cmp__ 方法,自已编写比较逻辑,然后你可以使用 == 来比较你的类,Python 将会替你调用你的 __ cmp__ 专用方法。
__ getslice__、__ setslice__、__ delslice__
该三个方法用于分片操作,如:列表
1 |
|
__ new__ 和 __ metaclass__
1 |
|
Django部分
随机抽取数据库数据
.objects.order_by(‘?’)[:2]来获取数据库数据,一般情况下性能并不差
关于DJango随机抽取数据性能说明
根据链接来获取数据库内容
前端的表单提取数据后,把选项封装成url向Django索要数据并封装为json给前端
代码来自以查房的代码
URL部分
Django根据正则来提取url中想要的关键字
1 |
|
view部分
就可以根据url来过得数据库的筛选条件
1 |
|
model部分
根据view的条件,从数据库获取数据
def get_absolute_url(self):里面的说明
注意到 URL 配置中的url(r’^mapjson_(?P
设定的 name=’map_json’在这里派上了用场。reverse 函数,它的第一个参数的值是 ‘building:map_json’,意思是 building 应用下的 name=map_json
的函数,由于上面通过 app_name = ‘building’ 告诉了 Django 这个 URL 模块是属于building 应用的,因此 Django 能够顺利地找到 blog 应用下
name 为 map_json 的视图函数,于是 reverse 函数会去解析这个视图函数对应的 URL
然后重点是。。。我设置错了,程序还能没有出现bug的运行,不过还是按照要求来,虽然有点乱
由于pk是view传过来的,所以这边不需要更改
1 |
|
利用模板限制在前端输出的字符
由于输出的文字长度不一,可以使用Django自带的方法判断是否大于某个长度,如果是就只输出定义的长度加…不是则输出全部
1 |
|
待整理
- 本文作者:So1n
- 本文链接:http://so1n.me/2017/11/07/17_python_notes/index.html
- 版权声明:本博客所有文章均采用 BY-NC-SA 许可协议,转载请注明出处!