Python惯用法和技巧等

字符串格式化

format格式化

支持位置和字典参数格式化。(不支持同时位置和字典参数形式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'{0} {0}'.format(1)
>>> 1 1
'{0} {0}'.format("hello")
>>>'hello hello'
'{0} {1}'.format('world') #不能有多余的大括号
>>>IndexError: tuple index out of range
'{var}'.format(var=1) >>>1
'{var}'.format(var="hello")
>>>'hello'
'{ var }'.format(var = 1)
>>> KeyError: ' var '
'{}{world}'.format('hello')
>>>KeyError: 'world' #不能格式化目的字符串有大括号的字符串

%百分号格式化

支持类型、位置和字典参数格式化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
'%s" % "this is c type'
>>>'this is c type'
'%(var)s, %(num)d' % {'var':'string', 'num':101}#注意这个可以是字符串
>>>'string, 101'
'%r %r' % ('string', 101)
>>>"'string' 101' #%r打印出原始对象,字符会带分号“”; 适合用在MySQL中作为value, 以及不知道对象的类型
'%0.2f' % 1.1111 #c类型的格式化
>>>'1.11'
'%-02d' % 1
>>>'1 '
'%02d' % 1
>>>'01'
'%02d' % 101
>>>'101'

()括号的作用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
t = (1, 2) #tuple
>>>(1, 2)
2 * (1 + 3) #优先级
>>> 8
(1) #作用域, 所以单元素的tuple定义需要尾部的逗号,例如 (1,)
>>>1
type(('a')) #('a') -> 'a', type('a') -> str
>>>str
if (1 + 1):print 'ok' #if(1 + 1), 括号相当于作用域
>>>ok
('not'
'a tuple, but str') #连接符
>>>'not a tuple, but str'

iter函数

  • iter(collections) -> iterator
  • iter(callable, stop) -> iterator
    返回一个迭代器,反复调用callabe函数直到其返回stop
1
2
3
4
5
6
from functools import partial
with open('inputfile', 'rb') as infile:
read64 = partial(infile.read, 64)
blocks = list(iter(read64, '')) # 以64byte为1个block,存入blocks
#lines = list(iter(file.readline, '')) # file.readlines()

类的私有方法

__method以双下划线开头的方法

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
class TestPrivateMethod(object):
def __private(self):
print 'private'
def public(self):
print 'call in public:', self.__private() #在实例内可以调用
@classmethod
def class(cls):
print 'call in class:', cls.__private() #在类内可以调用
p = TestPrivateMethod()
p.public()
>>>'call in public: private'
TestPrivateMethod.class()
>>>'call in class: private'
p.__private()
>>>AttributeError: 'TestPrivateMethod' class has not attribute '__private'
TestPrivateMethod.__private()
>>>AttributeError: 'TestPrivateMethod' class has not attribute '__private'
p._TestPrivateMethod__private()
>>>'private' #python只是将__开头的私有方法改名,从而达到隐藏的目的

类的私有属性,不能通过实例修改。

类似全局变量和私有变量的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Test(object):
attr_in_class = 1
def get(self):
print self.attr_in_class
def set(self):
self.attr_in_class = 'set'
def assign(self):
self.attr_in_class += 1
def set_in_class(self):
self.__class__.attr_in_class = 'class'
@classmethod
def change_in_class(cls):
cls.attr_in_class = 'cls'
Test.attr_in_class
>>>1
ins = Test()
ins.attr_in_class
>>>1

修改类的类变量有两种方式

第一种方式通过类直接修改

1
2
3
4
5
6
7
8
9
Test.change_in_class() #类方法实际是修改类(type的实例)的属性
Test.attr_in_class
>>>'cls'
ins.attr_in_class
>>>'cls'
Test().attr_in_class
>>>'cls' #类的共享变量被修改

第二种方式通过实例的class 修改。

1
2
3
4
5
ins.set_in_class()
>>>'class'
Test.attr_in_class
>>>'class'

当通过实例为 类属性 赋新值时,实际上是创建了一个新的实例属性,不会修改类属性。类似在函数内直接为全局变量赋值,不能修改全局变量,而是创建同名的局部变量

1
2
3
4
5
6
7
8
9
ins.attr_in_class = 'instance'
Test.attr_in_class
>>>'class'
Test().attr_in_class
>>>'class'
in.attr_in_class
>>>'instance' #attr_in_class通过赋值,变为ins的实例变

当然,如果类属性是一个可变变量,例如 [], {} 可以调用对应的方法修改内部元素,而不会创建实例变量

1
2
3
4
5
6
7
8
9
10
11
12
13
class TestMutable(object):
mut = []
t = TestMutable()
t.mut.append(1)
t.mut
>>>[1,]
TestMutable.mut
>>>[1,]
TestMutable().mut
>>>[1,]

类属性的最佳使用方式是,创建所有实例共享的变量,并且不会通过实例修改;或者通过 isntance.__class.var的方式显式的进行修改

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
class SumInstanceNum(object):
cnt = 0
def __init__(self):
self.__class__.cnt += 1
#do other construct thing
@classmethod
def get_instance_num(cls):
return cls.cnt
def __del__(self):
self.__class__.cnt -= 1
```
## 浅copy 和深copy
* 浅复制(shallow copy):只复制最外层对象,不复制内部的子对象(只复制引用)。属于浅复制的有,赋值=、裂片操作[:]、函数copy.copy等。
* 深复制(deep copy): 复制所有,包括对象(类,实例等)的内部对象。属于深复制的是copy.deepcopy
特殊方法 `__copy__` 定义浅复制的操作。
```python
# 浅复制
top = [1, 2, ['a', 'b']]
sec = top[:]
print top.append(3)
>>>[1, 2, ['a', 'b'], 3];
print sec
>>>[1, 2, ['a', 'b']];
top[2].append('c')
print top
>>>[1, 2, ['a', 'b', 'c'], 3];
print sec
>>>[1, 2, ['a', 'b', 'c']]; #同样增加元素 'c'
top[2] is sec[2]
>>>True
# 深复制
deep = [1, 2, ['a', 'b']]
deep2 = copy.deepcopy(deep)
deep[2] is deep2[2]
>>>False

函数参数在定义时实例化,可变参数被共享

1
2
3
4
5
6
7
8
def foo(numbers=[]):
numbers.append(9)
print numbers
foo()
>>>[9]
foo()
>>>[9, 9]
  • 参数numbers在定义时候实例化,并且默认绑定,等于每次传入相同的实例化对象。
  • 更好的实践方式,定义默认的不可变参数,然后处理
1
2
3
4
def foo(numbers=None):
numbers = [] if numbers is None else numbers
numbers.append(9)
print numbers

list列表

list 支持+;不支持-操作符

1
2
3
4
5
6
7
8
la = [1, 2,]
lb = [3, 4,]
la + lb
>>>[1, 2, 3, 4]
lc = [1,]
la - lc
>>>TypeError: unsupported operands types(s) for '-': 'list' and 'list'

输出格式化的文本的第x行的第y列

类似 grep ‘cpu0’ input_file |cut -d” “ -f2 命令, 选取cpu0行的第2个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import re
def get_field(data, field, linestart="", sep=" "):
'''
Parse data from string
@param data: Data to parse
example:
data:
cpu 1 2 3 4
cpu0 5 6 7 8
cpu1 9 10 11 12
linestart filed 0 1 2 3
@param field: Position of data after linestart
@param linestart: String to which start line
@param sp: separator between parameters regular expression
'''
search = re.compile(r'(<=^%s)\s*(.*)' % linestart, re.MULTILINE)
find = search.search(data)
if find is not None:
return re.split("%s" % sep, find.group(1))[field]
else:
return None

__slots__减少实例内存使用,

__slots__ 会阻止class建立 __dict__ 属性,即阻止实例动态的添加属性,从而减少创建 __dict__ 的内存消耗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#save bits to create __dict__
class Slot(object):
__slots__ = []
s = Slot()
try:
s.var = 1
except AttributeError:
print "can not create attribute, __slot__ stops it"
try:
s.__dict__
except AttributeError:
print "no __dict__ attribute, __slot__ stops it"

一颗树结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import json
from collections import defaultdict
def tree(): return defaultdict(tree)
t = tree()
t['a']['b']['c'] = 1
t['a']['c'] = 2
print t['a']['b']['c']
>>>1
print t['a']['c']
>>>2
print json.dumps(t)
>>> {'a':{'b':{'c':1}, 'c':2}}
def dicts(t):
return {k: dicts(t[k]) for k in t}

context manager上下文管理器

withas关键词是自动上下文管理的语法支持。

1
2
with open('./temp', 'w') as outfile:
outfile.write('this is a test for context manager')

file 类文件对象均支持 Context Manager。一个类实现 Context Manager的方法是利用 __enter____exit__ 特殊方法。实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class CM(object):
def __enter__(self):
print 'in enter'
return 'the object to be managered` #返回的值即为被管理的对象,通过`as`赋值给其他名字。
def __exit__(self, exc_type, exc_value, exc_tb):
print 'exit'
return True #返回True,则在`with`程序块中出现的异常不会向外层raise,即异常被忽略(非常不建议这样做)
with CM() as ret:
print 'in block'
print ret
>>>in enter #在程序__enter__运行
>>>in block #在block中运行
>>>the object to be managered #__enter__返回,as赋值给ret
>>>exit #在__exit__运行。

需要注意的点:

  • a)如果在 __enter__内发生异常,则程序不进入context manager机制,直接抛出异常。
1
2
3
4
5
6
7
8
9
10
class EnterException(object):
def __enter__(self):
raise IOError('test') #用IOError模拟异常
def __exit__(self, *args):
print 'exit'
return True
with EnterException():
print 'in block'
>>>IOError: 'test' #程序直接抛出异常,
  • b)如果在block内发生异常, __exit__将捕获异常。如果不返回True,则异常向上层传递。如果返回True,则异常被忽略。
1
2
3
4
5
6
7
8
9
10
11
12
class IngoreException(object):
def __enter__(self):
print 'enter'
def __exit__(self, *args):
print args
return True
with IngoreException():
raise IOError('will be slienced')
>>>enter
>>>[IOError, IOError('will be slienced'), traceback]
>>> #不会有异常被抛出。

如何对函数进行 context manager

利用 contexlib 模块, try...finally...yield语法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import contexlib
@contexlib.contexmanager
def go():
print 'before try'
try: #__enter__
print 'in enter'
yield 'ret value' # return
except: # __exit__
print 'in exit'
with go() as ret:
print 'in block'
print ret
>>>before try
>>>in enter
>>>in block
>>>ret value
>>>in exit

同时有多个对象需要context manager

例如复制一个文件内容到另一个文件。

1
2
with open('infile', 'r') as infile, open('outfile', 'w') as outfile:
outfile.write(infile.read())

descriptor描述符

描述符是将一个方法转为属性的便捷方式。descriptor类必须包含至少一个 __get__, __set__, __delete__ 特殊方法。 __get__描述一个属性取值时的表现; __set__描述一个属性赋值时的表现, __delete__描述一个属性被删除时的表现。

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
class Descriptor(object):
def __init__(self, v):
self.value = v
def __get__(self, instance, owner):
print 'instance:', instance
print 'owner:', owner
return self.value
def __del__(self, instance, owner):
del self.value
def __set__(self, instance, value):
self.value = value
class User(object):
value = Descriptor(0.0)
print User.value #类调用value,instance为None,owner为 User类, value所在的类。
#value的属性被改变,是一个描述符,而不是类的属性。
#(或者说,这个类属性的取值、赋值等行为被改写)
>>>instance None
>>>owner <class '__main__.User'>
>>>0.0
u = User()
print u.value #实例调用value, instance为User的实例, owner为 User类。
>>>instance <__main__.User object at 0x00000000046C7748>
>>>owner <class '__main__.User'>
>>>0.0

更常见的情况下,是将两个相互联系的属性通过descriptor连接起来。

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
class Meter(object):
'''Descriptor for a meter.'''
def __init__(self, value=0.0):
self.value = float(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = float(value)
class Foot(object):
'''Descriptor for a foot.'''
def __get__(self, instance, owner):
return instance.meter * 3.2808
def __set__(self, instance, value):
instance.meter = float(value) / 3.2808
class Distance(object):
'''Class to represent distance holding two descriptors for feet and meters.'''
meter = Meter()
foot = Foot()
d = Distance()
d.meter #d.meter --> Distance.__getattribute__('meter') --> Meter.__get__(meter, d, Distance) --> meter.value --> 0.0
>>>0.0
d.foot
>>>0.0
d.meter = 100
print d.meter, d.foot
>>>(100, 3048.0370641306997)
d.foot = 100
print d.meter, d.foot
>>>(30.480370641306997, 100)

或者作为装饰器修饰类的一个方法,改变其默认行为。

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
def memeto(obj, deep=False):
"""memeto is a decrotor to store the attributes"""
"""False == 0; True == 1"""
state = (copy.copy, copy.deepcopy)[deep](obj.__dict__)
def restore():
obj.__dict__.clear()
obj.__dict__.update(state)
return restore
class Transactional(object):
def __init__(self, method):
self.method = method
def __get__(self, instance, owner):
def transaction(*args, **kwargs):
self.memeto = memeto(instance)
try:
self.method(*args, **kwargs)
except:
self.memeto()
return transaction
class NumObj(object):
def __init__(self, v):
self.value = v
def increase(self):
self.value += 1
@Transactional
def DoStuff(self):
self.value = 's'
self.value += 1 #will fail
print 'error'
n = NumObj(0)
n.DoStuff()
print n.value #value is restored by Transactional
>>>0

利用__getattr__setattr 缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Cache(object):
def __getattr__(self, real_name):
print 'through getattr to get attribute %r' % real_name
value = str(real_name)
setattr(self, real_name, value)
return value
cache = Cache()
cache.__dict__
>>>{}
cache.a
>>>through getattr to get attribute 'a'
>>>a
cache.__dict__
>>>{'a':'a'}
cache.a
>>>a

第二次获取属性的时候没有通过 __getattr__ 方法获取,直接获取的属性。

iter迭代器的妙用

iter迭代器中的每个元素只会被迭代一次,可以通过innext消耗。

1
2
3
4
5
6
7
8
9
10
11
12
# 输出字符串中特殊字符的下一个字符
def print_next_char(s, ch):
s_iter = iter(s)
for c in s_iter:
if c == ch:
try:
print next(s_iter)
except StopIteration:
pass
print_next_char('abadeac', 'a') # b d c

通过slice函数对迭代器进行组合

1
2
3
4
5
i = iter([1, 2, 3, 4, 5])
zip(i, i) # [(1, 2), (3, 4)]
i = iter([1, 2, 3, 4, 5])
zip(i, i, i) # [(1, 2, 3,)]

如果使用map函数,可以产生所有组合,不足的使用None补充

1
2
i = iter([1, 2, 3, 4, 5])
map(None, i, i) # [(1, 2), (3, 4), (5, None)]

再结合s[start:] = s[start:None]的特性就可以输出给定序列的片段。

1
2
3
4
5
6
7
def print_slice(s, iss):
iter_is = iter(iss)
for start, end in map(None, iter_is, iter_is):
print s[start:end],
print_slice('abcde', [0, 1, 2, 7]) # a cde

flock超时函数(stackoverflow

利用linux中IO堵塞模型中遇到信号返回EINTR,利用Python IO模型中不成功返回IOError

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
import signal, errno
from contextlib import contextmanager
import fcntl
@contextmanager
def timeout(seconds):
def timeout_handler(signum, frame):
pass
original_handler = signal.signal(signal.SIGALRM, timeout_handler)
try:
signal.alarm(seconds)
yield
# 清理部分,获取或者超时都恢复信号
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, original_handler)
with timeout(1):
f = open("test.lck", "w")
try:
# 如果不能获取就堵塞
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
except IOError, e:
# SIGALRM到达errno.EINTR产生, 其他报错
if e.errno != errno.EINTR:
raise e
print "Lock timed out"

这段代码逻辑清楚,而且对获取锁、超时、其他异常的错误处理都很完美,并且体现了对linux和python都有很深的掌握