禁用类实例方法
我想知道怎么能快速根据某个条件禁用一个类实例中的所有方法。我简单的想法是用 __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 个回答
你想做的事情其实是重写 __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
属性)。