Lazy evaluation
是一段源码,关于Lazy evaluation的,看了很久才懂,记录一下
一,lazy方法返回的比较复杂,一层一层将其剥开。
- wraps(func)跳转到curry(update_wrapper, func, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES),最后return一个闭包,通过type(wraps(func))可以看到这是一个<function _curried>。
- wraps(func)(__wrapped)则会调用_curried(__wrapped__),进而调用update(func, __wrapped__ ,WRAPPER_ASSIGNMENTS, WRAPPER_UPDATE),这里注意WRAP这两个都是dict,所以会被收纳进**kwargs中。
- 通过update函数会把func的__module__,__name__,__doc__内容复制到<function __wrapper__>中,这样type(wrap(func)(__wrapper__))时,会返回一个<function func>。
这样lazy的返回就解析完了,这时候我们获得了一个__proxy__的构造函数。
1 def curry(_curried_func, *args, **kwargs): 2 def _curried(*moreargs, **morekwargs): 3 return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs)) 4 return _curried 5 6 WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__') 7 WRAPPER_UPDATES = ('__dict__',) 8 def update_wrapper(wrapper, 9 wrapped, 10 assigned = WRAPPER_ASSIGNMENTS, 11 updated = WRAPPER_UPDATES): 12 """Update a wrapper function to look like the wrapped function 13 14 wrapper is the function to be updated 15 wrapped is the original function 16 assigned is a tuple naming the attributes assigned directly 17 from the wrapped function to the wrapper function (defaults to 18 functools.WRAPPER_ASSIGNMENTS) 19 updated is a tuple naming the attributes off the wrapper that 20 are updated with the corresponding attribute from the wrapped 21 function (defaults to functools.WRAPPER_UPDATES) 22 """ 23 for attr in assigned: 24 try: 25 setattr(wrapper, attr, getattr(wrapped, attr)) 26 except TypeError: # Python 2.3 doesn't allow assigning to __name__. 27 pass 28 for attr in updated: 29 getattr(wrapper, attr).update(getattr(wrapped, attr)) 30 # Return the wrapper so this can be used as a decorator via curry() 31 return wrapper 32 33 def wraps(wrapped, 34 assigned = WRAPPER_ASSIGNMENTS, 35 updated = WRAPPER_UPDATES): 36 """Decorator factory to apply update_wrapper() to a wrapper function 37 38 Returns a decorator that invokes update_wrapper() with the decorated 39 function as the wrapper argument and the arguments to wraps() as the 40 remaining arguments. Default arguments are as for update_wrapper(). 41 This is a convenience function to simplify applying curry() to 42 update_wrapper(). 43 """ 44 return curry(update_wrapper, wrapped=wrapped, 45 assigned=assigned, updated=updated) 46 class Promise(object): 47 """ 48 This is just a base class for the proxy class created in 49 the closure of the lazy function. It can be used to recognize 50 promises in code. 51 """ 52 pass 53 54 def lazy(func, *resultclasses): 55 """ 56 Turns any callable into a lazy evaluated callable. You need to give result 57 classes or types -- at least one is needed so that the automatic forcing of 58 the lazy evaluation code is triggered. Results are not memoized; the 59 function is evaluated on every access. 60 """ 61 class __proxy__(Promise): 62 """ 63 Encapsulate a function call and act as a proxy for methods that are 64 called on the result of that function. The function is not evaluated 65 until one of the methods on the result is called. 66 """ 67 __dispatch = None 68 69 def __init__(self, args, kw): 70 self.__func = func 71 self.__args = args 72 self.__kw = kw 73 if self.__dispatch is None: 74 self.__prepare_class__() 75 76 def __prepare_class__(cls): 77 cls.__dispatch = {} 78 for resultclass in resultclasses: 79 cls.__dispatch[resultclass] = {} 80 for (k, v) in resultclass.__dict__.items(): 81 if hasattr(cls, k): 82 continue 83 setattr(cls, k, cls.__promise__(resultclass, k, v)) 84 cls._delegate_str = str in resultclasses 85 cls._delegate_unicode = unicode in resultclasses 86 assert not (cls._delegate_str and cls._delegate_unicode), "Cannot call lazy() with both str and unicode return types." 87 if cls._delegate_unicode: 88 cls.__unicode__ = cls.__unicode_cast 89 elif cls._delegate_str: 90 cls.__str__ = cls.__str_cast 91 __prepare_class__ = classmethod(__prepare_class__) 92 93 def __promise__(cls, klass, funcname, func): 94 # Builds a wrapper around some magic method and registers that magic 95 # method for the given type and method name. 96 def __wrapper__(self, *args, **kw): 97 # Automatically triggers the evaluation of a lazy value and 98 # applies the given magic method of the result type. 99 res = self.__func(*self.__args, **self.__kw) 100 for t in type(res).mro(): 101 if t in self.__dispatch: 102 return self.__dispatch[t][funcname](res, *args, **kw) 103 raise TypeError("Lazy object returned unexpected type.") 104 105 if klass not in cls.__dispatch: 106 cls.__dispatch[klass] = {} 107 cls.__dispatch[klass][funcname] = func 108 return __wrapper__ 109 __promise__ = classmethod(__promise__) 110 111 def __unicode_cast(self): 112 return self.__func(*self.__args, **self.__kw) 113 114 def __str_cast(self): 115 return str(self.__func(*self.__args, **self.__kw)) 116 117 def __cmp__(self, rhs): 118 if self._delegate_str: 119 s = str(self.__func(*self.__args, **self.__kw)) 120 elif self._delegate_unicode: 121 s = unicode(self.__func(*self.__args, **self.__kw)) 122 else: 123 s = self.__func(*self.__args, **self.__kw) 124 if isinstance(rhs, Promise): 125 return -cmp(rhs, s) 126 else: 127 return cmp(s, rhs) 128 129 def __mod__(self, rhs): 130 if self._delegate_str: 131 return str(self) % rhs 132 elif self._delegate_unicode: 133 return unicode(self) % rhs 134 else: 135 raise AssertionError('__mod__ not supported for non-string types') 136 137 def __deepcopy__(self, memo): 138 # Instances of this class are effectively immutable. It's just a 139 # collection of functions. So we don't need to do anything 140 # complicated for copying. 141 memo[id(self)] = self 142 return self 143 144 def __wrapper__(*args, **kw): 145 # Creates the proxy object, instead of the actual value. 146 return __proxy__(args, kw) 147 148 return wraps(func)(__wrapper__)
二,我们现在学着怎么使用lazy。
- 这里我们新建了两个类,分别为A、B,其中B是A的子类。
- 当调用x = lazy(func, A, B)(1, 2)的时候,会返回一个__proxy__对象,这里注意,因为类的定义是在lazy方法中,所以__func为func。
- 这中间会调用__prepare_class__方法,这是一个类方法。他会从resultclasses(这里是A,B)中逐个选择,它维护一个类字典cls.__dispatch,对其中每一个类都把类的__dict__中不包括__main__、__name__、__doc__的属性都经过__promiss__闭包将__wrap__赋给__proxy__类。
- 在__wrap__执行之前,会在cls.__dispatch中添加一个名为funcname的属性为func。而当调用__wrap__的时候,则会使用func计算初始的*args和**kwargs,解析返回对象的类型列表。
这里要注意一点,任何一个类的__dict__是不会自动包括它父类的属性和方法的,所以最后结果,x.func_a()是会报错的。这应该是这段代码的一个不足或者BUG吧。
1 def func(a, b): 2 print 'func' 3 return B() 4 5 class A(object): 6 """docstring for A""" 7 def __init__(self): 8 super(A, self).__init__() 9 def func_a(self): 10 print 'in func_a' 11 12 class B(object): 13 """docstring for B""" 14 def __init__(self): 15 super(B, self).__init__() 16 def func_b(self): 17 print 'in func_b' 18 19 print B.mro() 20 x = lazy(func, A, B)(1, 2) 21 print x.func_a(), x.func_b()
1 #[<class '__main__.B'>, <class '__main__.A'>, <type 'object'>] 2 #func 3 #in func_b 4 #None
三,怎样实现lazy evaluation?
将上面的代码改一下便可以实现lazy evaluation。
1 def func(num1, num2): 2 return B(num1, num2) 3 4 class A(object): 5 """docstring for A""" 6 def __init__(self): 7 super(A, self).__init__() 8 def func_a(self): 9 pass 10 11 class B(A): 12 """docstring for B""" 13 def __init__(self, num1, num2): 14 super(B, self).__init__() 15 self.num1 = num1 16 self.num2 = num2 17 18 def func_b(self, num1, num2): 19 print num1 + num2 20 21 a = 1 22 b = 2 23 c = 3 24 x = lazy(func, A, B)(a, b) 25 a = b 26 b = c 27 print a, b, c 28 x.func_b(a, b) 29 30 #2 3 3 31 #5
参考:http://blog.csdn.net/g9yuayon/article/details/759778
http://blog.donews.com/superisaac/archive/2006/03/16/771387.aspx