禁用类实例方法

3 投票
1 回答
3375 浏览
提问于 2025-04-15 19:26

我想知道怎么能快速根据某个条件禁用一个类实例中的所有方法。我简单的想法是用 __getattr__ 来重写,但这个方法在函数名已经存在的时候不会被调用。

class my():
    def method1(self):
        print 'method1'
    def method2(self):
        print 'method2'
    def __getattr__(self, name):
        print 'Fetching '+str(name)
        if self.isValid():
            return getattr(self, name)
    def isValid(self):
        return False
if __name__ == '__main__':
    m=my()
    m.method1()

1 个回答

6

你想做的事情其实是重写 __getattribute__ 方法,这个方法会在你访问每一个属性时被调用。不过要注意,这样做会很慢,而且因为是每一个,所以包括在 __getattribute__ 方法内部调用 self.isValid。这就意味着你需要用一些曲折的方式来访问这个属性,比如可以用 type(self).isValid(self),这样是从类里获取属性,而不是从实例里。

这里有个很糟糕的术语混淆:这不是在禁用“类的方法”,而是在禁用实例的方法,而且和 classmethods 没有关系。如果你想在类的层面上以类似的方式工作,而不是在实例层面上,你需要创建一个自定义的元类,并在元类上重写 __getattribute__(这是在你访问的属性时被调用的 -- 就像你在标题和文本中提到的那样,而不是在实例上 -- 你实际上是在做的事情,通常情况下这是更常见的情况)。

编辑:还有一种完全不同的方法是使用一种特别的 Python 风格来实现 State 设计模式:类切换。例如:

class _NotValid(object):
  def isValid(self):
    return False
  def setValid(self, yesno):
    if yesno:
      self.__class__ = TheGoodOne

class TheGoodOne(object):
  def isValid(self):
    return True
  def setValid(self, yesno):
    if not yesno:
      self.__class__ = _NotValid
  # write all other methods here

只要你能适当地调用 setValid,使得对象的 __class__ 被正确切换,这样做非常快速且简单 -- 本质上,对象的 __class__ 是所有对象方法的来源,所以通过切换它,你可以一次性切换对象在某一时刻的所有方法。不过,如果你坚持要在“恰好时刻”进行有效性检查,也就是在对象的方法被查找的那一瞬间,这种方法就不适用了。

在这种方法和 __getattribute__ 方法之间的一个中间方案是引入一个额外的间接层(这被普遍认为是解决所有问题的办法;-),类似于:

class _Valid(object):
  def __init__(self, actualobject):
    self._actualobject = actualobject
  # all actual methods go here
  # keeping state in self._actualobject

class Wrapit(object):
  def __init__(self):
    self._themethods = _Valid(self)
  def isValid(self):
    # whatever logic you want
    # (DON'T call other self. methods!-)
    return False
  def __getattr__(self, n):
    if self.isValid():
      return getattr(self._themethods, n)
    raise AttributeError(n)

这个方法比 __getattribute__ 更符合习惯,因为它依赖于 __getattr__ 只会在找不到其他方式访问的属性时被调用 -- 所以对象可以在它的 __dict__ 中保持正常的状态(数据),而且这些状态会被直接访问,没有太大的开销;只有方法调用会多出间接访问的开销。如果 _Valid 类的实例需要在无效对象上保持某些或所有状态,可以把这些状态保存在它们各自的 self._actualobject 中(这样无效状态会禁用方法,但不会影响数据属性的访问;从你的问题中不太清楚这是否需要,但这是这个方法提供的一个额外可能性)。这个习惯用法比 __getattribute__ 更不容易出错,因为在方法中可以更直接地访问状态(而不会触发有效性检查)。

如上所述,这个解决方案会创建一个循环引用,这可能会在垃圾回收方面带来一些开销。如果这在你的应用中是个问题,可以使用标准 Python 库中的 weakref 模块 -- 这个模块通常是消除循环引用的最简单方法,如果它们确实成为问题的话。(例如,可以将 _Valid 类实例的 _actualobject 属性设置为对持有该实例的对象的一个引用,作为它的 _themethods 属性)。

撰写回答