我们刚开始学习面向对象编程时,都是从类的概念开始学起的。类是对具体事物的抽象,类的名称是一个名词,类中包含属性和方法。 从概念层面来看,类是对具体事物的抽象。从语法层面来看,类就是将数据和行为打包。如果我们活学活用,其实也可以把类当做一个函数来使用,或者说,把一个函数封装成一个类。把函数封装成类具有许多好处。首先,我们可以在类的初始化函数中保存部分参数,从而减少函数调用时传递的参数个数;其次,我们可以将一个大的函数拆分成若干个小的函数,从而提高代码的可读性;最后,我们将多个紧密相关的函数组织在同一个命名空间下,有利于代码维护。 我们已经理解了将一个函数组织成类的好处,接下来的问题是,如何把一个类的对象当成普通函数来调用呢?在Python中,可以通过运算符重载来实现。Python的运算符重载通过特殊的命名来完成。其中,方法将对象变成一个可调用的对象。只要在类中实现了方法,我们就可以像普通函数那样调用一个类对象了。
有了上述基本认知之后,我们就看一个例子吧。代码如下:
import timefrom functools import wrapsclass Timeit: def __init__(self, fn): self.fn = fn def __call__(self, *args, **kwargs): start = time.time() ret = self.fn(*args, **kwargs) print(time.time() - start) return ret@Timeitdef sleep(): # sleep函数将赋值给Timeit类的fn属性 time.sleep(3) return 1# 拆解开来,等价于# sleep = Timeit(sleep)# 调用sleep()方法>>> sleep()3.0007045269012451上述代码其实使用类来实现了对函数的装饰。
如何理解呢?
from functools import wrapsdef fn(): passdef wrap(): passwraps(fn)(wrap)f = wraps(fn)(wrap)dir(f)['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__wrapped__']f.__name__fnwrap.__name__fndef f(): '''this is f''' passdef g(): '''this is g''' passf.__name__'f'f.__doc__'this is f'g.__name__'g'g.__doc__'this is g'wraps(f)(g) # 把f的一些属性复制给gg.__name__'f'g.__doc__'this is f'# 柯里化函数# 把f的一些属性,复制给g# Timeit还可以这样写import timefrom functools import wrapsclass Timeit: def __init__(self, fn): wraps(fn)(self) def __call__(self, *args, **kwargs): start = time.time() ret = self.__wrapped__(*args, **kwargs) print(time.time() - start) return ret经过修改之后,
import timefrom functools import wrapsclass Timeit: def __init__(self, fn): self.wrapped = wraps(fn)(self) def __call__(self, *args, **kwargs): start = time.time() ret = self.wrapped.__wrapped__(*args, **kwargs) # wrapped.__wrapped__ = fn print(time.time() - start) return ret@Timeitdef add(x, y): """ret x + y """ return x + yadd.__name__'add'add.__doc__'ret x + y'add(2, 3)0.05单例类的装饰器,
class Singleton: def __init__(self, cls): wraps(cls)(self) print(cls) self.instance = None print(self.instance) def __call__(self, *args, **kwargs): print(self.instance) if self.instance is None: self.instance = self.__wrapped__(*args, **kwargs) print(self.instance) return self.instance @Singletonclass A: """this is A""" passNonea1 = A()Nonea2 = A()a1 is a2Truea = A()a.__doc__'this is A'当调用实例时,使用方法。这并不是循环定义:如果定义了,Python就会为实例应用函数调用表达式运行方法。这样可以让类实例的外观和用法类似于函数。
class Callee: def __call__(self, *args, **kwargs): print('Called: ', args, kwargs) c = Callee()c(1, 2, 3) # c现在是可调用的Called: (1, 2, 3) {}c(1, 2, 3, x=4, y=5)Called: (1, 2, 3) {'y': 5, 'x': 4}方法支持适合函数的所有的参数传递。传递给实例的任何内容都会传递给该方法,包括通常隐式的实例参数。
再看一个例子:
class Prod: def __init__(self, value): # 只接收一个参数 self.value = value def __call__(self, other): return self.value * other In [47]: x = Prod(3) # 把3保留在状态中In [48]: x(4) # 4(传递) * 3(状态)Out[48]: 12In [49]: x(5)Out[49]: 15接下来看看可调用对象。可以通过小括号的方式调用的对象。
class F: passfn = F()fn_dir = set(dir(fn))class A: passa = A()a_dir = set(dir(a))fn_dir.difference(a_dir)# set() # 得到一个空集合In [9]: class Add: ...: def __call__(self, a, b): ...: print('{} + {}'.format(a, b)) ...: return a + b ...: In [10]: add = Add()In [11]: add(1, 2)1 + 2Out[11]: 3>>> class Adder:... def __call__(self, x, y):... return x + y... ... >>> Adder()(3, 5)8In [15]: callable(fn)Out[15]: FalseIn [16]: callable(lambda x: x)Out[16]: True>>> def fn():... pass... >>> fn.__call__>>> fn.__class__>>> class Fn:... def __call__(self):... print('{} called'.format(self))... ... ... >>> f = Fn()>>> f() called>>> callable(f)True当一个对象,只要实现方法,就可以通过小括号(像函数调用一样)来调用。当调用一个可调用对象时,实际上调用的方法。所有函数都是可调用对象。所有的函数都是的实例。
看一个不可调用的对象,
In [17]: class NotCallable: ...: pass ...: In [18]: nc = NotCallable()In [19]: nc()---------------------------------------------------------------------------TypeError Traceback (most recent call last) in ()----> 1 nc()TypeError: 'NotCallable' object is not callableIn [20]: callable(nc)Out[20]: FalseIn [21]: dir(NotCallable.__init__)Out[21]: ['__call__', '__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__objclass__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__text_signature__']内置函数callable用于判断一个对象是否是可调用对象。
再看一个小例子:
In [22]: class Adder: ...: def __call__(self, x, y): ...: return x + y ...: In [23]: print(Adder()(3, 5)) # 这里的两个括号,第一个小括号相当于实例化类Adder,第二个相当于方法调用,调用了`__call__` 方法8再看缓存的另一种写法:
import datetimeimport inspectfrom functools import wrapsclass Cache: def __init__(self, size=128, expire=0): self.size = size self.expire = expire self.data = {} @staticmethod def make_key(fn, args, kwargs): ret = [] names = set() params = inspect.signature(fn).parameters keys = list(params.keys()) for i, arg in enumerate(args): ret.append((keys, arg)) names.add(keys) ret.extend(kwargs.keys()) for k, v in params.items(): if k not in names: ret.append((k, v.default)) ret.sort(key=lambda x: x[0]) return '&'.join(['{}={}'.format(name, arg) for name, arg in ret]) def __call__(self, fn): @wraps(fn) def wrap(*args, **kwargs): key = self.make_key(fn, args, kwargs) now = datetime.datetime.now().timestamp() if key in self.data.keys(): value, timestamp, _ = self.data[key] if expire == 0 or now - timestamp = self.size: # 过期清理 if self.expire != 0: expires = set() for k, (_, timestamp, _) in self.data.items(): if now - timestamp >= self.expire: expires.add(k) for k in expires: self.data.pop(k) if len(self.data) >= self.size: # 换出 k = sorted(self.data.items(), key=lambda x: x[1][2])[0][0] self.data.pop(k) self.data[key] = (value, now, now) return value return wrap@Cache()def add(x, y): return x + yadd(1, 2)再看单例类的另一种实现方法,
class Singleton: def __init__(self, cls): self.cls = cls self.instance = None def __call__(self, *args, **kwargs): if self.instance is None: self.instance = self.cls(*args, **kwargs) return self.instance@Singletonclass A: passa = A()a1 = A()a is a1True 用来实现可调用对象,和闭包是殊途同归的,通常是为了封装一些内部状态。非常复杂的装饰器,使用类来写,可以方便拆分逻辑。
好了,今天就到这里,下次再见。
|
|