装饰一个Python类,使得大多数方法在满足某种条件时引发异常

4

我遇到了这样一种情况:类的大多数方法在被调用时需要抛出一个异常,但如果特定条件为False,则有一个方法不需要抛出异常。可以在大多数方法中编写 if not condition 语句,使每个方法在条件不为真时抛出异常,但我认为可能可以通过在类顶部使用单个装饰器来实现。

这个问题与此类似,但它涉及分别对每个方法进行装饰,如果我要这样做,我可能会直接将if语句放入每个方法中。

下面是一些代码和注释以帮助理解:

CONDITION = True  # change to False to test

def CheckMethods():
    if CONDITION:
        # run all the methods as usual if they are called
        pass 
    else:
        # raise an exception for any method which is called except 'cow'
        # if 'cow' method is called, run it as usual
        pass


@CheckMethods
class AnimalCalls:
    def dog(self):
        print("woof")
    def cat(self):
        print("miaow")
    def cow(self):
        print("moo")
    def sheep(self)
        print("baa") 

a = AnimalCalls()
a.dog()
a.cat()
a.cow()
a.sheep()

有人知道如何做到这一点吗?我以前从未装饰过类,也从未尝试过像这样检查它的方法。


1
你确定你需要一个装饰器,而不是代理吗? - Kijewski
我不确定那是什么。只要它检查一个条件,如果条件不成立,大多数方法都会引发异常。 - cardamom
@DavisHerring 的意图更多地体现在每个实例化上,这至少符合其余启发它的代码工作方式。 - cardamom
@cardamom,你打算动态切换“条件”吗?之前关闭的原始方法是否需要恢复? - RomanPerekhrest
@cardamom: 所以条件在任何给定对象构造后都不能更改吗?(这在Python中有些不寻常。) - Davis Herring
显示剩余6条评论
3个回答

5
实现代理就是这么简单。
class Proxy:
    def __init__(self, inst):
        self.__inst = inst

    def __getattr__(self, name):
        return getattr(self.__inst, name)

使用obj = SomeClass()代替obj = Proxy(SomeClass())。所有对obj.attribute的访问都会被Proxy.__getattr__拦截。这是您可以添加更多逻辑的方法,例如:

class MethodChecker:
    def __init__(self, inst, check):
        self.__inst = inst
        self.__check = check

    def __getattr__(self, name):
        self.__check()
        return getattr(self.__inst, name)

2

大部分是基于找到的代码进行调整,最初的回答

condition = False

def CheckMethods(Cls):
    class NewCls(object):
        def __init__(self,*args,**kwargs):
            self.oInstance = Cls(*args,**kwargs)
        def __getattribute__(self,s):
            try:    
                x = super(NewCls,self).__getattribute__(s)
            except AttributeError:      
                pass
            else:
                return x
            x = self.oInstance.__getattribute__(s)
            if condition:
                return x
            else:
                if s == 'cow':
                    return x
                else:
                    raise ValueError('Condition not true')
    return NewCls

@CheckMethods
class AnimalCalls(object):
    def dog(self):
        print("woof")
    def cat(self):
        print("miaow")
    def cow(self):
        print("moo")
    def sheep(self):
        print("baa") 

oF = AnimalCalls()

结果:

contition = False; of.moo() -> 'moo'
contition = True; of.moo() -> 'moo'
condition = False; of.dog() -> 'ValueError: Condition not true'
condition = True; of.dog() -> 'woof'

请注意,当查找dog时引发异常,而不是在调用时。除其他影响外,这意味着不会评估调用的参数。 - Davis Herring
我测试了一下,基本上它可以工作并且是一个装饰器,回答了这个问题。另外一个行为,即在查找时引发异常而不是在调用时引发异常,可能可以使用traceback中的一些方法来修复,具体请参考此答案 - cardamom

2
代理是我的首选,但这里有一个请求的装饰器。
我添加了一个测试,以排除任何以下划线开头的方法。您可能想包括_internal方法,但要小心不要干扰任何特殊的__dunder__方法。
# cond = lambda attr: True  # full access
cond = lambda attr: attr == 'cow'

def methodcheck(cls):
    def cond_getattribute(self, name):
        if name.startswith('_') or cond(name):
            return saved_gettattribute(self, name)
        raise AttributeError("access forbidden")
    saved_gettattribute = cls.__getattribute__
    cls.__getattribute__ = cond_getattribute
    return cls 

@methodcheck
class AnimalCalls:
    def dog(self):
        print("woof")
    def cat(self):
        print("miaow")
    def cow(self):
        print("moo")
    def sheep(self):
        print("baa"

谢谢@VPfB,这是一段惊人而紧凑的代码,带有递归...我仍在努力完全理解它。我注意到内部函数被执行了6次。我以为那是类方法的某个倍数,或者允许的类方法,但即使我删除一些方法,它似乎也不会改变6。你有注意到同样的事情吗? - cardamom
@cardamom,我想回答你的评论,但我并没有完全理解它。我的代码没有递归。装饰器安装了一个新的__getattribute__,负责属性查找,但除此之外不会改变原始类。新函数要么调用旧函数,即常规的__getattribute__,要么根据属性名称的条件引发错误。您应该看到每个属性查找(除了一些非常特殊的情况)都会有一个调用。文档:https://docs.python.org/3/reference/datamodel.html?highlight=__getattribute__#object.__getattribute__ - VPfB
我现在明白了。我上面提到的6次行为是在Jupyter笔记本中运行时的一些怪癖,在终端中的标准Python解释器中无法复制它。 - cardamom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接