In most decorator tutorials, it's taught using functions inside functions.
Isn't this inefficient because every time the decorator is called, you're
redefining the function, which takes up much more memory? I prefer defining
decorators as classes with __call__ overridden. Is there a reason why
decorators are taught with functions inside functions?
--
-Surya Subbarao
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(a)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(a)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
Speaking of using ABCs more, where should we put ABCs which have nothing to
do with collections? As of right now all ABCs seem to get shoved into
collections.abc, but e.g. Awaitable and Coroutine are not types of
collections. I personally want to add a context manager ABC with a default
__exit__.
I opened http://bugs.python.org/issue25637 to discuss this, but I figured a
wider discussion wouldn't hurt. Some suggest just putting the ABCs into the
abc module. We could create an interfaces module (top-level or a submodule
of ABC). The other option is to put the ABCs in subject-specific modules,
so my context manager one would go into contextlib (either top-level or an
abc submodule); don't know where the coroutine ones would go since it might
be overloading asyncio if we out them there.
Anyway, the key point is collections.abc is starting to get non-collections
stuff and if we are going to start pushing ABCs more we should decide how
we want to organize them in general in the stdlib and instead of dumping
them into collections.abc.
On Sun, Dec 27, 2015, 09:35 Guido van Rossum <guido(a)python.org> wrote:
> I think there's a lot of interesting stuff in this thread. Personally I
> don't think we should strive to distinguish between mappings and sequences
> structurally. We should instead continue to encourage inheriting from (or
> registering with) the corresponding ABCs. The goal is to ensure that
> there's one best-practice way to distinguish mappings from sequences, and
> it's by using isinstance(x, Sequence) or isinstance(x, Mapping).
>
> If we want some way to turn something that just defines __getitem__ and
> __len__ into a proper sequence, it should just be made to inherit from
> Sequence, which supplies the default __iter__ and __reversed__.
> (Registration is *not* good enough here.) If we really want a way to turn
> something that just supports __getitem__ into an Iterable maybe we can
> provide an additional ABC for that purpose; let's call it a HalfSequence
> until we've come up with a better name. (We can't use Iterable for this
> because Iterable should not reference __getitem__.)
>
> I also think it's fine to introduce Reversible as another ABC and
> carefully fit it into the existing hierarchy. It should be a one-trick pony
> and be another base class for Sequence; it should not have a default
> implementation. (But this has been beaten to death in other threads -- it's
> time to just file an issue with a patch.)
>
>
> --
> --Guido van Rossum (python.org/~guido)
>
_______________________________________________
> Python-ideas mailing list
> Python-ideas(a)python.org
> https://mail.python.org/mailman/listinfo/python-ideas
> Code of Conduct: http://python.org/psf/codeofconduct/