【问题标题】:How to reassign a class method after calling another method?调用另一个方法后如何重新分配一个类方法?
【发布时间】: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 的大小。

标签: python methods decorator


【解决方案1】:

您正在尝试做的事情似乎并不特别困难。我怀疑您使任务过于复杂。假设您愿意尊重自己的私有方法,您可以执行类似的操作

class Unblocker2:
    def __init__(self):
        self.data = None

    def populate_data(self, data):
        self.data = data
        self.do_something = self._do_something_populated

    def do_something(self):
        raise DataNotPopulatedError('Data not populated yet')

    def _do_something_populated(self):
        print("Data is:", self.data)

由于方法是非数据描述符,将绑定方法分配给实例属性do_something 将隐藏类属性。这样,已填充数据的实例可以避免以最少的冗余进行检查。

话虽如此,在开始优化代码之前先对代码进行概要分析。您会惊讶于哪些部分花费的时间最长。

【讨论】:

  • 为了清楚起见,可能将实际方法称为更具描述性的名称,例如 _do_something_error_not_populated_do_something_really_populated?
  • @tripleee。我已经更新了私有方法。很清楚,IMO
  • 我?过于复杂的东西?不可能的。感谢您的反馈!这基本上是我的想法,但我试图用装饰器来做,因为我认为它会很酷和优雅。
  • @Antyos。他们是。有趣的是,当别人这样做时,我很容易看出过于复杂:)
猜你喜欢
  • 2015-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-07
  • 1970-01-01
  • 2019-05-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多