Note
Since compact dict has landed in 3.6, __definition_order__has been removed.cls.__dict__ now mostly accomplishes the samething instead.
The class definition syntax is ordered by its very nature. Classattributes defined there are thus ordered. Aside from helping withreadability, that ordering is sometimes significant. If it wereautomatically available outside the class definition then theattribute order could be used without the need for extra boilerplate(such as metaclasses or manually enumerating the attribute order).Given that this information already exists, access to the definitionorder of attributes is a reasonable expectation. However, currentlyPython does not preserve the attribute order from the classdefinition.
This PEP changes that by preserving the order in which attributesare introduced in the class definition body. That order will now bepreserved in the__definition_order__ attribute of the class.This allows introspection of the original definition order, e.g. byclass decorators.
Additionally, this PEP requires that the default class definitionnamespace be ordered (e.g.OrderedDict) by default. Thelong-lived class namespace (__dict__) will remain adict.
The attribute order from a class definition may be useful to toolsthat rely on name order. However, without the automatic availabilityof the definition order, those tools must impose extra requirements onusers. For example, use of such a tool may require that your class usea particular metaclass. Such requirements are often enough todiscourage use of the tool.
Some tools that could make use of this PEP include:
When a class is defined using aclass statement, the class bodyis executed within a namespace. Currently that namespace defaults todict. If the metaclass defines__prepare__() then the resultof calling it is used for the class definition namespace.
After the execution completes, the definition namespace iscopied into a newdict. Then the original definition namespace isdiscarded. The new copy is stored away as the class’s namespace andis exposed as__dict__ through a read-only proxy.
The class attribute definition order is represented by the insertionorder of names in thedefinition namespace. Thus, we can haveaccess to the definition order by switching the definition namespaceto an ordered mapping, such ascollections.OrderedDict. This isfeasible using a metaclass and__prepare__, as described above.In fact, exactly this is by far the most common use case for using__prepare__.
At that point, the only missing thing for later access to thedefinition order is storing it on the class before the definitionnamespace is thrown away. Again, this may be done using a metaclass.However, this means that the definition order is preserved only forclasses that use such a metaclass. There are two practical problemswith that:
First, it requires the use of a metaclass. Metaclasses introduce anextra level of complexity to code and in some cases (e.g. conflicts)are a problem. So reducing the need for them is worth doing when theopportunity presents itself.PEP 422 andPEP 487 discuss this atlength. We have such an opportunity by using an ordered mapping (e.g.OrderedDict for CPython at least) for the default class definitionnamespace, virtually eliminating the need for__prepare__().
Second, only classes that opt in to using theOrderedDict-basedmetaclass will have access to the definition order. This is problematicfor cases where universal access to the definition order is important.
Part 1:
__definition_order__ attribute__definition_order__ is atuple of identifiers (orNone)__definition_order__ is always set:__definition_order__ is defined in the class body then itmust be atuple of identifiers orNone; any other valuewill result inTypeError__definition_order__ set toNone__prepare__() returned something other thanOrderedDict (or a subclass) have their__definition_order__set toNone (except where #2 applies)Not changing:
dir() will not depend on__definition_order____getattribute__ methods are unconstrainedregarding__definition_order__Part 2:
OrderdDict)cls.__dict__ does not change, remaining a read-only proxy arounddictNote that Python implementations which have an ordereddict won’tneed to change anything.
The following code demonstrates roughly equivalent semantics for bothparts 1 and 2:
classMeta(type):@classmethoddef__prepare__(cls,*args,**kwargs):returnOrderedDict()classSpam(metaclass=Meta):ham=Noneeggs=5__definition_order__=tuple(locals())
Use of a tuple reflects the fact that we are exposing the order inwhich attributes on the class weredefined. Since the definitionis already complete by the time__definition_order__ is set, thecontent and order of the value won’t be changing. Thus we use a typethat communicates that state of immutability.
There are some valid arguments for making__definition_order__a read-only attribute (likecls.__dict__ is). Most notably, aread-only attribute conveys the nature of the attribute as “complete”,which is exactly correct for__definition_order__. Since itrepresents the state of a particular one-time event (execution ofthe class definition body), allowing the value to be replaced wouldreduce confidence that the attribute corresponds to the original classbody. Furthermore, often an immutable-by-default approach helps tomake data easier to reason about.
However, in this case there still isn’t astrong reason to counterthe well-worn precedent found in Python. Per Guido:
Idon't see why it needs to be a read-only attribute. There areveryfewofthose--ingeneralweletusersplayaroundwiththingsunlesswehaveahardreasontorestrictassignment(e.g.theinterpreter's internal state could be compromised). I don'tseesuchahardreasonhere.
Also, note that a writeable__definition_order__ allows dynamicallycreated classes (e.g. by Cython) to still have__definition_order__properly set. That could certainly be handled through specificclass-creation tools, such astype() or the C-API, without the needto lose the semantics of a read-only attribute. However, with awriteable attribute it’s a moot point.
__definition_order__ is centered on the class definitionbody. The use cases for dealing with the class namespace (__dict__)post-definition are a separate matter.__definition_order__ wouldbe a significantly misleading name for a feature focused on more thanclass definition.
Names starting and ending with “__” are reserved for use by theinterpreter. In practice they should not be relevant to the users of__definition_order__. Instead, for nearly everyone they would onlybe clutter, causing the same extra work (filtering out the dundernames) for the majority. In cases where a dunder name is significant,the class definitioncould manually set__definition_order__,making the common case simpler.
However, leaving dunder names out of__definition_order__ meansthat their place in the definition order would be unrecoverably lost.Dropping dunder names by default may inadvertently cause problems forclasses that use dunder names unconventionally. In this case it’sbetter to play it safe and preserveall the names from the classdefinition. This isn’t a big problem since it is easy to filter outdunder names:
(namefornameincls.__definition_order__ifnot(name.startswith('__')andname.endswith('__')))
In fact, in some application contexts there may be other criteria onwhich similar filtering would be applied, such as ignoring any namestarting with “_”, leaving out all methods, or including onlydescriptors. Ultimately dunder names aren’t a special enough case tobe treated exceptionally.
Note that a couple of dunder names (__name__ and__qualname__)are injected by default by the compiler. So they will be included eventhough they are not strictly part of the class definition body.
A key objective of adding__definition_order__ is to preserveinformation in class definitions which was lost prior to this PEP.One consequence is that__definition_order__ implies an originalclass definition. UsingNone allows us to clearly distinguishclasses that do not have a definition order. An empty tuple clearlyindicates a class that came from a definition statement but did notdefine any attributes there.
The absence of an attribute requires more complex handling thanNonedoes for consumers of__definition_order__.
If__definition_order__ is manually set in the class body then itwill be used. We require it to be a tuple of identifiers (orNone)so that consumers of__definition_order__ may have a consistentexpectation for the value. That helps maximize the feature’susefulness.
We could also allow an arbitrary iterable for a manually set__definition_order__ and convert it into a tuple. However, notall iterables infer a definition order (e.g.set). So we opt infavor of requiring a tuple.
Python doesn’t make much effort to hide class-specific attributesduring lookup on instances of classes. While it may make senseto consider__definition_order__ a class-only attribute, hiddenduring lookup on objects, setting precedent in that regard isbeyond the goals of this PEP.
__slots__ will be added to__definition_order__ like anyother name in the class definition body. The actual slot nameswill not be added to__definition_order__ since they aren’tset as names in the definition namespace.
Since the definition order is not preserved in__dict__, it islost once class definition execution completes. Classescouldexplicitly set the attribute as the last thing in the body. However,then independent decorators could only make use of classes that had doneso. Instead,__definition_order__ preserves this one bit of infofrom the class body so that it is universally available.
Arguably, most C-defined Python types (e.g. built-in, extension modules)have a roughly equivalent concept of a definition order. So conceivably__definition_order__ could be set for such types automatically. ThisPEP does not introduce any such support. However, it does not prohibitit either. However, since__definition_order__ can be set at anytime through normal attribute assignment, it does not need any specialtreatment in the C-API.
The specific cases:
This PEP does not break backward compatibility, except in the case thatsomeone reliesstrictly ondict as the class definition namespace.This shouldn’t be a problem sinceissubclass(OrderedDict,dict) istrue.
In addition to the class syntax, the following expose the new behavior:
Also, the 3-argument form ofbuiltins.type() will allow inclusionof__definition_order__ in the namespace that gets passed in. Itwill be subject to the same constraints as when__definition_order__is explicitly defined in the class body.
Pending feedback, the impact on Python implementations is expected tobe minimal. All conforming implementations are expected to set__definition_order__ as described in this PEP.
The implementation is found in thetracker.
Instead of storing the definition order in__definition_order__,the now-ordered definition namespace could be copied into a newOrderedDict. This would then be used as the mapping proxied as__dict__. Doing so would mostly provide the same semantics.
However, usingOrderedDict for__dict__ would obscure therelationship with the definition namespace, making it less useful.
Additionally, (in the case ofOrderedDict specifically) doingthis would require significant changes to the semantics of theconcretedict C-API.
There has been some discussion about moving to a compact dictimplementation which would (mostly) preserve insertion order. Howeverthe lack of an explicit__definition_order__ would still remainas a pain point.
PEP 422introduced a new “namespace” keyword arg to class definitionsthat effectively replaces the need to__prepare__().However, the proposal was withdrawn in favor of the simplerPEP 487.
This has all the same problems as writing your own metaclass. Theonly advantage is that you don’t have to actually write thismetaclass. So it doesn’t offer any benefit in the context of thisPEP.
Each class’s__qualname__ is determined at compile-time.This same concept could be applied to__definition_order__.The result of composing__definition_order__ at compile-timewould be nearly the same as doing so at run-time.
Comparative implementation difficulty aside, the key differencewould be that at compile-time it would not be practical topreserve definition order for attributes that are set dynamicallyin the class body (e.g.locals()[name]=value). However,they should still be reflected in the definition order. Onepossible resolution would be to require class authors to manuallyset__definition_order__ if they define any class attributesdynamically.
Ultimately, the use ofOrderedDict at run-time or compile-timediscovery is almost entirely an implementation detail.
This document has been placed in the public domain.
Source:https://github.com/python/peps/blob/main/peps/pep-0520.rst
Last modified:2025-02-01 08:59:27 GMT