Painless Decorators in Python
hackflow.com
hackflow.com
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.
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.
So it's either more boilerplate or more abstraction. Choose exactly one.
( 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.
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. @decorator.decorator
def int_args(func, *args):
"""Coerces any function arguments to ints"""
return func(*map(int, args))
to your: @funcy.decorator
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".
I still don't understand this after a skim of your post, since one example has
@decorator
def some_decorator(func, args, kwargs):
while the next goes @decorator
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...
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.
If I'm writing code and using a lot of different decorators everywhere I take that as a sign that something is probably wrong.
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.
Deleted comment