poo multiple importar herencia ejemplos clases python class metaclass

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)