LocalProxy代理对象的源码分析

一、 源码分析

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( # type: ignore
class_value=__doc__, fallback=lambda self: type(self).__doc__
)
# __del__ should only delete the proxy
__repr__ = _ProxyLookup( # type: ignore
repr, fallback=lambda self: f"<{type(self).__name__} unbound>"
)
__str__ = _ProxyLookup(str) # type: ignore
__bytes__ = _ProxyLookup(bytes)
__format__ = _ProxyLookup() # type: ignore
__lt__ = _ProxyLookup(operator.lt)
__le__ = _ProxyLookup(operator.le)
__eq__ = _ProxyLookup(operator.eq) # type: ignore
__ne__ = _ProxyLookup(operator.ne) # type: ignore
__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__"):
# A Python function, can be turned into a bound method.

def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
return f.__get__(obj, type(obj)) # type: ignore

elif f is not None:
# A C function, use partial to bind the first argument.

def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
return partial(f, obj) # type: ignore

else:
# Use getattr, which will produce a bound method.
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) # type: ignore

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.代理模式总的来说在调用和被调用方之间做了一层拦截,拦截到后做额外的操作,是不是非常的方便呢?


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!