Python decorators they don't tell you about
github.com
github.com
I completely agree in that saying "a decorator is a function that takes a function and returns a function" isn't technically correct since you can do much more than just that; but for readability, maintainability and other real world purposes, you might want to keep to that definition.
"Decorators for preconditions" look pretty neat as well. It's a little sad that the preconditions themselves have to be written inside strings though.
Isn’t it possible to pass a lambda instead?
In fact, you could define a number of useful precondition functions in a single module and use them throughout a project. A couple of higher-order functions could make the post's examples safer and nicer-looking too. For example:
@precondition(starts_with('The year is '))
where the starts_with precondition is: def starts_with(param):
def test(s):
return s.startswith(param)
return testCan you elaborate on that, please?
Considering the examples given by the author, I think they would be a better option.
- they can only contain a single expression
- they cannot contain statements
They're called functions.
Annotation, registration, dispatch and verification are pretty much the most common uses for decorators. If using decorators for these is bad, then decorators aren't needed at all: function wrapping is much more rarely used than those other four, and would not warrant an additional syntax construct.
What I dislike are mostly the inputs 8, 9, 13, 14, and 23 that could give nasty ideas to an inexperienced developer, while a more experienced developer wouldn't need them to understand that a decorator is just a syntactic sugar and that, yes, "applying a decorator d to a function x [is] the same as writing the definition of x, then x = d(x)" (which the author seems to refute).
I understand that those examples are solely intended to show that "it works", but they aren't educational to me.
[1]: https://docs.python.org/3/library/functools.html#functools.w...
[2]: https://docs.python.org/3/library/functools.html#functools.u...
EDIT: s/wrapped/wraps/; tip of the hat to @ramnes
There's also a wealth of resource in the docs!
https://github.com/osteele/callgraph/blob/master/examples/ca... has some examples of using @lru_cache, together with a decorator (@callgraph) defined in that repo, that helps visualize recursion and memoization.
[1]: https://docs.python.org/3/library/functools.html#functools.l...
And it is right that the fate is in the author's hands. It's those hands that we are trying to tame.
Hmm? I don't see any such PR.
> Hmm? I don't see any such PR.
Hmm, neither do I.
They can also be abused in such ways that clarity is actually destroyed.
https://notebooks.azure.com/smortaz/libraries/PythonDecorato...
Click Clone & Run.
fun
crazy and fun
Python being duck-typed, what's the distinction?
isinstance(obj, types.FunctionType)You can "call" a class. Doing so allocates an instance of that class (Or calls the __new__ function if one is defined) and returns it, first calling the __init__ function of that class if necessary.
You can also "call" an object if its class has a __call__ function defined.
@decorator_with_params("Some string")
def my_function():
pass
you have to make 'decorator_with_params' a function that returns a decorator; i.e. def decorator_with_params(param):
def real_decorator(function):
def inner(*args, **kwargs):
print(param)
function(*args, **kwargs)
return inner
return real_decorator
(You can also do something similar with callable objects, but that's ickier IMO.)Demo of both (decorator as instance method and decorator with parameters): https://gist.github.com/jonathonw/dac88b715aca4e4f1b2f513e1f...
class MetaDec(args, here):
...
def __call__(self, func):
# Decorate function here
def meta_dec(args, here):
return MetaDec(args, here)
@meta_dec
def decorated:
...
(apologies for any syntax fails, python is not my best language)At which point one would imagine you can abstract out the __call__ part and just have a 'decorate' routine on any class designed to work what way or similar.
Whether this is an improvement or not over closures almost certainly depends on the use case, of course.
Given its reliance on '...' and the fact I'm not great at python and typed it straight into the comment ... no. No it doesn't.
> Wouldn't you need to create a instance within meta_dec and return the result of the __call__ method.
No, like with ES6 parameterised decorators return a function that then gets called with the thing being decorated.
> See at first glance I won't think it would work if I didn't know __call__ is a magic method in python.
(1) my reply was assuming the context of us both having just read the article, which has an example with __call__
(2) as I said in my original comment, you would abstract this away (and document it), I was sketching out an alternative implementation path to allow you to have an object instead of a closure since you didn't seem to be happy with closures, not offering a complete solution
> Mixing classes and FP like that doesn't resonate well with me tbh, but yeah you could do it ... but should you?
(3) if you don't like closures and you don't like objects, I'm not sure what other combined function+data abstraction is available in python that you wouldn't also dislike
(4) I'd probably consider doing it if I had a bunch of similar decorators and found the functional approach was starting to groan at the seams - I use objects for 'parameterised bag of functions, possibly with inheritance' quite a lot for that purpose
> It can be a real pain to read code like this if it's overdone
wherein (4) is, when things start getting complicated, a way to end up avoiding things turning into closure soup. Also being able to selectively override things with inheritance can really help factor things out and clean things up.
> at some point it doesn't seem like overkill anymore.
Though I suspect "at that point, ask yourself if you're overengineering it and only if you're sure you aren't consider moving to a class" definitely applies :)
class FancyDecorator():
__init__(self, *args):
self.args = args
__call__(self, fn):
return decorate_fn_with_args(fn, self.args)
dec = FancyDecorator(42)
@dec
def foo():
pass
@FancyDecorator(41)
def bar()
pass
Might have it wrong since I haven't messed with decorators in a while and they aren't exactly what one would call simple.--edit--
code formatting is all wonky so...
[1]: https://en.wikipedia.org/wiki/Advice_(programming)
[2]: https://en.wikipedia.org/wiki/Aspect-oriented_programming
I think they're one of those things you don't need to use unless you need to use them...though, honestly, you probably don't ever need to use them unlike metaclasses where sometimes they are the only way to do something in a reasonable manner.
By their definition, you don't ever need to use them. They're just a convenient syntactic sugar.
@foo
def bar():
...
is, as far as I know, exactly equivalent to def bar():
...
bar = foo(bar)