Как читать атрибуты класса в том же порядке, что и объявлено?
Я пишу метакласс, который читает атрибуты класса и сохраняет их в списке, но я хочу, чтобы список (cls.columns) соблюдал порядок объявления (то есть: mycol2
, mycol3
, zut
, cool
, menfin
, a
в моем примере):
import inspect
import pprint
class Column(object):
pass
class ListingMeta(type):
def __new__(meta, classname, bases, classDict):
cls = type.__new__(meta, classname, bases, classDict)
cls.columns = inspect.getmembers(cls, lambda o: isinstance(o, Column))
cls.nb_columns = len(cls.columns)
return cls
class Listing(object):
__metaclass__ = ListingMeta
mycol2 = Column()
mycol3 = Column()
zut = Column()
cool = Column()
menfin = Column()
a = Column()
pprint.pprint(Listing.columns)
Результат:
[('a', <__main__.Column object at 0xb7449d2c>),
('cool', <__main__.Column object at 0xb7449aac>),
('menfin', <__main__.Column object at 0xb7449a8c>),
('mycol2', <__main__.Column object at 0xb73a3b4c>),
('mycol3', <__main__.Column object at 0xb744914c>),
('zut', <__main__.Column object at 0xb74490cc>)]
Это не соответствует порядку объявления атрибутов Column()
для класса Listing
. Если я использую classDict
напрямую, это тоже не помогает.
Как я могу продолжить?
Ответы
Ответ 1
В текущей версии Python порядок классов сохраняется. Смотрите PEP520 для деталей.
В более старых версиях языка (3.5 и ниже, но не в 2.x) вы можете предоставить метакласс, который использует OrderedDict
для пространства имен класса.
import collections
class OrderedClassMembers(type):
@classmethod
def __prepare__(self, name, bases):
return collections.OrderedDict()
def __new__(self, name, bases, classdict):
classdict['__ordered__'] = [key for key in classdict.keys()
if key not in ('__module__', '__qualname__')]
return type.__new__(self, name, bases, classdict)
class Something(metaclass=OrderedClassMembers):
A_CONSTANT = 1
def first(self):
...
def second(self):
...
print(Something.__ordered__)
# ['A_CONSTANT', 'first', 'second']
Однако этот подход не поможет вам с существующими классами, где вам нужно использовать самоанализ.
Ответ 2
Вот обходной путь, который я разработал:
import inspect
class Column(object):
creation_counter = 0
def __init__(self):
self.creation_order = Column.creation_counter
Column.creation_counter+=1
class ListingMeta(type):
def __new__(meta, classname, bases, classDict):
cls = type.__new__(meta, classname, bases, classDict)
cls.columns = sorted(inspect.getmembers(cls,lambda o:isinstance(o,Column)),key=lambda i:i[1].creation_order)
cls.nb_columns = len(cls.columns)
return cls
class Listing(object):
__metaclass__ = ListingMeta
mycol2 = Column()
mycol3 = Column()
zut = Column()
cool = Column()
menfin = Column()
a = Column()
for colname,col in Listing.columns:
print colname,'=>',col.creation_order
Ответ 3
Для python 3.6 это стало поведением по умолчанию. См. PEP520: https://www.python.org/dev/peps/pep-0520/
class OrderPreserved:
a = 1
b = 2
def meth(self): pass
print(list(OrderPreserved.__dict__.keys()))
# ['__module__', 'a', 'b', 'meth', '__dict__', '__weakref__', '__doc__']
Ответ 4
Если вы используете Python 2.x, вам понадобится взломать, например, тот, который предлагает Леннарт. Если вы используете Python 3.x, тогда прочитайте PEP 3115, так как в нем содержится пример, который делает то, что вы хотите. Просто измените пример, чтобы посмотреть только на экземпляры Column():
# 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
Ответ 5
1) Поскольку атрибуты Python 3.6 в определении класса имеют тот же порядок, в котором имена появляются в источнике. Этот порядок теперь сохраняется в новом атрибуте classs __dict__
(https://docs.python.org/3.6/whatsnew/3.6.html#whatsnew36-pep520):
class Column:
pass
class MyClass:
mycol2 = Column()
mycol3 = Column()
zut = Column()
cool = Column()
menfin = Column()
a = Column()
print(MyClass.__dict__.keys())
Вы увидите такой вывод (MyClass.__dict__
может использоваться как OrderedDict):
dict_keys(['__module__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a', '__dict__', '__weakref__', '__doc__'])
Обратите внимание на дополнительные поля __xxx__
, добавленные python, возможно, вам придется их игнорировать.
2) Для предыдущих версий Python 3.x вы можете использовать решение, основанное на ответе @Duncan, но более простое.
мы используем тот факт, что метод __prepare__
возвращает OrderDict
вместо простого dict
- поэтому все атрибуты, собранные до вызова __new__
, будут упорядочены.
from collections import OrderedDict
class OrderedClass(type):
@classmethod
def __prepare__(mcs, name, bases):
return OrderedDict()
def __new__(cls, name, bases, classdict):
result = type.__new__(cls, name, bases, dict(classdict))
result.__fields__ = list(classdict.keys())
return result
class Column:
pass
class MyClass(metaclass=OrderedClass):
mycol2 = Column()
mycol3 = Column()
zut = Column()
cool = Column()
menfin = Column()
a = Column()
Теперь вы можете использовать атрибут __fields__
для доступа к атрибутам в требуемом порядке:
m = MyClass()
print(m.__fields__)
['__module__', '__qualname__', 'mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a']
Обратите внимание, что в классе type
появятся атрибуты '__module__'
, '__qualname__'
. Чтобы избавиться от них, вы можете фильтровать имена следующим образом (изменить OrderedClass.__new__
):
def __new__(cls, name, bases, classdict):
result = type.__new__(cls, name, bases, dict(classdict))
exclude = set(dir(type))
result.__fields__ = list(f for f in classdict.keys() if f not in exclude)
return result
он выдаст только атрибуты из MyClass:
['mycol2', 'mycol3', 'zut', 'cool', 'menfin', 'a']
3) этот ответ доступен только в python3.x, потому что в python2.7 нет определения __prepare__
Ответ 6
Ответ, исключающий методы:
from collections import OrderedDict
from types import FunctionType
class StaticOrderHelper(type):
# Requires python3.
def __prepare__(name, bases, **kwargs):
return OrderedDict()
def __new__(mcls, name, bases, namespace, **kwargs):
namespace['_field_order'] = [
k
for k, v in namespace.items()
if not k.startswith('__') and not k.endswith('__')
and not isinstance(v, (FunctionType, classmethod, staticmethod))
]
return type.__new__(mcls, name, bases, namespace, **kwargs)
class Person(metaclass=StaticOrderHelper):
first_name = 'First Name'
last_name = 'Last Name'
phone_number = '000-000'
@classmethod
def classmethods_not_included(self):
pass
@staticmethod
def staticmethods_not_included(self):
pass
def methods_not_included(self):
pass
print(Person._field_order)
Ответ 7
Я думаю, вы должны иметь возможность создать класс, в котором вы замените его __dict__
на ordered-dict