Проверьте атрибуты класса Python - PullRequest
29 голосов
/ 22 ноября 2010

Мне нужен способ проверки класса, чтобы я мог безопасно определить, какие атрибуты являются пользовательскими атрибутами класса.Проблема в том, что такие функции, как dir (), inspect.getmembers () и friends, возвращают все атрибуты класса, включая предопределенные, такие как: __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)?

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

С наилучшими пожеланиями, Jakob

Ответы [ 5 ]

29 голосов
/ 22 ноября 2010

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

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
6 голосов
/ 22 ноября 2010

Двойные подчеркивания на обоих концах «специальных атрибутов» были частью 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 голосов
/ 22 ноября 2010

Спасибо 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 как старого, так и нового стиля.

/ Якоб

2 голосов
/ 22 ноября 2010

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

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__ появляются при наследовании от объекта, но отсутствуют в самом объекте. Это может быть особый случай - я сомневаюсь, что они будут меняться очень часто.

1 голос
/ 28 апреля 2019

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

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

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 сделало его похожим на менее трудоемкий.

...