Ошибка IPython REPL при использовании пользовательского __getattribute__ - PullRequest
0 голосов
/ 02 февраля 2019

У меня есть пользовательский __getattribute__, который должен изменять возвращаемое значение, если член не является методом (таким образом, атрибутом).Предположим, что все атрибуты (self.a, self.b и т. Д.) str.

class A:
    def __init__(self):
        self.a = 1

    def __getattribute__(self, k):
        attr = object.__getattribute__(self, k)
        if type(attr) != types.MethodType:
            return '{}!'.format(attr)
        return attr

Я получаю сообщение об ошибке в IPython при получении представления экземпляров класса A, но я нене понимаю, почему.
Например:

In [26]: a = A()
In [27]: a
Out[27]: ---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/IPython/core/formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)
    703             printer.flush()
    704             return stream.getvalue()

~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    380             #   1) a registered printer
    381             #   2) a _repr_pretty_ method
--> 382             for cls in _get_mro(obj_class):
    383                 if cls in self.type_pprinters:
    384                     # printer registered in self.type_pprinters

~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in _get_mro(obj_class)
    318         # Old-style class. Mix in object to make a fake new-style class.
    319         try:
--> 320             obj_class = type(obj_class.__name__, (obj_class, object), {})
    321         except TypeError:
    322             # Old-style extension type that does not descend from object.

AttributeError: 'str' object has no attribute '__name__'

Но print(a) отлично работает

In [33]: print(a)
<__main__.A object at 0x10c566390>

Примечание: в простом REPL Python кажется, что работает правильно.

>>> a = A()
>>> a
<__main__.A object at 0x1032b9320>

1 Ответ

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

В IPython стандартный вывод отображает довольно напечатанное __repr__ представление объекта.В то время как в Python стандартный вывод print s __repr__ представление объекта, короче, print(repr(obj)).

Python :

Как вы будетеОбратите внимание, что стандартный вывод в Python аналогичен вызову функции print() для repr(a).repr(a) является объектным представлением a и при вызове вызывает __repr__.

>>> a = A()
>>> a
<__main__.A object at 0x000000D886391438>
>>> repr(a)
'<__main__.A object at 0x000000D886391438>'
>>> print(repr(a))
<__main__.A object at 0x000000D886391438>

IPython :

IPython, с другой стороны, имеетего собственная реализация отображения стандартного вывода и довольно печатает __repr__ для объекта перед отображением.Красивая печать объекта для stdout происходит в функции pretty(), расположенной в классе RepresentationPrinter в .. / IPython / lib / pretty.py :

def pretty(self, obj):
        """Pretty print the given object."""
        obj_id = id(obj)
        cycle = obj_id in self.stack
        self.stack.append(obj_id)
        self.begin_group()
        try:
            obj_class = _safe_getattr(obj, '__class__', None) or type(obj)
        #<---code--->

Однакоперед вызовом pretty() IPython вызывает метод __call__(self,obj) в .. / IPython / core / formatters.py .Вы заметите это как самый верхний стек в ошибке исключения трассировки, и функция pretty() выше вызывается в строке 702:

AttributeError                            Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/IPython/core/formatters.py in __call__(self, obj)
    700                 type_pprinters=self.type_printers,
    701                 deferred_pprinters=self.deferred_printers)
--> 702             printer.pretty(obj)

В функции pretty() над строкой _safe_getattr(obj, '__class__', None) or type(obj) интересно.Определение для этой функции говорит, что это безопасная реализация getarr(), что означает, что если при получении атрибута для этого объекта вызывается исключение, он вернет None:

def _safe_getattr(obj, attr, default=None):
"""Safe version of getattr.

Same as getattr, but will return ``default`` on any Exception,
rather than raising.
"""
    try:
        return getattr(obj, attr, default)
    except Exception:
        return default

В pretty() функция, значение _safe_getattr(obj, '__class__', None) or type(obj) сохраняется в obj_class.Позже в той же функции эта переменная передается в _get_mro().Это показано во втором стеке исключения трассировки в строке 382:

    ~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in pretty(self, obj)
    380             #   1) a registered printer
    381             #   2) a _repr_pretty_ method
--> 382             for cls in _get_mro(obj_class):
    383                 if cls in self.type_pprinters:
    384                     # printer registered in self.type_pprinters

Задача _get_mro(obj_class) - получить MRO (порядок разрешения методов) для obj_class.В Python 3 все классы новый стиль и имеют атрибут __mro__.Однако определения классов старого стиля были сохранены для обратной совместимости и не имеют этого атрибута.Ваш класс определен с синтаксисом старого стиля.Вы можете узнать больше о NewClass v / s OldClass здесь .В определении для _get_mro(obj_class) ваш код попадает в блок try для синтаксиса старого стиля и выдает ошибки.Это самый последний и самый нижний стек в исключении трассировки:

  ~/miniconda3/lib/python3.7/site-packages/IPython/lib/pretty.py in _get_mro(obj_class)
    318         # Old-style class. Mix in object to make a fake new-style class.
    319         try:
--> 320             obj_class = type(obj_class.__name__, (obj_class, object), {})
    321         except TypeError:

Итак, что происходит :

Давайте использовать все, что мы узнали, и понять, чтодействительно происходит за кулисами.Я изменил ваш код ниже, чтобы использовать вышеуказанные функции из модуля IPython.Попробуйте это сделать на консоли IPython / записной книжке Jupyter:

    In [1]: from IPython.lib.pretty import _safe_getattr
       ...: from IPython.lib.pretty import pretty
       ...: from IPython.lib.pretty import _get_mro
       ...:
       ...: class A:
       ...:     def __init__(self):
       ...:         self.a = 1
       ...:
       ...:     def __getattribute__(self, k):
       ...:         attr = object.__getattribute__(self, k)
       ...:         if type(attr) != types.MethodType:
       ...:             return '{}!'.format(attr)
       ...:         return attr
       ...:
       ...: a = A()
       ...: a.test_attr = 'test_string'
    In [2]: getattr_res = _safe_getattr(a, 'test_attr') or type(a)
    In [6]: getattr_res
    Out[6]: 'test_string!'
    In [10]: getattr_res == getattr(a, 'test_attr')
    Out[10]: True

Я определил атрибут test_attr, в котором хранится строка 'test_string', поскольку вы упомянули, что все атрибуты str.Переменная getattr_res хранит значение для вызова _safe_getattr(a, 'test_attr'), которое совпадает с вызовом getattr(a, 'test_attr'), который в основном вызывает __getattribute__ в вашем коде:

In [13]: a.__getattribute__('test_attr')
Out[13]: 'test_string!'

Как вы заметите, getattr_res имеет типстрока и строковые объекты не имеют атрибута __mro__.У нас должен быть объект класса, чтобы получить MRO:

In [14]: type(getattr_res)
Out[14]: str
In [15]: _get_mro(getattr_res)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-d0ae02b5a6ac> in <module>()
----> 1 _get_mro(getattr_res)

C:\ProgramData\Anaconda3\lib\site-packages\IPython\lib\pretty.py in _get_mro(obj_class)
    316         # Old-style class. Mix in object to make a fake new-style class.
    317         try:
--> 318             obj_class = type(obj_class.__name__, (obj_class, object), {})
    319         except TypeError:
    320             # Old-style extension type that does not descend from object.

AttributeError: 'str' object has no attribute '__name__'

Это исключение выглядит знакомым, не так ли?Вызов функции _safe_getattr(obj, '__class__', None) IPython вызывает __getattribute__ в вашем коде, который возвращает строковый объект, который не имеет атрибута __mro__, и даже если _get_mro(obj_class) пытается выполнить в блоке try, мы получаем AttributeError, потому чтомы знаем, что str объекты не имеют атрибута '__name__':

In [16]: getattr_res.__name__
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-16-0d8ba2c5af23> in <module>()
----> 1 getattr_res.__name__

AttributeError: 'str' object has no attribute '__name__'

Как мы можем это исправить :

В IPython возможнодобавьте наши собственные красивые правила печати для объектов в нашем классе.Вдохновленный документами для модуля lib.pretty , я изменил код и определил функцию _repr_pretty_(self, p, cycle), которая явно вызывается в __getattribute__ (после проверки типа) для отображения объекта в желаемомформат.Если атрибут является строкой, он просто возвращает строку снова:

In [20]: class A:
    ...:     def __init__(self):
    ...:         self.a = 1
    ...:
    ...:     def __getattribute__(self, k):
    ...:         attr = object.__getattribute__(self, k)
    ...:         if type(attr) != types.MethodType:
    ...:             return self._repr_pretty_(attr, cycle=False)
    ...:         return attr
    ...:
    ...:     def _repr_pretty_(self, p, cycle):
    ...:         if cycle:
    ...:             p.text('MyList(...)')
    ...:         else:
    ...:             if isinstance(p,str):
    ...:                 return p
    ...:             return p.text(repr(self) + '!')

In [21]: a = A()
In [22]: a
Out[22]: <__main__.A object at 0x0000005E6C6C00B8>!
In [24]: a.test = 'test_string'
In [25]: a.test
Out[25]: 'test_string'

Обратите внимание, что cycle=False при вызове _repr_pretty_() в __getattribute__(self, k), поскольку attr не является итеративным.

Как правило, рекомендуется добавить функцию __repr__ в ваш класс, поскольку она четко показывает представление объектов в вашем классе.Подробнее об этом можно прочитать здесь .

Заключение : стандартный вывод IPython реализует свой собственный довольно печатный __repr__ в отличие от интерпретатора Python, который использует встроенную функцию repr() для stdout.Чтобы изменить поведение стандартного вывода на IPython, можно добавить функцию _repr_pretty_() в их класс для отображения вывода по желанию.

...