笔者的上一篇python文章阅读量不错,看来python爱好者很多,所以再写一篇,以飨读者。
先接着上一篇讲一个问题,下面这段code有没有问题?
def countcalls(func): count = 0 def wrapper(*args, **kwargs): count += 1 print('num of calls: {}'.format(count)) return func(*args, **kwargs) return wrapper @countcalls def foo(x): print (x+1) foo(1)
运行时会发现:
UnboundLocalError: local variable 'count' referenced before assignment
原因是count在wrapper下面除非global,不然是不可见的,那么就没有初始化了。但是这是不能加global的,因为它不是global,如果移出函数外,那么结果又不对了。怎么解决呢?
python3的解决方案:
nonlocal count count += 1
python2的解决方案:
def countcalls(func): def wrapper(*args, **kwargs): wrapper.count += 1 print('num of calls: {}'.format(wrapper.count)) return func(*args, **kwargs) wrapper.count = 0 return wrapper
基于class的decorator
class PrintLog: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print ('CALLING: {}'.format(self.func.__name__)) return self.func(*args, **kwargs)
主要是__call__决定的,任何object只要定义了__call__方法就能当函数用。下面对比一下:
class PrintLog: def __init__(self, func): self.func = func def __call__(self, *args, **kwargs): print ('CALLING: {}'.format(self.func.__name__)) return self.func(*args, **kwargs) def printlog(func): def wrapper(*args, **kwargs): print("CALLING: " + func.__name__) return func(*args, **kwargs) return wrapper @printlog def f(n): return n+2 @PrintLog def f_class(n): return n+2
CALLING: f 5 CALLING: f_class 5
完全等效。__call__就相当于wrapper function
magic methods
相当于是python中的syntax sugar,让+,-,*,/啥的拿过来就用,举例如下:
class Angle: def __init__(self, value): self.value = value % 360 def __add__(self, other_angle): return Angle(self.value + other_angle.value) a = Angle(45) b = Angle(90) c = a + b print (c.value)
135
常用方法如下:
数学运算 __add__ a + b __sub__ a - b __mul__ a * b __truediv__ a/b(浮点除) __mod__ a % b __pow__ a ** b 位运算 __lshift__ a << b __rshift__ a >> b __and__ a & b __xor__ a ^ b __or__ a | b
比较运算
__eq__ a == b
__ne__ a != b
__lt__ a < b
__le__ a <= b
__gt__ a > b
__ge__ a >= b
举个例子:
class Money(object): def __init__(self, dollar, cent): self.dollars = dollar self.cents = cent def __str__(self): return "$" + str(self.dollars) + "." + "{:02}".format(self.cents) def __repr__(self): return "Money(" + str(self.dollars) + ", " + str(self.cents) + ")" def __add__(self, other): cents = (self.cents + other.cents) % 100 dollars = self.dollars + other.dollars + (self.cents + other.cents)/100 return Money(dollars, cents) def __sub__(self, other): if self.cents < other.cents: cents = 100 + self.cents - other.cents dollars = self.dollars - other.dollars - 1 else: cents = self.cents - other.cents dollars = self.dollars - other.dollars return Money(dollars, cents) def __mul__(self, other): cents = (self.cents * other) % 100 dollars = self.dollars * other + (self.cents * other) / 100 return Money(dollars, cents) def __eq__(self, other): return self.dollars == other.dollars and self.cents == other.cents def __ge__(self, other): return self.dollars >= other.dollars and self.cents >= other.cents def __lt__(self, other): return self.dollars < other.dollars and self.cents < other.dollars
__str__和__repr__也是会时常用到的方法,它们都会放回string。__str__被用在print()时,同时str()也会调用它。而__repr__则是告诉你如何重现这个object,python命令行交互中输入object会调用__repr__,同时repr会调用它。
python collection类型
__getitem__相当于[index]
据两个例子大家就明白了,第一个例子是关于list,第二个例子是针对dict
class UniqueList: def __init__(self, items): self.items = [] for item in items: self.append(item) def append(self, item): if item not in self.items: self.items.append(item) def __getitem__(self, index): return self.items[index] ul = UniqueList([2,2,2,3,3,3,4,4,4]) print ul[0] print ul[1] print ul[2]
2 3 4
当然在具体实现的过程中对于index 要有input check,有问题需要raise IndexException,negative index要考虑语义的支持。
class MultiDict: def __init__(self): self.data = {} def insert(self, key, value): if key not in self.data: self.data[key] = [] self.data[key].append(value) def get_values(self, key): return self.data[key] def get(self, key): return self.get_values(key)[-1] def __getitem__(self, key): return self.get_values(key)[-1] md = MultiDict() md.insert("a", 1) md.insert("a", 2) print md['a']#2
Iterator
回到list那个例子,你觉得下面这个语法能够工作吗?
for i in ul: print i
答:可以的,通过__getitem__我们不小心实现了iterator,事实上只要__getitem__能够接受0,1,2,... 并且在访问越界的时候raise IndexException,就相当于实现了iterator
Iterator的另一种实现是:
def __iter__(self): return self def __next__(self): ... raise StopIteration return ...
可见__getitem__显得更简洁。