一、 源码分析 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 class LocalProxy : __slots__ = ("__local" , "__name" , "__wrapped__" ) def __init__ ( self, local: t.Union ["Local" , t.Callable [[], t.Any ]], name: t.Optional [str ] = None , ) -> None : object .__setattr__(self, "_LocalProxy__local" , local) object .__setattr__(self, "_LocalProxy__name" , name) if callable (local) and not hasattr (local, "__release_local__" ): object .__setattr__(self, "__wrapped__" , local) def _get_current_object (self ) -> t.Any : """ 获取在代理背后的真实属性,同样从对应线程id处进行获取 可以通过 """ if not hasattr (self.__local, "__release_local__" ): return self.__local() try : return getattr (self.__local, self.__name) except AttributeError: raise RuntimeError(f"no object bound to {self.__name} " ) __doc__ = _ProxyLookup( class_value=__doc__, fallback=lambda self: type (self).__doc__ ) __repr__ = _ProxyLookup( repr , fallback=lambda self: f"<{type (self).__name__} unbound>" ) __str__ = _ProxyLookup(str ) __bytes__ = _ProxyLookup(bytes ) __format__ = _ProxyLookup() __lt__ = _ProxyLookup(operator.lt) __le__ = _ProxyLookup(operator.le) __eq__ = _ProxyLookup(operator.eq) __ne__ = _ProxyLookup(operator.ne) __gt__ = _ProxyLookup(operator.gt)
1.使用__slots__
机制,实例化对象时,不为对象生成__dict__
的属性空间,将__slots__
中指定的元素构建成一个元组。减少创建对象产生的内存空间,固定对象中的属性值,使之在运行过程中不能被修改,不能添加新的属性。
2.__init__
函数中可以接受local和name,local可以Local的实例,也可以是函数;作为函数,例如在globals.py中定义的partial”冰冻”函数(其目的是从stack获取栈顶元素—AppContext或RequestContext,返回Local实例中的某个属性)。
3.Python中的双前导下划线是为了避免与子类定义的名称冲突。
4._get_current_object方法,通过反射读取__local
中是否有释放Local实例中资源的属性或方法,如果没有,则说明__local
并不是Local实例,而是一个可回调的对象;反之,从__local
中获取对应当前线程属性空间下的属性值。
5._ProxyLookup类是一个描述器,根据类名,可以大致推测用于搜寻被代理对象中的属性的,接下来通过源码来学习它是如何搜寻被代理对象中的属性的。源码如下:
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 class _ProxyLookup : """Descriptor that handles proxied attribute lookup for """ __slots__ = ("bind_f" , "fallback" , "class_value" , "name" ) def __init__ ( self, f: t.Optional [t.Callable ] = None , fallback: t.Optional [t.Callable ] = None , class_value: t.Optional [t.Any ] = None , ) -> None : bind_f: t.Optional [t.Callable [["LocalProxy" , t.Any ], t.Callable ]] if hasattr (f, "__get__" ): def bind_f (instance: "LocalProxy" , obj: t.Any ) -> t.Callable : return f.__get__(obj, type (obj)) elif f is not None : def bind_f (instance: "LocalProxy" , obj: t.Any ) -> t.Callable : return partial(f, obj) else : bind_f = None self.bind_f = bind_f self.fallback = fallback self.class_value = class_value def __set_name__ (self, owner: "LocalProxy" , name: str ) -> None : self.name = name def __get__ (self, instance: "LocalProxy" , owner: t.Optional [type ] = None ) -> t.Any : if instance is None : if self.class_value is not None : return self.class_value return self try : obj = instance._get_current_object() except RuntimeError: if self.fallback is None : raise return self.fallback.__get__(instance, owner) if self.bind_f is not None : return self.bind_f(instance, obj) return getattr (obj, self.name) def __repr__ (self ) -> str : return f"proxy {self.name} " def __call__ (self, instance: "LocalProxy" , *args: t.Any , **kwargs: t.Any ) -> t.Any : """Support calling unbound methods from the class. For example, this happens with ``copy.copy``, which does ``type(x).__copy__(x)``. ``type(x)`` can't be proxied, so it returns the proxy type and descriptor. """ return self.__get__(instance, type (instance))(*args, **kwargs)
分析:
由于类中存在__get__
和__set_name
方法,我们可以很快推断出这是一个描述器,描述器所要做的事情是控制我们访问方法和实例的属性。而这个类的作用为我们提供了访问被代理对象Local实例中当前线程属性字典中的相关属性。
以__str__ = _ProxyLookup(str)
为例具体说明下执行逻辑。
当我们要打印一个属性值时print(attribute)
,如果定义了__str__
方法时,会先调用该方法,然后就会调用_ProxyLookup
中的__call__
方法。在__call__
方法中会调用了__get__
方法,其中传入__get__
方法的instance为self
,即LocalProxy
的实例对象。进而进入__get__
代码段中,通过instance._get_current_object()
获取到对象,然后利用反射机制,获取Local实例中当前线程属性字典中相对应(self.name)的属性。
二、 总结 1.从LocalProxy角度来看,代理模式带来的好处是简化属性访问的复杂性,原本Local对象的数据结构很复杂,包含了多层字典嵌套。使用了代理模式提供了统一的数据访问接口去访问底层的复杂数据。 2.之前跟女朋友一起学习时,看过Vue3.0的响应式原理,以computed为例,computed的函数中就使用了代理对象做了一层拦截,实现依赖收集和派发通知操作,实现函数延迟调用和访问缓存旧数据,当新数据更新了,重新执行runner方法并派发通知,通知watcher去触发组件的重新渲染。 3.代理模式总的来说在调用和被调用方之间做了一层拦截,拦截到后做额外的操作,是不是非常的方便呢?