This notebook should be named "how to abuse decorators". Honestly, there is very little here that you want to use in a real project.
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.
> This notebook should be named "how to abuse decorators". Honestly, there is very little here that you want to use in a real project.
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.
Annotation and registration are indeed common and proper uses of decorators.
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.
Yes. Lambdas in Python are awkward, but you can definitely use either a lambda or a named function.
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 test
I've used decorators for registration multiple times in the real world. It's nice to be able to visibly attach the registration function call to the object you are registering rather than having it just written after it somewhere.
The @route decorator in Flask has only the side effect of registering a route with the function as handler. I prefer to have a map for big projects where I can see with a glance what routes directs to what functions, but for getting started and for little projects I find it nice and reasonable.
I disagree. Actually I am not a big fan of decorators since for me they tend to obscure WTF is going on in other people's code I'm reading. However, I quite liked the section "Decorators for verification" -- I hadn't seen that use case before and it seems handy to me.
Technically a better definition would be -- "A decorator is a callable object which takes in a callable object and returns a callable object" but I agree that it is much easier wrapping one's head around it when the callable object is a function and decorator just acts as syntactic sugar for higher order functions.
Thanks, I stand corrected :) That makes me wonder , I have not been able to imagine a usecase for it when it doesn't return a callable, the article doesn't help in that regard either. Got some thinking to do I guess.
the article mention multiple examples, one of being the "registering things" when it returns the same function untouched, just put away the function instance in a list or something to use it later.
Returning a function would be returning a callable object, which is what I was alluding to. The section under "Decorator Toolbox" contains no suggested usecases for returning a non-callable object, will try and think of something.
Consider functools.wraps[1] in your own decorators. This uses functools.update_wrapper to copy the name, docstring, and some other other attributes[2] from the wrapped function to the wrapper.
I've used decorators mainly to retry a function if it throws an exception, like retrying DNS queries multiple times, etc. I've never got a chance to do more with them, but this article gives me an incentive to try more.
Medium to large Django projects were the first times that I began to see the power and usefulness of decorators. The most useful one I've used was a decorator to require certain JSON parameters in a POST and optionally specify a list of possible parameters. Makes the API code logic infinitely simpler, especially if you write some custom middleware to handle / report API errors. The code is clear and declarative, and errors are handled gracefully all in one place.
Which is very funny, because you'd get the same without decorators by simply
using properly defined RPC protocol instead of ad hoc developed entangled mess
that passes as REST.
curl | bash is easy to run locally too, and very few people actually read the code that runs to check for “rm -rf /“-type things. When executing untrusted code from the internet, either you read it or your fate is in the author’s hands. No exceptions, not even for beginners getting started in programming via jupyter notebooks.
People also run compiled code all day long. Curl | bash gets a lot of derision for being a bad practice. Unlike other applications that install themselves the one advantage of curling to bash is that its trivial to read the code.
I agree with all the above, but I am more concerned with what's given out. Not a great idea for a tutorial IMO. You can make the point without having to actually using that command - just make it clear as a comment. If this were some other doomsday commands, you are screwed too. Just be nice to your readers.
I've been exposed to decorators (starting with Java) and have 'gotten' them and I will proudly admit that I despise decorators and don't think they are great at all.
I once wrote a decorator that translated the function to Javascript and spawned a web server, allowing the function to be computed in parallel by multiple browsers: https://bitbucket.org/robertodealmeida/parallejax
Decorators are great. One or two or three. 4 is pushing it. Some people get all carried away though. I saw code recently that had a stack of like 8-10 decorators on top of each function. More decorators than function in every case. Thinking it might be time to re-evaluate basic structure in this case but maybe I just don't get it.
"callable" essentially just means you can put a "()" after the name in the code and it works.
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.
So just a function that returns a closure? It can be a real pain to read code like this if it's overdone -- I really would need to trust somebody to let them go meta at their discretion.
I can't see any reason you can't do something like:
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.
Does the code actually work? Wouldn't you need to create a instance within meta_dec and return the result of the __call__ method. See at first glance I won't think it would work if I didn't know __call__ is a magic method in python. I don't think I'm being overly picky but I found the previous example much easier to read -- since it's a pattern I defaulted to myself before across several different languages. Mixing classes and FP like that doesn't resonate well with me tbh, but yeah you could do it ... but should you?
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
Sorry, must of given off the wrong impression. I'm quite happy with FP in general, I'm just aware it is possible to write some pretty horrendous things. But yeah you are completely right about (4), at some point it doesn't seem like overkill anymore.
> 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 :)
They're a kind of advice [1]. Advice was indeed subsumed by CLOS method combination, but existed prior to that and has a life of its own, for example in Aspect Oriented Programming.
> To be honest, decorators were always, idk, I felt like there was something off about them.
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.
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.