Hacker News new | comments | show | ask | jobs | submit login
Painless Decorators in Python (hackflow.com)
97 points by Suor 1266 days ago | hide | past | web | 26 comments | favorite

>to reduce clutter and expose your intentions. However, there is usual cost. This as any abstraction brings an additional layer of complexity on top of python. And python could be seen as a layer on top of c, and that as one above asm, and that

I took a look at the rest of funcy and it seems a great package, thanks! However, a python dev I'm expected to know how decorators work not how funcy makes decorators work. So unless this was part of the standard lib most devs who already know how decorators work would end up having to read another doc. I dont think its an issue of abstraction (the code is straight forward python: https://github.com/Suor/funcy/blob/master/funcy/decorators.p... ) but rather uniformity. I think coping with the standard library until something like this becomes part of it is the best strategy.

This is a matter of taste. I think certain standard abstractions in Python are pretty terrible, and really need to be upgraded. The standard way of doing function decoration isn't good. Neither is the standard way of accessing mysql. These, and a number of other things, really should have better abstractions built on them to amplify productivity and readability.

Sometimes there's a war between standard techniques and DRY principles. I've observed a lot of good coming from adhering to the idea of never copying code or doing boilerplate. But as you point out, sometimes it can lead to a bunch of nonstandard code. But I think any non-trivial codebase is going to have a chunk of abstractions it creates and builds upon to amplify its own expressiveness. When you can import some of those abstractions, that's arguably a win. There are lots of people that would (and have) disagreed with me on this point, but it is a subjective matter of taste.

That's a good point. A lot of the changes in python start off as alternate packages though, and as package== more popular--> more users --> more features--> included as a part of stdlib (e.g venv). And python's modular structure makes it quite transparent what outside stuff is being used

Usually, standard libraries tend to change only when a better way is demonstrated not just as abstractly doable, but becomes used in practice over what is currently in the standard library, because, otherwise, what's the reason to change what's established?

The article is interesting but really if you want to make decorators easier to understand don't introduce another layer of abstraction, use a class. The __init__ function handles the decorator arguments just like you would expect from a class. The __call__ function is called when the decorator is called, just return your new function and well; your done. And it's all pretty straight forward.

You will still need a wrapper function and update it's metadata. Which is even more boilerplate. Or you can hide that in base Decorator class, but then we'll back to another layer of abstraction.

So it's either more boilerplate or more abstraction. Choose exactly one.

Well if you use functools like you started with in your lead up examples your talking about what two lines of boilerplate (functools decorator and function definition)? Personally I often times just add the four lines of boiler plate and forgo functools.... Look I'm not saying that your library is a bad idea; Its neat, in fact it seems to handle a few thing better than the more well known decorator library does.

( Out of curiosity though how about the functions signature, I think functools doesn't maintain the original functions signature but I think decorator does, what does funcy do? )

For me personally I feel its better to have the decorator all in one place where you can see everything that is going on, and making it a class makes it pretty straight forward to understand.

funcy doesn't preserve function signature. The only way for now to do this is to use exec, e.g. compile code from source or AST, which I choose to avoid.

I tend to see functions as merely a special-case of classes anyway. In a certain mindset, I consider this:

    def foo(...):
shorthand for this:

    class Foo(object):
        def __call__(self, ...):
    foo = Foo()
And so, if the situation warrants, I may expand some function to instead be a full class, take init arguments, etc. But commonly I don't. Even the case of a (not too complex) decorator I feels is handled nicer by closure than by instance variables.

How does this compare to the decorator module?


This handles decorators with arguments and makes a general case of passing same arguments to function as to wrapper less cluttered.

Decorators with arguments are much better handled by classes IMO. This does indeed appear to make that one (common) case of passing the function arguments through unchanged marginally simpler. But it makes every other case worse. Compare, for example:

    def int_args(func, *args):
        """Coerces any function arguments to ints"""
        return func(*map(int, args))
to your:

    def int_args(call):
        """Coerces any function arguments to ints"""
        return call._func(*map(int, call._args))
In the former case, everything is defined explicitly and it's clear that the function only accepts positional arguments. In the latter case, you're required to know the API of the library: that necessary objects are hidden behind the attributes call._func and call._args, and that the keyword arguments are silently discarded. It's an extremely leaky abstraction.

You probably think that passing 'self' to every method is boilerplate too, and arguably it is. But the Python philosophy is "explicit is better than implicit".

It might help to show the same examples from that module's documentation, how they are similar or different.

I still don't understand this after a skim of your post, since one example has

    def some_decorator(func, args, kwargs):
while the next goes

    def some_decorator(call):
Does this introspect on the parameters of some_decorator?

FWIW I'm happy enough with https://github.com/darius/sketchbook/blob/master/misc/decora...

The first part of a post is mere reasoning showing how I got to the actual interface I use. @decorator works with `call` not with `func, args, *kwargs`.

Ah, OK. So there's no way to express an example that needs to get at func, etc.?

I think I'd separate out an @decorator that fixes the metadata on the returned function, and another one, using it, that makes a decorator that works on the call() interface.

This is much more useful and more explicit than Sour's. I would rather use this old one.

the decorator module also preserves function signature, which is nice (check out the FunctionMaker class - it's crazy)

Great library! I wrote something similar once [1] and thought two special cases might be useful to optimize for: a predecoration that modifies the arguments to a function and a postdecoration that modifies the return value.

1. https://github.com/anfedorov/decorations

It's important when adding a layer of abstraction like this to consider both how much it makes this simpler, but also how much you're going to use it. While it makes some common decorator patterns easier, your code really shouldn't have so many decorators that having to understand this library is made up for in the number of places it simplifies the code.

If I'm writing code and using a lot of different decorators everywhere I take that as a sign that something is probably wrong.

You might want to look into class-based decorators, they take some of the pain away.

This looks like even more boilerplate.

Arguably, it can make things easier to read in cases where you want "static" variables, eg. a decorator that synchronises a function with a lock. The lock shouldn't be passed in by the user, nor should it be created in an individual invocation of the decorated function, so having a class to contain the state is useful.

That's a great situation in which to use a class: state you don't want to pass in every time it's used. It doesn't seem to connect to the idea of a decorator, though. Rather than decorating the function, why not just make it a method of the state-managing class? Or if it shouldn't be so closely coupled, why not just pass it the result of invoking a different method of the class?

I don't imply that there isn't a good example of a decorator that should be a class. I just don't think we've seen one on this thread yet.

I like this, will try it next time I need a decorator.

What about iterators/yield?

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | DMCA | Apply to YC | Contact