Decorator which skips `self` for methods











up vote
0
down vote

favorite
1












Let's say we have multiple functions which all accept an URL as their first argument and this URL needs to be validated. This can be nicely solved with a decorator



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


This approach will work and allow me to factor the validation behavior out of many instances of similar functions. But now I would want to write a class method which also takes a validated URL. However, the naive approach will not work



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


because we will end up attempting to validate self and not url. My question is how to write a single decorator which will work for both functions and methods with the minimum amount of boilerplate.



Note 1: I am aware why this happens, it's just that I don't know how to work around this in the most elegant manner.



Note 2: The URL validation problem is just an example, so checking if isinstance(args[0], str) is not a good solution.










share|improve this question
























  • Not sure whether this can be applied here
    – Abdul Niyas P M
    Sep 18 at 17:28












  • Could you define a @staticmethod like def validate_url(url, *args) that runs inside of some_func? You could then decorate validate_url. Seems hacky and non-Pythonic, but that's the first thing I'd try
    – C.Nivs
    Sep 18 at 19:27















up vote
0
down vote

favorite
1












Let's say we have multiple functions which all accept an URL as their first argument and this URL needs to be validated. This can be nicely solved with a decorator



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


This approach will work and allow me to factor the validation behavior out of many instances of similar functions. But now I would want to write a class method which also takes a validated URL. However, the naive approach will not work



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


because we will end up attempting to validate self and not url. My question is how to write a single decorator which will work for both functions and methods with the minimum amount of boilerplate.



Note 1: I am aware why this happens, it's just that I don't know how to work around this in the most elegant manner.



Note 2: The URL validation problem is just an example, so checking if isinstance(args[0], str) is not a good solution.










share|improve this question
























  • Not sure whether this can be applied here
    – Abdul Niyas P M
    Sep 18 at 17:28












  • Could you define a @staticmethod like def validate_url(url, *args) that runs inside of some_func? You could then decorate validate_url. Seems hacky and non-Pythonic, but that's the first thing I'd try
    – C.Nivs
    Sep 18 at 19:27













up vote
0
down vote

favorite
1









up vote
0
down vote

favorite
1






1





Let's say we have multiple functions which all accept an URL as their first argument and this URL needs to be validated. This can be nicely solved with a decorator



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


This approach will work and allow me to factor the validation behavior out of many instances of similar functions. But now I would want to write a class method which also takes a validated URL. However, the naive approach will not work



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


because we will end up attempting to validate self and not url. My question is how to write a single decorator which will work for both functions and methods with the minimum amount of boilerplate.



Note 1: I am aware why this happens, it's just that I don't know how to work around this in the most elegant manner.



Note 2: The URL validation problem is just an example, so checking if isinstance(args[0], str) is not a good solution.










share|improve this question















Let's say we have multiple functions which all accept an URL as their first argument and this URL needs to be validated. This can be nicely solved with a decorator



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


This approach will work and allow me to factor the validation behavior out of many instances of similar functions. But now I would want to write a class method which also takes a validated URL. However, the naive approach will not work



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


because we will end up attempting to validate self and not url. My question is how to write a single decorator which will work for both functions and methods with the minimum amount of boilerplate.



Note 1: I am aware why this happens, it's just that I don't know how to work around this in the most elegant manner.



Note 2: The URL validation problem is just an example, so checking if isinstance(args[0], str) is not a good solution.







python






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 at 21:32

























asked Sep 18 at 17:23









Jatentaki

818




818












  • Not sure whether this can be applied here
    – Abdul Niyas P M
    Sep 18 at 17:28












  • Could you define a @staticmethod like def validate_url(url, *args) that runs inside of some_func? You could then decorate validate_url. Seems hacky and non-Pythonic, but that's the first thing I'd try
    – C.Nivs
    Sep 18 at 19:27


















  • Not sure whether this can be applied here
    – Abdul Niyas P M
    Sep 18 at 17:28












  • Could you define a @staticmethod like def validate_url(url, *args) that runs inside of some_func? You could then decorate validate_url. Seems hacky and non-Pythonic, but that's the first thing I'd try
    – C.Nivs
    Sep 18 at 19:27
















Not sure whether this can be applied here
– Abdul Niyas P M
Sep 18 at 17:28






Not sure whether this can be applied here
– Abdul Niyas P M
Sep 18 at 17:28














Could you define a @staticmethod like def validate_url(url, *args) that runs inside of some_func? You could then decorate validate_url. Seems hacky and non-Pythonic, but that's the first thing I'd try
– C.Nivs
Sep 18 at 19:27




Could you define a @staticmethod like def validate_url(url, *args) that runs inside of some_func? You could then decorate validate_url. Seems hacky and non-Pythonic, but that's the first thing I'd try
– C.Nivs
Sep 18 at 19:27












1 Answer
1






active

oldest

votes

















up vote
1
down vote



accepted










One solution would be to somehow detect whether the decorated function is a class method or not — which seems to be difficult if not impossible (as far as I can tell anyway) to do so cleanly. The inspect module's ismethod() and isfunction() don't work inside a decorator used inside a class definition.



Given that, here's a somewhat hacky way of doing it which checks to see if the decorated callable's first argument has been given the name "self", which is the coding convention for it in class methods (although it is not a requirement, so caveat emptor and use at your own risk).



The following code seems to work in both Python 2 and 3. However in Python 3 it may raise DeprecationWarnings depending on exactly what sub-version is being used—so they have been suppressed in a section of the code below.



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!')


Output:



** 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!





share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














     

    draft saved


    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52391727%2fdecorator-which-skips-self-for-methods%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    1 Answer
    1






    active

    oldest

    votes








    1 Answer
    1






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes








    up vote
    1
    down vote



    accepted










    One solution would be to somehow detect whether the decorated function is a class method or not — which seems to be difficult if not impossible (as far as I can tell anyway) to do so cleanly. The inspect module's ismethod() and isfunction() don't work inside a decorator used inside a class definition.



    Given that, here's a somewhat hacky way of doing it which checks to see if the decorated callable's first argument has been given the name "self", which is the coding convention for it in class methods (although it is not a requirement, so caveat emptor and use at your own risk).



    The following code seems to work in both Python 2 and 3. However in Python 3 it may raise DeprecationWarnings depending on exactly what sub-version is being used—so they have been suppressed in a section of the code below.



    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!')


    Output:



    ** 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!





    share|improve this answer



























      up vote
      1
      down vote



      accepted










      One solution would be to somehow detect whether the decorated function is a class method or not — which seems to be difficult if not impossible (as far as I can tell anyway) to do so cleanly. The inspect module's ismethod() and isfunction() don't work inside a decorator used inside a class definition.



      Given that, here's a somewhat hacky way of doing it which checks to see if the decorated callable's first argument has been given the name "self", which is the coding convention for it in class methods (although it is not a requirement, so caveat emptor and use at your own risk).



      The following code seems to work in both Python 2 and 3. However in Python 3 it may raise DeprecationWarnings depending on exactly what sub-version is being used—so they have been suppressed in a section of the code below.



      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!')


      Output:



      ** 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!





      share|improve this answer

























        up vote
        1
        down vote



        accepted







        up vote
        1
        down vote



        accepted






        One solution would be to somehow detect whether the decorated function is a class method or not — which seems to be difficult if not impossible (as far as I can tell anyway) to do so cleanly. The inspect module's ismethod() and isfunction() don't work inside a decorator used inside a class definition.



        Given that, here's a somewhat hacky way of doing it which checks to see if the decorated callable's first argument has been given the name "self", which is the coding convention for it in class methods (although it is not a requirement, so caveat emptor and use at your own risk).



        The following code seems to work in both Python 2 and 3. However in Python 3 it may raise DeprecationWarnings depending on exactly what sub-version is being used—so they have been suppressed in a section of the code below.



        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!')


        Output:



        ** 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!





        share|improve this answer














        One solution would be to somehow detect whether the decorated function is a class method or not — which seems to be difficult if not impossible (as far as I can tell anyway) to do so cleanly. The inspect module's ismethod() and isfunction() don't work inside a decorator used inside a class definition.



        Given that, here's a somewhat hacky way of doing it which checks to see if the decorated callable's first argument has been given the name "self", which is the coding convention for it in class methods (although it is not a requirement, so caveat emptor and use at your own risk).



        The following code seems to work in both Python 2 and 3. However in Python 3 it may raise DeprecationWarnings depending on exactly what sub-version is being used—so they have been suppressed in a section of the code below.



        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!')


        Output:



        ** 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!






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited Nov 21 at 23:40

























        answered Sep 18 at 20:00









        martineau

        64.8k887174




        64.8k887174






























             

            draft saved


            draft discarded



















































             


            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f52391727%2fdecorator-which-skips-self-for-methods%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            What visual should I use to simply compare current year value vs last year in Power BI desktop

            How to ignore python UserWarning in pytest?

            Alexandru Averescu