python - multiple - ¿Cómo leer los atributos de clase en el mismo orden que declarado?
importar clases en python (7)
Estoy escribiendo una metaclase que lee los atributos de clase y los almacena en una lista, pero quiero que la lista (cls.columns) respete el orden de las declaraciones (es decir: mycol2
, mycol3
, zut
, cool
, menfin
, a
en mi ejemplo) :
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)
Resultado:
[(''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>)]
Esto no respeta el orden de declaración de los atributos de Column()
para la clase de Listing
. Si uso classDict
directamente, tampoco sirve de nada.
¿Cómo puedo proceder?
Aquí está la solución que desarrollé:
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
Basado en la solución de @Duncan, pero más simple (solo para python3 ). Usamos ese hecho, ese método __prepare__
devuelve un OrderDict
lugar de un dict
simple, por lo que todos los atributos recopilados antes de la llamada __new__
serán ordenados.
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()
Ahora puede usar el atributo __fields__
para acceder a los atributos en el orden requerido:
m = MyClass()
print(m.__fields__)
[''__module__'', ''__qualname__'', ''mycol2'', ''mycol3'', ''zut'', ''cool'', ''menfin'', ''a'']
Tenga en cuenta que habrá attrs ''__module__''
, ''__qualname__''
nacidos de la clase de type
. Para deshacerse de ellos, puede filtrar los nombres de la siguiente manera (cambiar 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
solo dará atributos de MyClass:
[''mycol2'', ''mycol3'', ''zut'', ''cool'', ''menfin'', ''a'']
por favor, recuerde que su respuesta solo es factible en python3.x, porque no hay definición de preparación en python2.7
En la versión actual de Python, el orden de clases se conserva. Ver PEP520 para más detalles.
En versiones anteriores del lenguaje (3.5 y siguientes, pero no 2.x), puede proporcionar una metaclase que use un OrderedDict
para el espacio de nombres de la clase.
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'']
Sin embargo, este enfoque no te ayuda con las clases existentes, donde necesitarás usar la introspección.
Para Python 3.6, este se ha convertido en el comportamiento predeterminado. Ver PEP520: PEP520
class OrderPreserved:
a = 1
b = 2
def meth(self): pass
print(list(OrderPreserved.__dict__.keys()))
# [''__module__'', ''a'', ''b'', ''meth'', ''__dict__'', ''__weakref__'', ''__doc__'']
Si está utilizando Python 2.x, necesitará un truco como el que propone Lennart. Si está utilizando Python 3.x, lea PEP 3115 ya que contiene un ejemplo que hace lo que desea. Simplemente modifique el ejemplo para que solo observe sus instancias de Columna ():
# 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
Supongo que deberías poder hacer una clase en la que reemplaces su __dict__
con un ordered-dict
Una respuesta que excluye métodos:
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)