poo - Inspeccionar los atributos de la clase python
python poo tutorial (4)
Debajo está la manera dura. Aquí está la manera fácil. No sé por qué no se me ocurrió antes.
import inspect
def get_user_attributes(cls):
boring = dir(type(''dummy'', (object,), {}))
return [item
for item in inspect.getmembers(cls)
if item[0] not in boring]
Aquí hay un comienzo
def get_user_attributes(cls):
boring = dir(type(''dummy'', (object,), {}))
attrs = {}
bases = reversed(inspect.getmro(cls))
for base in bases:
if hasattr(base, ''__dict__''):
attrs.update(base.__dict__)
elif hasattr(base, ''__slots__''):
if hasattr(base, base.__slots__[0]):
# We''re dealing with a non-string sequence or one char string
for item in base.__slots__:
attrs[item] = getattr(base, item)
else:
# We''re dealing with a single identifier as a string
attrs[base.__slots__] = getattr(base, base.__slots__)
for key in boring:
del attrs[''key''] # we can be sure it will be present so no need to guard this
return attrs
Esto debería ser bastante robusto. Básicamente, funciona al ignorar los atributos que se encuentran en una subclase predeterminada del object
. A continuación, obtiene el mro de la clase que se le pasa y lo cruza en orden inverso para que las claves de subclase puedan sobrescribir las claves de superclase. Devuelve un diccionario de pares clave-valor. Si desea una lista de claves, tuplas de valores como en inspect.getmembers
, simplemente devuelva attrs.items()
o list(attrs.items())
en Python 3.
Si realmente no desea atravesar el mro y solo quiere atributos definidos directamente en la subclase, entonces es más fácil:
def get_user_attributes(cls):
boring = dir(type(''dummy'', (object,), {}))
if hasattr(cls, ''__dict__''):
attrs = cls.__dict__.copy()
elif hasattr(cls, ''__slots__''):
if hasattr(base, base.__slots__[0]):
# We''re dealing with a non-string sequence or one char string
for item in base.__slots__:
attrs[item] = getattr(base, item)
else:
# We''re dealing with a single identifier as a string
attrs[base.__slots__] = getattr(base, base.__slots__)
for key in boring:
del attrs[''key''] # we can be sure it will be present so no need to guard this
return attrs
Necesito una forma de inspeccionar una clase para poder identificar con seguridad qué atributos son atributos de clase definidos por el usuario. El problema es que funciones como dir (), inspect.getmembers () y amigos devuelven todos los atributos de clase, incluidos los predefinidos, como: __class__
, __doc__
, __dict__
, __hash__
. Esto, por supuesto, es comprensible, y se podría argumentar que podría hacer una lista de miembros nombrados para ignorar, pero desafortunadamente estos atributos predefinidos están destinados a cambiar con diferentes versiones de Python, lo que hace que mi proyecto sea modificable en el proyecto python. - y no me gusta eso.
ejemplo:
>>> class A:
... a=10
... b=20
... def __init__(self):
... self.c=30
>>> dir(A)
[''__doc__'', ''__init__'', ''__module__'', ''a'', ''b'']
>>> get_user_attributes(A)
[''a'',''b'']
En el ejemplo anterior, deseo una forma segura de recuperar solo los atributos de clase definidos por el usuario [''a'', ''b''] y no ''c'', ya que es un atributo de instancia. Entonces mi pregunta es ... ¿Alguien puede ayudarme con la función ficticia anterior get_user_attributes(cls)
?
PD. He dedicado un tiempo a tratar de resolver el problema analizando la clase en AST, lo que sería muy fácil. Pero no puedo encontrar una forma de convertir objetos ya analizados a un árbol de nodos AST. Supongo que toda la información AST se descarta una vez que una clase ha sido compilada en bytecode.
Saludos cordiales Jakob
Gracias aaronasterling, me diste la expresión que necesitaba :-) Mi última función de inspector de atributos de clase tiene este aspecto:
def get_user_attributes(cls,exclude_methods=True):
base_attrs = dir(type(''dummy'', (object,), {}))
this_cls_attrs = dir(cls)
res = []
for attr in this_cls_attrs:
if base_attrs.count(attr) or (callable(getattr(cls,attr)) and exclude_methods):
continue
res += [attr]
return res
O bien devuelve variabels de atributo de clase únicamente (exclude_methods = True) o también recupera los métodos. Mis pruebas iniciales de la función anterior son compatibles tanto con las clases python antiguas como con las nuevas.
/ Jakob
Los guiones bajos dobles en ambos extremos de ''atributos especiales'' han sido parte de python antes de 2.0. Sería muy poco probable que cambien eso en cualquier momento en el futuro cercano.
class Foo(object):
a = 1
b = 2
def get_attrs(klass):
return [k for k in klass.__dict__.keys()
if not k.startswith(''__'')
and not k.endswith(''__'')]
print get_attrs(Foo)
[''a'', ''b'']
Si usa nuevas clases de estilo, ¿podría simplemente restar los atributos de la clase padre?
class A(object):
a = 10
b = 20
#...
def get_attrs(Foo):
return [k for k in dir(Foo) if k not in dir(super(Foo))]
Editar: No del todo. __dict__
, __module__
y __weakref__
aparecen cuando __weakref__
de un objeto, pero no están en el mismo objeto. Podrían tratarse casos especiales, dudo que cambien muy a menudo.