Какой метод Python 2 использует для печати кортежей?

Оператор Python print обычно печатает repr() своего ввода. Кортежи не являются исключением:

>>> print (1, 2, 3)
(1, 2, 3)
>>> print repr((1, 2, 3))
(1, 2, 3)

Но затем я наткнулся на какое-то странное поведение, когда возился с внутренними компонентами CPython. Вкратце: если вы обманом заставите Python 2 создать самоссылающийся кортеж, его прямая печать будет вести себя совершенно иначе, чем печать его представлений repr()/str()/unicode().

>>> print outer   # refer to the link above
((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((
... many lines later ...
((((((((((Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError: stack overflow
>>> print repr(outer)
((...),)
>>> print str(outer)
((...),)
>>> print unicode(outer)
((...),)

Так что именно делает print? Пытаясь ответить на этот вопрос самостоятельно, я сослался на справочник по языку:

6.6. Заявление print

print вычисляет каждое выражение по очереди и записывает полученный объект в стандартный вывод (см. ниже). Если объект не является строкой, он сначала преобразуется в строку с использованием правил преобразования строк.

И правила преобразования строк:

5.2.9. Преобразование строк

Преобразование строки представляет собой список выражений, заключенный в обратные (также известные как обратные) кавычки:

string_conversion ::=  "`" expression_list "`"

Но заключение outer в обратные кавычки дает тот же результат, что и вызов repr() и друзей. Нет игральных костей. Так что, черт возьми, print на самом деле делает за кулисами?

(Интересно, что это поведение исправлено в Python 3: печать кортежа, ссылающегося на себя, дает форму, усеченную многоточием.)


person ashastral    schedule 21.12.2013    source источник
comment
Я получил struct.error: 'I' format requires 0 <= number <= 4294967295, когда попробовал ваш код.   -  person thefourtheye    schedule 21.12.2013
comment
Вероятно, вы используете 64-битную сборку Python. Замена обоих экземпляров «I» на «Q» теоретически должна это исправить.   -  person ashastral    schedule 21.12.2013
comment
Теперь он терпит неудачу в c_outer[inner_index:inner_index+4] = struct.pack('Q', id(outer)) с ValueError: Can only assign sequence of same size   -  person thefourtheye    schedule 21.12.2013
comment
Попробуйте заменить +4 на +8. EDIT: я обновил связанный Gist, и теперь он должен работать как на 32-разрядных, так и на 64-разрядных платформах.   -  person ashastral    schedule 21.12.2013
comment
+1 произошел сбой из-за ошибки сегментации после печати огромного куска (.   -  person thefourtheye    schedule 21.12.2013
comment
Скорее всего это ошибка. Почему бы вам не сообщить об этом?   -  person thefourtheye    schedule 21.12.2013
comment
Я сомневаюсь, что это достаточно важно, чтобы гарантировать исправление, учитывая, что единственный способ воспроизвести это — возиться с указателями. Меня больше интересует, почему, по-видимому, существует два варианта поведения строковых кортежей, один из которых ломается в очень неясном угловом случае.   -  person ashastral    schedule 21.12.2013
comment
Об этом уже сообщили и отклонили как not to be fixed в Python 2. http://bugs.python.org/issue1069092   -  person Ned Deily    schedule 21.12.2013
comment
Этот отчет об ошибке имеет дело со многими кортежами, глубоко вложенными друг в друга, а не с одним кортежем, вложенным внутрь себя. Переполнение стека в моем примере на самом деле просто побочный эффект фактической ошибки.   -  person ashastral    schedule 21.12.2013
comment
Извините, я должен был посмотреть поближе. Трек стека показывает, что переполнение стека возникает в результате рекурсивного цикла вызова между internal_print (около object.c:315 и tupleprint (около tupleobject.c:253). Проблема с рекурсивными представлениями контейнера была исправлена ​​в Python 3.2: bugs.python.org/issue9840   -  person Ned Deily    schedule 21.12.2013


Ответы (1)


Вы можете узнать, что на самом деле происходит, разобрав байт-код Python.

>>> from dis import dis
>>> dis(compile('print outer', '<string>', 'exec'))
  1           0 LOAD_NAME                0 (outer)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE

И чтение источника для базовых кодов операций.

PRINT_ITEM в итоге достигает этого блока кода:

else if (Py_TYPE(op)->tp_print == NULL) {
    PyObject *s;
    if (flags & Py_PRINT_RAW)
        s = PyObject_Str(op);
    else
        s = PyObject_Repr(op);
    ...
}
else
    ret = (*Py_TYPE(op)->tp_print)(op, fp, flags);

Это означает, что __str__ или __repr__ будут вызываться только в том случае, если тип объекта не имеет функции tp_print. А у tupleobject есть один.

Если вы хотите понять внутренности CPython, лучше всего прочитать исходный код. Я рекомендую серию руководств по внутреннему устройству Python, в которых объясняется все, что вы должны знать, чтобы полностью понять вывод функции python dis.

person Blin    schedule 21.12.2013