Python 中的上下文变量
Context variables in Python
假设我的 Python 应用程序中有一个定义某种上下文的函数 - 例如 user_id
。此函数调用不将此上下文作为函数参数的其他函数。例如:
def f1(user, operation):
user_id = user.id
# somehow define user_id as a global/context variable for any function call inside this scope
f2(operation)
def f2(operation):
# do something, not important, and then call another function
f3(operation)
def f3(operation):
# get user_id if there is a variable user_id in the context, get `None` otherwise
user_id = getcontext("user_id")
# do something with user_id and operation
我的问题是:
- Python3.7的Context Variables可以用来做这个吗?怎么样?
- 这就是这些上下文变量的用途吗?
- 如何使用 Python v3.6 或更早版本执行此操作?
编辑
出于多种原因(架构遗留、库等)我不能t/won不能更改像f2
这样的中间函数的签名,所以我不能只将 user_id
作为参数传递,也不能将所有这些函数放在同一个 class.
中
您可以在函数调用中使用 kwargs 以传递
def f1(user, operation):
user_id = user.id
# somehow define user_id as a global/context variable for any function call inside this scope
f2(operation, user_id=user_id)
def f2(operation, **kwargs):
# do something, not important, and then call another function
f3(operation, **kwargs)
def f3(operation, **kwargs):
# get user_id if there is a variable user_id in the context, get `None` otherwise
user_id = kwargs.get("user_id")
# do something with user_id and operation
kwargs
dict 等同于您在上下文变量中查看的内容,但仅限于调用堆栈。它是在每个函数中(通过类似指针)传递的相同内存元素,而不是内存中的重复变量。
在我看来,但我想看看你们是怎么想的,上下文变量是授权和控制全局变量的一种优雅方式。
本质上,您正在寻找的是一种在一组函数之间共享状态的方法。在面向对象语言中这样做的规范方法是使用 class:
class Foo(object):
def __init__(self, operation, user=None):
self._operation = operation
self._user_id = user.id if user else None
def f1(self):
print("in f1 : {}".format(self._user_id))
self.f2()
def f2(self):
print("in f2 : {}".format(self._user_id))
self.f3()
def f3(self):
print("in f3 : {}".format(self._user_id))
f = Foo(operation, user)
f.f1()
使用此解决方案,您的 class 个实例(这里 f
)是 "the context" 在其中执行函数的 - 每个实例都有自己的专用上下文。
等效的函数式编程是使用闭包,我不打算在这里举一个例子,因为虽然 Python 支持闭包,但它仍然是第一个并且主要是一种对象语言,所以 OO 解决方案是最显而易见。
最后,干净的程序解决方案是在整个调用链中传递此上下文(可以表示为 dict
或任何类似的数据类型),如 DFE 的回答所示。
作为一般规则:依赖于全局变量或某些 "magic" 上下文可以 - 或不 - 由 you-dont-who-nor-where-nor-when 设置使得代码很难如果不是不可能推理的话,这可能会以最不可预测的方式中断(谷歌搜索 "globals evil" 会产生大量关于该主题的文献)。
您可以使用 Python 3.7 中的 contextvars
来解决您的问题。通常很容易:
import contextvars
user_id = contextvars.ContextVar("user_id")
def f1(user, operation):
user_id.set(user.id)
f2()
def f2():
f3()
def f3():
print(user_id.get(default=None)) # gets the user_id value, or None if no value is set
ContextVar
returns Token
实例上的 set
方法,可用于将变量重置为 [=13] 之前的值=] 操作发生了。因此,如果您希望 f1
恢复原样(对于 user_id
上下文变量不是很有用,但与在 decimal
模块中设置精度更相关),您可以做:
token = some_context_var.set(value)
try:
do_stuff() # can use value from some_context_var with some_context_var.get()
finally:
some_context_var.reset(token)
contextvars
模块的功能远不止于此,但您几乎可以肯定不需要处理其他内容。如果您从头开始编写自己的异步框架,您可能只需要创建自己的上下文和 运行 其他上下文中的代码。
如果您只是使用 一个现有的框架(或者编写一个您希望与异步代码一起玩的库),则不需要处理那些东西.只需创建一个全局 ContextVar
(或查找一个已由您的框架定义的)和 get
和 set
值,如上所示,您就可以开始了。
很多 contextvars
使用可能会在后台进行,作为各种库的实现细节,这些库希望 "global" 状态不会泄漏线程之间的变化或在单个线程内的独立异步任务之间。在这种情况下,上面的示例可能更有意义:f1
和 f3
是同一个库的一部分,而 f2
是用户提供的回调传递到其他地方的库。
假设我的 Python 应用程序中有一个定义某种上下文的函数 - 例如 user_id
。此函数调用不将此上下文作为函数参数的其他函数。例如:
def f1(user, operation):
user_id = user.id
# somehow define user_id as a global/context variable for any function call inside this scope
f2(operation)
def f2(operation):
# do something, not important, and then call another function
f3(operation)
def f3(operation):
# get user_id if there is a variable user_id in the context, get `None` otherwise
user_id = getcontext("user_id")
# do something with user_id and operation
我的问题是:
- Python3.7的Context Variables可以用来做这个吗?怎么样?
- 这就是这些上下文变量的用途吗?
- 如何使用 Python v3.6 或更早版本执行此操作?
编辑
出于多种原因(架构遗留、库等)我不能t/won不能更改像f2
这样的中间函数的签名,所以我不能只将 user_id
作为参数传递,也不能将所有这些函数放在同一个 class.
您可以在函数调用中使用 kwargs 以传递
def f1(user, operation):
user_id = user.id
# somehow define user_id as a global/context variable for any function call inside this scope
f2(operation, user_id=user_id)
def f2(operation, **kwargs):
# do something, not important, and then call another function
f3(operation, **kwargs)
def f3(operation, **kwargs):
# get user_id if there is a variable user_id in the context, get `None` otherwise
user_id = kwargs.get("user_id")
# do something with user_id and operation
kwargs
dict 等同于您在上下文变量中查看的内容,但仅限于调用堆栈。它是在每个函数中(通过类似指针)传递的相同内存元素,而不是内存中的重复变量。
在我看来,但我想看看你们是怎么想的,上下文变量是授权和控制全局变量的一种优雅方式。
本质上,您正在寻找的是一种在一组函数之间共享状态的方法。在面向对象语言中这样做的规范方法是使用 class:
class Foo(object):
def __init__(self, operation, user=None):
self._operation = operation
self._user_id = user.id if user else None
def f1(self):
print("in f1 : {}".format(self._user_id))
self.f2()
def f2(self):
print("in f2 : {}".format(self._user_id))
self.f3()
def f3(self):
print("in f3 : {}".format(self._user_id))
f = Foo(operation, user)
f.f1()
使用此解决方案,您的 class 个实例(这里 f
)是 "the context" 在其中执行函数的 - 每个实例都有自己的专用上下文。
等效的函数式编程是使用闭包,我不打算在这里举一个例子,因为虽然 Python 支持闭包,但它仍然是第一个并且主要是一种对象语言,所以 OO 解决方案是最显而易见。
最后,干净的程序解决方案是在整个调用链中传递此上下文(可以表示为 dict
或任何类似的数据类型),如 DFE 的回答所示。
作为一般规则:依赖于全局变量或某些 "magic" 上下文可以 - 或不 - 由 you-dont-who-nor-where-nor-when 设置使得代码很难如果不是不可能推理的话,这可能会以最不可预测的方式中断(谷歌搜索 "globals evil" 会产生大量关于该主题的文献)。
您可以使用 Python 3.7 中的 contextvars
来解决您的问题。通常很容易:
import contextvars
user_id = contextvars.ContextVar("user_id")
def f1(user, operation):
user_id.set(user.id)
f2()
def f2():
f3()
def f3():
print(user_id.get(default=None)) # gets the user_id value, or None if no value is set
ContextVar
returns Token
实例上的 set
方法,可用于将变量重置为 [=13] 之前的值=] 操作发生了。因此,如果您希望 f1
恢复原样(对于 user_id
上下文变量不是很有用,但与在 decimal
模块中设置精度更相关),您可以做:
token = some_context_var.set(value)
try:
do_stuff() # can use value from some_context_var with some_context_var.get()
finally:
some_context_var.reset(token)
contextvars
模块的功能远不止于此,但您几乎可以肯定不需要处理其他内容。如果您从头开始编写自己的异步框架,您可能只需要创建自己的上下文和 运行 其他上下文中的代码。
如果您只是使用 一个现有的框架(或者编写一个您希望与异步代码一起玩的库),则不需要处理那些东西.只需创建一个全局 ContextVar
(或查找一个已由您的框架定义的)和 get
和 set
值,如上所示,您就可以开始了。
很多 contextvars
使用可能会在后台进行,作为各种库的实现细节,这些库希望 "global" 状态不会泄漏线程之间的变化或在单个线程内的独立异步任务之间。在这种情况下,上面的示例可能更有意义:f1
和 f3
是同一个库的一部分,而 f2
是用户提供的回调传递到其他地方的库。