Ниже трудный путь. Вот простой способ. Не знаю, почему это не пришло мне в голову раньше.
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]
Вот начало
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
Это должно быть довольно надежно. По сути, он работает, получая атрибуты, которые находятся в подклассе по умолчанию object
, чтобы игнорировать. Затем он получает mro класса, переданного ему, и пересекает его в обратном порядке, чтобы ключи подкласса могли перезаписывать ключи суперкласса. Возвращает словарь пар ключ-значение. Если вам нужен список ключей, значений, как в inspect.getmembers
, просто верните либо attrs.items()
, либо list(attrs.items())
в Python 3.
Если вы на самом деле не хотите проходить через mro и просто хотите, чтобы атрибуты были определены непосредственно в подклассе, тогда это проще:
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