代码之家  ›  专栏  ›  技术社区  ›  Jatentaki

为方法跳过“self”的Decorator

  •  0
  • Jatentaki  · 技术社区  · 7 年前

    def validate_url(f):
       def validated(url, *args, **kwargs):
           assert len(url.split('.')) == 3 # trivial example
           return f(url, *args, **kwargs)
        return validated
    
    @validate_url
    def some_func(url, some_other_arg, *some_args, **some_kwargs):
        pass
    

    class SomeClass:
        @validate_url
        def some_method(self, url, some_other_args):
            pass
    

    因为我们最终会试图验证 self 而不是 url . 我的问题是如何用最少的样板编写一个既适用于函数又适用于方法的装饰器。

    注1:我知道 为什么? 这种情况时有发生,只是我不知道如何用最优雅的方式来解决这个问题。

    isinstance(args[0], str)

    1 回复  |  直到 6 年前
        1
  •  1
  •   martineau    6 年前

    一种解决方案是以某种方式检测修饰函数是否是类方法,如果不是不可能的话(据我所知),很难做到干净。这个 inspect ismethod() isfunction() 不要在类定义中使用的装饰器中工作。

    考虑到这一点,这里有一种有点黑客的方法来检查修饰的callable的第一个参数是否被命名为 "self" ,这是它在类方法中的编码约定(尽管 ,所以 caveat emptor

    下面的代码似乎可以在python2和python3中使用。但是在python3中,它可能会 DeprecationWarning 取决于所使用的子版本,所以它们在下面的代码部分中被抑制了。

    from functools import wraps
    import inspect
    import warnings
    
    def validate_url(f):
        @wraps(f)
        def validated(*args, **kwargs):
            with warnings.catch_warnings():
                # Suppress DeprecationWarnings in this section.
                warnings.simplefilter('ignore', category=DeprecationWarning)
    
                # If "f"'s first argument is named "self",
                # assume it's a method.
                if inspect.getargspec(f).args[0] == 'self':
                    url = args[1]
                else:  # Otherwise assume "f" is a ordinary function.
                    url = args[0]
            print('testing url: {!r}'.format(url))
            assert len(url.split('.')) == 3  # Trivial "validation".
            return f(*args, **kwargs)
        return validated
    
    @validate_url
    def some_func(url, some_other_arg, *some_args, **some_kwargs):
        print('some_func() called')
    
    
    class SomeClass:
        @validate_url
        def some_method(self, url, some_other_args):
            print('some_method() called')
    
    
    if __name__ == '__main__':
        print('** Testing decorated function **')
        some_func('xxx.yyy.zzz', 'another arg')
        print('  URL OK')
        try:
            some_func('https://bogus_url.com', 'another thing')
        except AssertionError:
            print('  INVALID URL!')
    
        print('\n** Testing decorated method **')
        instance = SomeClass()
        instance.some_method('aaa.bbb.ccc', 'something else')  # -> AssertionError
        print('  URL OK')
        try:
            instance.some_method('foo.bar', 'arg 2')  # -> AssertionError
        except AssertionError:
            print('  INVALID URL!')
    

    ** Testing decorated function **
    testing url: 'xxx.yyy.zzz'
    some_func() called
      URL OK
    testing url: 'https://bogus_url.com'
      INVALID URL!
    
    ** Testing decorated method **
    testing url: 'aaa.bbb.ccc'
    some_method() called
      URL OK
    testing url: 'foo.bar'
      INVALID URL!