【发布时间】:2021-10-03 22:10:08
【问题描述】:
我目前正在开发一个 Python 项目,其中包含几个类方法,每个类方法都被调用了数万次。这些方法的一个问题是它们首先依赖于通过另一种方法填充数据,因此如果在填充数据之前调用函数,我希望能够引发错误。
在有人问之前,我选择将数据填充阶段与类构造函数分开。这是因为数据填充(和处理)非常密集,我想将其与构造函数分开管理。
简单(低效)实现
一个简单的实现可能如下所示:
class DataNotPopulatedError(Exception):
...
class Unblocker1:
def __init__(self):
self.data = None
self._is_populated = False
def populate_data(self, data):
self.data = data
self._is_populated = True
# It will make sense later why this is its own method
def _do_something(self):
print("Data is:", self.data)
def do_something(self):
if not self._is_populated:
raise DataNotPopulatedError
return self._do_something()
unblocker1 = Unblocker1()
# Raise an error (We haven't populated the data yet)
unblocker1.do_something()
# Don't raise an error (We populated the data first)
unblocker1.populate_data([1,2,3])
unblocker1.do_something()
我的目标
因为假设的 do_something() 方法被调用了数十(或数百)次,我认为那些确保数据已被填充的额外检查将开始加起来。
虽然我可能找错了树,但我提高函数效率的第一个想法是在填充数据后动态重新分配方法。即,当第一次创建类时,do_something() 方法将指向另一个只引发DataNotPopulatedError 的函数。然后,populate_data() 方法将填充数据并通过将 do_something() 动态重新分配回所编写的函数来“解除阻塞”do_something()。
我认为实现这样的最简洁的方法是使用装饰器。
假设用法
我不知道如何实现上述技术,但是,我确实使用以前的低效方法创建了一个假设用法。考虑到目标实现,可能需要两个装饰器——一个用于阻塞函数,一个用于解除阻塞。
import functools
def blocked2(attr, raises):
def _blocked2(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Assumes `args[0]` is `self`
# If `self.key` is False, raise `raises`, otherwise call `func()`
if not getattr(args[0], attr):
raise raises
return func(*args, **kwargs)
return wrapper
return _blocked2
class Unblocker2:
def __init__(self):
self.data = None
self._is_populated = False
def populate_data(self, data):
self.data = data
self._is_populated = True
@blocked2("_is_populated", DataNotPopulatedError)
def do_something(self):
print("Data is:", self.data)
我一直很难解释我正在尝试做什么,所以我愿意接受其他建议来实现类似的目标(以及可能更好的帖子标题)。我很有可能在这里采取了完全错误的方法;这只是学习的一部分。如果有更好的方法来做我想做的事,我会全力以赴!
【问题讨论】:
-
检查数据填充操作是否昂贵?即使调用了很多次,布尔检查也不昂贵。就我个人而言,我只会在这里做类只读属性。
-
另外,
do_something是什么类型的操作?如,它是否独立于data的大小。