Re: [Python-ideas] Bad programming style in decorators?
Yes, I knew this already. This is what I understood when I said I understood. I thought the *decorator* had a function inside it, not the *decorator factory*. So of course the decorator factory is only called twice. On Mon, Jan 4, 2016 at 10:48 AM, Andrew Barnert <abarnert@yahoo.com> wrote:
(off-list, because I think this is no longer relevant to suggesting changes to Python)
On Sunday, January 3, 2016 10:58 AM, u8y7541 The Awesome Person < surya.subbarao1@gmail.com> wrote:
Thanks for explaining the differences tho. I got confused between the decorator and the decorator factory, thinking the decorator had a function inside it. Sorry :)
I think you're _still_ confused. Not your fault, because this is confusing stuff. The decorator actually does (usually) have a function definition in it too. But the decorated function--the wrapper that it defines--doesn't. And that's the thing that you usually call a zillion times, not the decorator or the decorator factory.
Let's make it concrete and as simple as possible, and then walk through all the details:
def div(id): def decorator(func):
@wraps(func) def wrapper(*args, **kw): return "<div id='{}'>{}</div>".format(id, func(*args, **kw)) return wrapper return decorator
@div('eggs') def eggs(): return 'eggs'
@div('cheese') def cheeses(): return '<ul><li>gouda</li><li>edam</li></ul>'
for _ in range(1000000): print(eggs()) print(cheeses())
When you're importing the module and hit that "@div('eggs')", that calls the "div" factory. The only other time that happens is at the "@div('cheese')". So, the factory does of course create a function, but the factory only gets called twice in your entire program. (Also, the functions it creates become garbage as soon as they're called, so by the time you get to the "for" loop, they've both been deleted.)
When you finish the "def eggs():" or "def cheeses():" statement, the decorator function "decorator" returned by "div('eggs')" or "div('cheese')" gets called. And that decorator also creates a function. But each one only gets called once in your entire program, and there are only two of them, so that's only two extra function definitions. (Obviously these two aren't garbage--they're the functions you call inside the loop.)
When you hit that "print(eggs())" line, you're calling the decorated function "wrapper", returned by the decorator function "decorator", returned by the decorator factory function "div". That function does not have a function definition inside of it. So, calling it a million times doesn't cost anything in function definitions.
And of course "div" itself isn't garbage--you don't need it anymore, but if you don't tell Python "del div", it'll stick around. So, at your peak, in the middle of that "for" loop, you have 5 function definitions around (div, decorated eggs, original eggs, decorated cheese, original cheese).
If you refactor things differently, you could have 5 functions, 2 class objects, 2 class instances (your intended class-style design); or 4 functions, 1 class object, 2 class instances, 2 bound methods, (a simple class-style decorator); 4 functions, 1 class object, 2 class instances, (the smallest possible class-style decorator); 2 functions but with duplicated code and string constants (by inlining div directly into each function); or 3 functions (with eggs and cheese explicitly calling div--I suspect this would be actually be smallest here); etc. The difference is going to be a few hundred bytes one way or the other, and the smallest possible design may have a severe cost in (time) performance or in readability. But, as you suggested, and Nick confirmed, there are cases where it matters. Which means it's worth knowing how to write all the different possibilities, and evaluate them analytically, and test them.
-- -Surya Subbarao
participants (1)
-
u8y7541 The Awesome Person