简体   繁体   English

装饰器调用实例方法

[英]Decorator to invoke instance method

I have a class A with method do_something(self,a,b,c) and another instance method that validates the input and check permissions named can_do_something(self,a,b,c) . 我有一个方法为do_something(self,a,b,c)的类A ,另一个实例方法是对输入进行验证并检查权限的实例方法can_do_something(self,a,b,c)

This is a common pattern in my code and I want to write a decorator that accepts a validation function name and perform the test. 这是我代码中的常见模式,我想编写一个接受验证函数名称并执行测试的装饰器。

def validate_input(validation_fn_name):
        def validation_decorator(func):
            def validate_input_action(self,*args):
                error = getattr(self,validation_fn_name)(*args)
                if not error == True:
                    raise error
                else:
                    return func(*args)
            return validate_input_action
        return validation_decorator

Invoking the functions as follows 调用功能如下

@validate_input('can_do_something')
def do_something(self,a,b,c):
   return a + b + c

Problem is that i'm not sure how to maintain self through out the validation function. 问题是我不确定如何通过验证功能来保持self I've used the validation fn name with getattr so the fn could be ran in the context of the instance but i cannot do that for func(*args) . 我已经将验证fn名称与getattr因此可以在实例的上下文中运行fn,但是我不能对func(*args)这样做。

So what is the proper way to achieve this ? 那么实现这一目标的正确方法是什么?

Thanks. 谢谢。

EDIT 编辑

So following @André Laszlo answer I realized that self is just the first argument so there is no need to use getattr at all but just pass on the *args . 因此,在@AndréLaszlo回答之后,我意识到self只是第一个参数,因此根本不需要使用getattr ,而只需传递*args

def validate_input(validation_fn):
    def validation_decorator(func):
        def validate_input_action(*args):
            error = validation_fn(*args)
            if not error == True:
                raise error
            else:
                return func(*args)
        return validate_input_action
    return validation_decorator

Much more elegant and it also supports static methods as well. 更优雅,它还支持静态方法。

Adding a static method to @André Laszlo example proves the decorator is working : 向@AndréLaszlo示例添加静态方法证明装饰器正在工作:

 class Foo(object):
    @staticmethod
    def validate_baz(a,b,c):
       if a > b:
          return ValueError('a gt b')

    @staticmethod
    @validate_input(Foo.validate_baz)
    def baz(a,b,c):
       print a,b,c

    >>> Foo.baz(1,2,3)
    1 2 3
    >>> Foo.baz(2,1,3)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 6, in validate_input_action
    ValueError: a gt b

But, when i'm trying to do them same thing in a django model: 但是,当我尝试在Django模型中做相同的事情时:

from django.db import models
from django.conf import settings

settings.configure()

class Dummy(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)

    def can_say_name(self):
        if name is None:
            return Exception('Does not have a name')

    @validate_input(can_say_name)
    def say_name(self):
        print self.name

    @staticmethod
    def can_create_dummy(name):
        if name == 'noname':
            return Exception('No name is not a name !')

    @staticmethod
    @validate_input(Dummy.can_create_dummy)
    def create_dummy(name):
        return Dummy.objects.create(name=name)

I get the following : 我得到以下内容:

NameError: name 'Dummy' is not defined

So what is the different between a django model and an Object in relation to this issue ? 那么,关于这个问题,Django模型和Object之间有什么区别?

I think this does what you want: 我认为这可以满足您的需求:

def validate_input(validation_fn_name):
    def validation_decorator(func):
        def validate_input_action(self, *args):
            error = getattr(self, validation_fn_name)(*args)
            if error is not None:
                raise error
            else:
                arglist = [self] + list(args)
                return func(*arglist)
        return validate_input_action
    return validation_decorator

class Foo(object):

    def validate_length(self, arg1):
        if len(arg1) < 3:
            return ValueError('%r is too short' % arg1)

    @validate_input('validate_length')
    def bar(self, arg1):
        print "Arg1 is %r" % arg1


if __name__ == "__main__":
    f = Foo()
    f.bar('hello')
    f.bar('')

Output is: 输出为:

Arg1 is 'hello'
Traceback (most recent call last):
  File "validator.py", line 27, in <module>
    f.bar('')
  File "validator.py", line 6, in validate_input_action
    raise error
ValueError: '' is too short

Updated answer 更新的答案

The error ( NameError: name 'Dummy' is not defined ) occurs because the Dummy class is not defined yet when the validate_input decorator gets Dummy as an argument. 由于validate_input装饰器将Dummy作为参数时,尚未定义Dummy类,因此发生错误( NameError: name 'Dummy' is not defined )。 I guess this could have been implemented differently, but for now that's the way Python works. 我想这可能会以不同的方式实现,但是现在这就是Python的工作方式。 The easiest solution that I see is to stick to using getattr , which will work because it looks up the method at run time. 我看到的最简单的解决方案是坚持使用getattr ,因为它可以在运行时查找方法,所以该方法可以工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM