Обходной путь для специального метода __getattr__, нарушающего inspect.getmembers () в Python 3.x - PullRequest
0 голосов
/ 01 февраля 2019

Функция inspect.getmembers прерывается при использовании с классом, который определяет метод __getattr__ (в Python 3.x):

import inspect

class C:
    def __getattr__(self, name):
        def wrapper():
            print("For MCVE purposes, this does nothing useful.")
        return wrapper

print(inspect.getmembers(C()))

Полученная ошибка выглядит следующим образом:

Traceback (most recent call last):
  File "tmp.py", line 9, in <module>
    print(inspect.getmembers(C()))
  File "/usr/lib/python3.7/inspect.py", line 330, in getmembers
    for base in object.__bases__:
TypeError: 'function' object is not iterable

Кажется, что проблема заключается в том, что inspect.getmodule опирается на более легкий способ просить прощения и предполагает, что атрибут __bases__ либо не существует (что бросает * 1013)* исключение) или содержит базовые классы.

Я мог бы исправить это, явно бросив AttributeError в C.__getattr__;однако я хочу использовать inspect.getmembers с объектами классов, которые я не могу контролировать.У меня такой вопрос: есть ли обходной путь для этой проблемы, который работает без каких-либо изменений класса C или его экземпляров?

Ответы [ 2 ]

0 голосов
/ 01 февраля 2019

Вы запросили обходной путь, который работает без изменения класса C или его экземпляров.Вот решение, которое выполняет эту работу, хотя оно включает в себя переписывание длинной функции getmembers(), что также может быть неудобно.Возможно, это больше подходит для запроса на извлечение из библиотеки.

(исходный код https://github.com/python/cpython/blob/3.7/Lib/inspect.py)

import inspect

def getmembers(object, predicate=None):
    """Return all members of an object as (name, value) pairs sorted by name.
    Optionally, only return members that satisfy a given predicate."""
    # Line below adds inspect. reference to isclass()
    if inspect.isclass(object):
        # Line below adds inspect. reference to getmro()
        mro = (object,) + inspect.getmro(object)
    else:
        mro = ()
    results = []
    processed = set()
    names = dir(object)
    # :dd any DynamicClassAttributes to the list of names if object is a class;
    # this may result in duplicate entries if, for example, a virtual
    # attribute with the same name as a DynamicClassAttribute exists
    try:
        for base in object.__bases__:
            for k, v in base.__dict__.items():
                if isinstance(v, types.DynamicClassAttribute):
                    names.append(k)
    # Line below edited to catch TypeError
    except (AttributeError, TypeError):
        pass
    for key in names:
        # First try to get the value via getattr.  Some descriptors don't
        # like calling their __get__ (see bug #1785), so fall back to
        # looking in the __dict__.
        try:
            value = getattr(object, key)
            # handle the duplicate key
            if key in processed:
                raise AttributeError
        except AttributeError:
            for base in mro:
                if key in base.__dict__:
                    value = base.__dict__[key]
                    break
            else:
                # could be a (currently) missing slot member, or a buggy
                # __dir__; discard and move on
                continue
        if not predicate or predicate(value):
            results.append((key, value))
        processed.add(key)
    results.sort(key=lambda pair: pair[0])
    return results

Теперь, если вы запустите свой новый getmembers():

class C:
    def __getattr__(self, name):
        def wrapper():
            print("For MCVE purposes, this does nothing useful.")
        return wrapper

print(getmembers(C()))

Вы должны получить результат, который вам нужен:

[('__class__', <class '__main__.C'>), ('__delattr__', <method-wrapper '__delattr__' of C object at 0x1155fa7b8>), ('__dict__', {}), ('__dir__', <built-in method __dir__ of C object at 0x1155fa7b8>), ('__doc__', None), ('__eq__', <method-wrapper '__eq__' of C object at 0x1155fa7b8>), ('__format__', <built-in method __format__ of C object at 0x1155fa7b8>), ('__ge__', <method-wrapper '__ge__' of C object at 0x1155fa7b8>), ('__getattr__', <bound method C.__getattr__ of <__main__.C object at 0x1155fa7b8>>), ('__getattribute__', <method-wrapper '__getattribute__' of C object at 0x1155fa7b8>), ('__gt__', <method-wrapper '__gt__' of C object at 0x1155fa7b8>), ('__hash__', <method-wrapper '__hash__' of C object at 0x1155fa7b8>), ('__init__', <method-wrapper '__init__' of C object at 0x1155fa7b8>), ('__init_subclass__', <built-in method __init_subclass__ of type object at 0x7fd01899e4c8>), ('__le__', <method-wrapper '__le__' of C object at 0x1155fa7b8>), ('__lt__', <method-wrapper '__lt__' of C object at 0x1155fa7b8>), ('__module__', '__main__'), ('__ne__', <method-wrapper '__ne__' of C object at 0x1155fa7b8>), ('__new__', <built-in method __new__ of type object at 0x1008b0c48>), ('__reduce__', <built-in method __reduce__ of C object at 0x1155fa7b8>), ('__reduce_ex__', <built-in method __reduce_ex__ of C object at 0x1155fa7b8>), ('__repr__', <method-wrapper '__repr__' of C object at 0x1155fa7b8>), ('__setattr__', <method-wrapper '__setattr__' of C object at 0x1155fa7b8>), ('__sizeof__', <built-in method __sizeof__ of C object at 0x1155fa7b8>), ('__str__', <method-wrapper '__str__' of C object at 0x1155fa7b8>), ('__subclasshook__', <built-in method __subclasshook__ of type object at 0x7fd01899e4c8>), ('__weakref__', None)]
0 голосов
/ 01 февраля 2019

Попробуйте установить c.__bases__ = () перед вызовом inspect.getmembers(c).

Редактировать: более безопасная и более сложная версия в ответ на комментарий:

object.__setattr__(c, '__bases__', ())
try:
    members = inspect.getmembers(c)
finally:
    object.__delattr__(c, '__bases__')

Это не удастся, если:

  1. C определяет __slots__ или не позволяет установить __bases__ по какой-либо другой причине, что вызовет исключение.
  2. c уже имеет фактический атрибут __bases__, которыйне должно быть перезаписаноЭто можно проверить с помощью дополнительного кода.
...