Reputation: 3231
I am trying to use an 'ordered class' as described in PEP 3115 (that is, a class whose members can be accessed in the order they were declared). The implementation given there is
# The custom dictionary
class member_table(dict):
def __init__(self):
self.member_names = []
def __setitem__(self, key, value):
# if the key is not already defined, add to the
# list of keys.
if key not in self:
self.member_names.append(key)
# Call superclass
dict.__setitem__(self, key, value)
# The metaclass
class OrderedClass(type):
# The prepare function
@classmethod
def __prepare__(metacls, name, bases): # No keywords in this case
return member_table()
# The metaclass invocation
def __new__(cls, name, bases, classdict):
# Note that we replace the classdict with a regular
# dict before passing it to the superclass, so that we
# don't continue to record member names after the class
# has been created.
result = type.__new__(cls, name, bases, dict(classdict))
result.member_names = classdict.member_names
return result
class MyClass(metaclass=OrderedClass):
# method1 goes in array element 0
def method1(self):
pass
# method2 goes in array element 1
def method2(self):
pass
There are a few things I am confused about. Firstly, is there a reason why __prepare__
is a classmethod
? The definition doesn't use metacls
- is this just a convention?
Secondly, when I try this code, '__module__'
ends up in MyClass.member_names
before 'method1'
and 'method2'
, apparently contradicting the comments which claim 'method1'
is the first element. Why does this special attribute end up in the list while no others do? Are there any others that might surprise me (apart from __doc__
if the class has a docstring, and any I define explicitly)?
Finally, this implementation does not retrieve the member_names
from base classes. If I want to achieve that, is there anything wrong with the following change to __prepare__
(apart from the fact that it doesn't check for duplicates)?
@classmethod
def __prepare__(metacls, name, bases):
prep_dict = member_table()
for base in bases:
try:
prep_dict.member_names.extend(base.member_names)
except AttributeError:
pass
return prep_dict
Upvotes: 4
Views: 1959
Reputation: 110271
Firstly, is there a reason why
__prepare__
is a classmethod? The definition doesn't use metacls - is this just a convention?
As pointed out in the comments, when __prepare__
is called the class itself is not yet instantiated. That means, if it where an ordinary method (with self
as the first parameter) - there would be no value to become the self
in this call.
And it makes sense, because what __prepare__
does is to return a dict like object to be used instead of an ordinary dict in the parsing of the class body - so it does not depend on the class being created at all.
If, as mentioned in the comments, it were a staticmethod
(meaning it would not get the metacls
first parameter) then __prepare__
would be unable to access any other methods of the metaclass -- this is unnecessarily limiting.
And yes, just like self
, metacls
in this case is just a convention - an ordinary Python identifier. (Note that when classmethods are used in ordinary classes, the first parameter is usually denoted cls
instead of metacls
.)
Secondly, when I try this code,
__module__
ends up in MyClass.member_names beforemethod1
andmethod2
, apparently contradicting the comments which claimmethod1
is the first element. Why does this special attribute end up in the list while no others do? Are there any others that might surprise me (apart from__doc__
if the class has a docstring, and any I define explicitly)?
Probably because the __module__
special member for classes was thought off after they thought about the __prepare__
method for metaclasses. (I was unable to verify by googling for the PEP defining __module__
but I'd bet this is the case.)
And no, there are no guarantees that future versions of Python won't add some more magic attributes to classes that could come before your explicit class members in the dictionary.
But with metaclasses, you are fully in control - you can just tweak your dict like object
(member_table
in your code) to not count any attributes starting with "__" - for example. Or even not to add then to the final class instance at all (at risk of your classes defined this way not working with certain Python features).
Finally, this implementation does not retrieve the member_names from base classes. If I want to achieve that, is there anything wrong with the following change to
__prepare__
?
By reading it, I see nothing wrong with your proposed implementation, but you'd have to test it, of course.
Update (2013-06-30): this subject is being actively discussed in the Python developer list, and itlooks like from Python 3.4 all classes will be ordered by default, with no need to use a metaclass or __prepare__
only for ordering
Upvotes: 5