|
|
Log in / Subscribe / Register

PEP 649 revisited

PEP 649 revisited

Posted Aug 18, 2021 21:12 UTC (Wed) by pj (subscriber, #4506)
Parent article: PEP 649 revisited

@cached_property is now in the stdlib ; having it be used to implement PEP649 seems straightforward.


to post comments

PEP 649 revisited

Posted Aug 18, 2021 23:26 UTC (Wed) by NYKevin (subscriber, #129325) [Link]

Well yes, but... we need to clear up a couple of things about how @property, @cached_property, and annotations work.

@property and @cached_property are, essentially, syntactic sugar for a language feature called "descriptors." A descriptor works like this:

0. You declare a class, let's call it Foo.
1. You override the methods __get__() (usually), __set__() (sometimes), and/or __delete__() (rarely).
2. You make an instance of Foo, and assign it to a class variable inside of another class (which I'm going to call Bar, and let's call the class variable baz).
3. You create an instance of Bar, which we can call qux.
4. You try to access qux.baz.
5. Subject to certain conditions, the language will then call __get__(), __set__(), or __delete__() on your Foo instance, as applicable to the type of access you attempted.

In this context, instances of Foo are called "descriptors." If either of __set__() or __delete__() are implemented, then Foo instances are called "data descriptors," and have slightly different semantics (in particular, they take priority over qux.__dict__['baz'], if it exists, while a non-data descriptor is only invoked if there's no instance variable and the access would otherwise fail). The full documentation may be found at https://docs.python.org/3/reference/datamodel.html#implem...

@property and @cached_property are basically just doing #0-2 for you. The functions which you declare inside of a class are considered class variables, and the decorator syntax reassigns them to the descriptor object created by property() or cached_property(). The underlying functionality has been around since Python 2.x, and so you don't actually need explicit language support to do this kind of chicanery (or rather, the language support which you need already existed). It's always been possible to write out the logic by hand, and that is what PEP 649 currently proposes doing (probably to make it easier for end users to inspect __co_annotations__ separately for debugging purposes). But at runtime, the difference is largely immaterial.

Incidentally, plain old functions are already descriptors (at the C level, naturally). That's how the self parameter gets set on a method call. The @staticmethod decorator basically wraps it in a descriptor-that-returns-the-original-function to defeat this mechanism (the descriptor protocol is not recursive), and @classmethod is also a descriptor. This is why you can sometimes run into problems if you try to stack those decorators or do anything "interesting" with them.

You can use this to build really interesting things, like Django's model/field syntax, which dynamically transpiles Python method calls into SQL.


Copyright © 2026, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds