Подтвердить что ты не робот

Осмотреть атрибуты класса python

Мне нужен способ проверки класса, поэтому я могу безопасно идентифицировать, какие атрибуты являются определяемыми пользователем атрибутами класса. Проблема в том, что такие функции, как dir(), inspect.getmembers() и друзья, возвращают все атрибуты класса, включая предварительно определенные: __class__, __doc__, __dict__, __hash__. Это, конечно, понятно, и можно утверждать, что я мог бы просто перечислить список именованных членов, чтобы игнорировать, но, к сожалению, эти заранее определенные атрибуты могут меняться с разными версиями Python, что делает мой проект уязвимым для изменения в проекте python - и мне это не нравится.

Пример:

>>> 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']

В приведенном выше примере я хочу безопасный способ получить только определенные пользователем атрибуты класса ['a', 'b'] не 'c', поскольку это атрибут экземпляра. Поэтому мой вопрос: может ли кто-нибудь помочь мне с вышеупомянутой фиктивной функцией get_user_attributes(cls)?

P.S. Я потратил некоторое время, пытаясь решить проблему, разобрав класс на уровне АСТ, что было бы очень легко. Но я не могу найти способ преобразовать уже обработанные объекты в дерево AST node. Я думаю, вся информация AST отбрасывается, как только класс был скомпилирован в байт-код.

С наилучшими пожеланиями, Якоб

4b9b3361

Ответ 1

Ниже приведен трудный путь. Вот простой способ. Не знаю, почему это не произошло раньше.

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

Ответ 2

Двойные подчеркивания на обоих концах "специальных атрибутов" были частью python до 2.0. Было бы маловероятно, чтобы они изменили это в любое время в ближайшем будущем.

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']

Ответ 3

Спасибо aaronasterling, вы дали мне выражение, которое мне нужно:-) Моя последняя функция проверки атрибута класса выглядит следующим образом:

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

Вернуть только атрибуты атрибута класса атрибута (exclude_methods = True) или также получить методы. Мои начальные тесты этой функции поддерживают как старые, так и новые классы python.

/Якоб

Ответ 4

Если вы используете новые классы стиля, можете ли вы просто вычесть атрибуты родительского класса?

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))]

Изменить: Не совсем. __dict__, __module__ и __weakref__ появляются при наследовании от объекта, но не находятся в самом объекте. Это может быть особый случай - я сомневаюсь, что они будут меняться очень часто.

Ответ 5

Прошу прощения за некро-натыкаясь нить. Я удивлен, что до сих пор нет простой функции (или библиотеки) для обработки такого распространенного использования с 2019 года.

Я хотел бы поблагодарить aaronasterling за эту идею. На самом деле, set container обеспечивает более простой способ выразить это:

class dummy:    pass

def abridged_set_of_user_attributes(obj):
    return set(dir(obj))-set(dir(dummy))

def abridged_list_of_user_attributes(obj):
    return list(abridged_set_of_user_attributes(obj))

Исходное решение, использующее понимание списка, на самом деле представляет собой двухуровневые циклы, потому что in ключевом слове есть два составных, несмотря на то, что только один for ключевого слова сделал его похожим на менее трудоемкий, чем он есть.