werzeug之LocalProxy源码

原文链接

链接:https://www.jianshu.com/p/3f38b777a621

werzeug之LocalProxy注释

# 源码注释

"""
充当本地 werkzeug 的代理。 将所有操作转发到
近在咫多的物体。 唯一不支持转发的操作
是右手操作和任何类型的分配。

范例用法:

从 werkzeug.本地导入本地
l = 本地()

这些是代理
请求 = l("请求")
用户 = l("用户")

从 werkzeug.本地导入本地 Stack
_response_local = 本地堆栈()

这是一个代理
响应 = _response_local()

"""

LocalProxy用于代理Local对象和LocalStack对象,而所谓代理就是作为中间的代理人来处理所有针对被代理对象的操作,如下图

源码如下

@implements_bool
class LocalProxy(object):
	__slots__ = ('__local', '__dict__', '__name__', '__wrapped__')

	def __init__(self, local, name=None):
		object.__setattr__(self, '_LocalProxy__local', local)
		object.__setattr__(self, '__name__', name)
		if callable(local) and not hasattr(local, '__release_local__'):
			# "local" is a callable that is not an instance of Local or
			# LocalManager: mark it as a wrapped function.
			object.__setattr__(self, '__wrapped__', local)

	def _get_current_object(self):
		"""Return the current object.  This is useful if you want the real
		object behind the proxy at a time for performance reasons or because
		you want to pass the object into a different context.
		"""
		# 由于所有Local或LocalStack对象都有__release_local__ method, 所以如果没有该属性就表明self.__local为callable对象
		if not hasattr(self.__local, '__release_local__'):
			return self.__local()
		try:
			# 此处self.__local为Local或LocalStack对象
			return getattr(self.__local, self.__name__)
		except AttributeError:
			raise RuntimeError('no object bound to %s' % self.__name__)

	@property
	def __dict__(self):
		try:
			return self._get_current_object().__dict__
		except RuntimeError:
			raise AttributeError('__dict__')

	def __getattr__(self, name):
		if name == '__members__':
			return dir(self._get_current_object())
		return getattr(self._get_current_object(), name)

	def __setitem__(self, key, value):
		self._get_current_object()[key] = value

	def __delitem__(self, key):
		del self._get_current_object()[key]

	if PY2:
		__getslice__ = lambda x, i, j: x._get_current_object()[i:j]

		def __setslice__(self, i, j, seq):
			self._get_current_object()[i:j] = seq

		def __delslice__(self, i, j):
			del self._get_current_object()[i:j]

	# 截取部分操作符代码
	__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
	__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
	__str__ = lambda x: str(x._get_current_object())
	__lt__ = lambda x, o: x._get_current_object() < o
	__le__ = lambda x, o: x._get_current_object() <= o
	__eq__ = lambda x, o: x._get_current_object() == o

浅析

1.	首先在__init__method中传递的local参数会被赋予属性_LocalProxy__local,
该属性可以通过self.__local进行访问

2. LocalProxy通过_get_current_object来获取代理的对象。需要注意的是当初始化参数
为callable对象时,则直接调用以返回Local或LocalStack对象,具体看源代码的注释。

3. 重载了绝大多数操作符,以便在调用LocalProxy的相应操作时,
通过_get_current_object method来获取真正代理的对象,然后再进行相应操作

LocalProxy的使用

#初始化LocalProxy有三种方式:

'''
1. 通过Local或者LocalStack对象的__call__ method
from werkzeug.local import Local
l = Local()

# these are proxies
request = l('request')
user = l('user')


from werkzeug.local import LocalStack
_response_local = LocalStack()

# this is a proxy
response = _response_local()

'''

上述代码直接将对象像函数一样调用,这是因为Local和LocalStack都实现了__call__ 方法,这样其对象就是callable的,因此当我们将对象作为函数调用时,
实际调用的是__call__ 方法,可以看下本文开头部分的Local的源代码,
会发现__call__ 方法,会返回一个LocalProxy对象

2.	通过LocalProxy类进行初始化

'''
l = Local()
request = LocalProxy(l, 'request')
'''

实际上这段代码跟第一种方式是等价的,但这种方式是最'原始'的方式,
我们在Local的源代码实现中看到其__call__ 方法就是通过这种方式生成LocalProxy的

3. 使用callable对象作为参数

'''
request = LocalProxy(get_current_request())
'''
通过传递一个函数,我们可以自定义如何返回Local或LocalStack对象

为什么要使用LocalProxy

可是说了这么多,为什么一定要用proxy,而不能直接调用Local或LocalStack对象呢?
这主要是在有多个可供调用的对象的时候会出现问题,

如下图

示例:不用LocalProxy

# use Local object directly
from werkzeug.local import LocalStack
user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})

def get_user():
	# do something to get User object and return it
	return user_stack.pop()


# 直接调用函数获取user对象
user = get_user()
print user['name']
print user['name']

# 结果
'''
John
John
'''

示例:使用LocalProxy

# use LocalProxy
from werkzeug.local import LocalStack, LocalProxy
user_stack = LocalStack()
user_stack.push({'name': 'Bob'})
user_stack.push({'name': 'John'})

def get_user():
	# do something to get User object and return it
	return user_stack.pop()

# 通过LocalProxy使用user对象
user = LocalProxy(get_user)
print user['name']
print user['name']

# 结果
'''
Bob
John
'''

总结

直接使用LocalStack对象,user一旦赋值就无法再动态更新了,而使用Proxy,
每次调用操作符(这里[]操作符用于获取属性),都会重新获取user,
从而实现了动态更新user的效果。

Flask以及Flask的插件很多时候都需要这种动态更新的效果,
因此LocalProxy就会非常有用了

如下图

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)
偏函数的作用:将所作用的函数作为partial()函数的第一个参数,原函数的各个参
数依次作为partial()函数的后续参数,原函数有关键字参数的一定要带上关键字,
没有的话,按原有参数顺序进行补充。

简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设
置默认值),返回一个新的函数,调用这个新函数会更简单。

flask之LocalProxy应用偏函数

# LocalProxy代理
import functools
class LocalProxy(object):
	def __init__(self, local):
	
		# self.__local = local
		object.__setattr__(self, "_LocalProxy__local", local) 


	def _get_current_object(self):
		return self.__local() # self._LocalProxy__local()

	def __setitem__(self, key, value):
		# 函数() 自动传入session,ctx.session[key] = value
		self._get_current_object()[key] = value

	def __getattr__(self, name):
		# ctx.request.method
		return getattr(self._get_current_object(), name)

def _lookup_req_object(name):

"""

传入参数:name = request/session/g

"""

# 取栈顶 ctx = (request, session) / app_ctx = (app, g)
top = _request_ctx_stack.top
if top is None:
    raise RuntimeError(_request_ctx_err_msg)
	
# return:ctx.request / ctx.session/ app_ctx.g 
return getattr(top, name)


_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()

# request.form >>> 触发__getattr__
request = LocalProxy(partial(_lookup_req_object, "request"))

# session['k1'] = 123,触发__setitem__
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))
希望你眼眸有星辰,心中有山海,从此以梦为马,不负韶华
原文地址:https://www.cnblogs.com/daviddd/p/11938519.html