卿哥聊技术

导航

 

笔者的上一篇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__显得更简洁。

 

posted on 2018-03-25 11:33  卿哥聊技术  阅读(158)  评论(0编辑  收藏  举报