gpt4 book ai didi

python - 创建一个到经常访问的对象的钩子(Hook)

转载 作者:太空宇宙 更新时间:2023-11-03 13:48:40 30 4
gpt4 key购买 nike

我有一个应用程序,它在很大程度上依赖于一个Context实例,该实例充当执行给定计算的上下文的访问点。
如果我想提供对Context实例的访问,我可以:
依赖于global
Context作为参数传递给所有需要它的函数
我宁愿不使用global变量,并且将Context实例传递给所有函数是繁琐和冗长的。
如何“隐藏,但使其可访问”计算?
例如,假设Context只是根据不同的数据计算行星的状态(位置和速度)。

class Context(object):
def state(self, planet, epoch):
"""base class --- suppose `state` is meant
to return a tuple of vectors."""
raise NotImplementedError("provide an implementation!")

class DE405Context(Context):
"""Concrete context using DE405 planetary ephemeris"""
def state(self, planet, epoch):
"""suppose that de405 reader exists and can provide
the required (position, velocity) tuple."""
return de405reader(planet, epoch)

def angular_momentum(planet, epoch, context):
"""suppose we care about the angular momentum of the planet,
and that `cross` exists"""
r, v = context.state(planet, epoch)
return cross(r, v)

# a second alternative, a "Calculator" class that contains the context
class Calculator(object):

def __init__(self, context):
self._ctx = context

def angular_momentum(self, planet, epoch):
r, v = self._ctx.state(planet, epoch)
return cross(r, v)

# use as follows:
my_context = DE405Context()
now = now() # assume this function returns an epoch
# first case:
print angular_momentum("Saturn", now, my_context)
# second case:
calculator = Calculator(my_context)
print calculator.angular_momentum("Saturn", now)

当然,我可以将所有操作直接添加到“Context”中,但感觉不对。
在现实生活中, Context不仅计算行星的位置!它可以计算更多的东西,并且作为许多数据的访问点。
所以,为了使我的问题更简洁:如何处理需要被许多类访问的对象?
我目前正在探索:python的上下文管理器,但是运气不好。我还考虑了直接将属性“context”动态添加到所有函数中(函数是对象,因此它们可以对任意对象有一个访问点),即:
def angular_momentum(self, planet, epoch):
r, v = angular_momentum.ctx.state(planet, epoch)
return cross(r, v)

# somewhere before calling anything...
import angular_momentum
angular_momentum.ctx = my_context

编辑
最好的方法是用 Context语句创建一个“计算上下文”,例如:
 with my_context:
h = angular_momentum("Earth", now)

当然,如果我简单地写下:
 with my_context as ctx:
h = angular_momentum("Earth", now, ctx) # first implementation above

可能是这个的一个变种?

最佳答案

您通常不想在python中“隐藏”任何内容。你可能想告诉人类读者,他们应该把它当作“私有的”,但这实际上只是意味着“即使你忽略了这个对象,你也应该能够理解我的API”,而不是“你不能访问这个”。
在Python中,惯用的方法是在其前面加上下划线,如果您的模块可能与from foo import *一起使用,则添加一个显式的__all__全局变量,列出所有公共导出同样,这两种方法都不会阻止任何人看到您的变量,甚至在import foo之后从外部访问它。
有关详细信息,请参见全局变量名上的PEP 8
一些样式指南建议全局符号使用特殊前缀、所有大写名称或其他特殊区分标记,但pep 8特别指出,除__all__和/或前导下划线外,其他约定都是相同的。
同时,您需要的行为显然是全局变量的行为,即每个人都隐式共享和引用的单个对象试图把它伪装成其他的东西对你没有好处,除了可能通过一个lint检查或者一个你不应该通过的代码检查全局变量的所有问题都来自于作为每个人都隐式共享和引用的单个对象,而不是直接存在于globals()字典或类似的东西中,因此任何像样的假全局都和真正的全局一样糟糕。如果这确实是您想要的行为,那么将其设置为全局变量。
总而言之:

# do not include _context here
__all__ = ['Context', 'DE405Context', 'Calculator', …

_context = Context()

当然,您也可以将其称为 _global_context或甚至 _private_global_context,而不仅仅是 _context
但请记住,全局变量仍然是模块的成员,而不是整个宇宙的成员,因此,当客户端代码执行 context操作时,即使是公共的 foo.context也仍将被定义为 import foo。这也许正是你想要的如果您想让客户机脚本导入模块并控制其行为,那么 foo.context = foo.Context(…)可能正是正确的方法当然,这在多线程(或gevent/coroutine/etc)代码中是行不通的,而且在其他各种情况下也是不合适的,但是如果这不是问题,在某些情况下,这是可以的。
由于您在注释中提出了多线程:在具有长时间运行作业的简单多线程样式中,全局样式实际上工作得非常好,只需稍作更改即可将全局 Context替换为包含 threading.local的全局 Context实例。即使在由线程池处理小任务的样式中,也不会复杂得多。将上下文附加到每个作业,然后当工作进程从队列中提取作业时,它会将线程本地上下文设置为该作业的上下文。
不过,我不确定多线程是否适合你的应用在Python中,当您的任务偶尔需要为IO阻塞时,多线程是很好的,并且您希望能够在不停止其他任务的情况下这样做,但是,由于GIL,它对于并行化CPU工作几乎没有用处,而且听起来像是您正在寻找的那样多处理(无论是通过 multiprocessing模块还是其他方式)可能更适合您使用单独的过程,保持单独的上下文更简单。(或者,您可以编写基于线程的代码并将其切换到多处理,保持 threading.local变量不变,只改变生成新任务的方式,一切仍然正常。)
提供上下文管理器意义上的“context”可能是有意义的,就像标准库的 decimal模块的外部版本一样,这样就有人可以编写:
with foo.Context(…):
# do stuff under custom context
# back to default context

然而,没有人能真正想到一个好的用例(特别是,至少在天真的实现中,它并不能真正解决线程等问题),所以它没有添加到标准库中,您也可能不需要它。
如果你想这么做,那就太微不足道了如果您使用的是私有全局设置,只需将其添加到 Context类中:
def __enter__(self):
global _context
self._stashedcontext = _context
_context = self
def __exit__(self, *args):
global context
_context = self._stashedcontext

而且很明显,如何将其调整为public、thread local等选项。
另一种选择是使所有内容都成为 Context对象的成员然后,顶级模块函数只委托给具有合理默认值的全局上下文。这正是标准库 random模块的工作方式,您可以创建 random.Random()并对其调用 randrange,也可以只调用 random.randrange(),它对全局默认对象调用相同的东西。
如果在导入时创建一个 random.Random()的任务太重,特别是如果它可能不被使用(因为没有人可能调用全局函数),那么可以使用singleton模式在第一次访问时创建它。但这几乎没有必要。如果不是,代码就很简单。例如,从第881行开始的 sourceContext执行以下操作:
_inst = Random()
seed = _inst.seed
random = _inst.random
uniform = _inst.uniform


就这些。
最后,正如您所建议的,您可以使所有内容成为拥有 random对象的不同 Calculator对象的成员。这是传统的OOP解决方案;过度使用它会让Python感觉像Java,但在适当的时候使用它并不是坏事。

关于python - 创建一个到经常访问的对象的钩子(Hook),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14131920/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com