Примечание. Этот вопрос носит исключительно информационный характер. Мне интересно посмотреть, как глубоко во внутренностях Python можно пойти с этим.
Не так давно внутри определенного вопроса началось обсуждение того, можно ли изменить строки, переданные в операторы print, после / во время вызова print
. Например, рассмотрим функцию:
def print_something():
print('This cat was scared.')
Теперь, когда print
выполняется, то вывод на терминал должен отображать:
This dog was scared.
Обратите внимание, слово «кошка» было заменено словом «собака». Что-то где-то каким-то образом смогло изменить эти внутренние буферы, чтобы изменить то, что было напечатано. Предположим, что это делается без явного разрешения автора исходного кода (следовательно, взлома / угона).
Этот комментарий от мудрого @abarnert, в частности, заставил меня задуматься:
Есть несколько способов сделать это, но все они очень уродливы, и никогда не должны быть сделаны. Наименее уродливый способ - это заменить
code
объект внутри функции на другой с другимco_consts
списком. Далее, вероятно, доступ к C API для доступа к внутреннему буферу str. [...]
Так что, похоже, это действительно возможно.
Вот мой наивный способ решения этой проблемы:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
Конечно, exec
это плохо, но это на самом деле не отвечает на вопрос, потому что на самом деле ничего не изменяет во время вызова / после print
вызова.
Как бы это было сделано, как объяснил @abarnert?
42
на,23
чем плохая идея изменить значение"My name is Y"
на"My name is X"
.Ответы:
Во-первых, на самом деле есть гораздо менее хакерский способ. Все, что мы хотим сделать, это изменить то
print
, что печатает, верно?Или, аналогично, вы можете
sys.stdout
вместо monkeypatchprint
.Кроме того, нет ничего плохого в
exec … getsource …
идее. Ну, конечно, с этим много чего не так, но здесь меньше того, что следует ...Но если вы хотите изменить константы кода объекта функции, мы можем это сделать.
Если вы действительно хотите поэкспериментировать с объектами кода по-настоящему, вы должны использовать библиотеку, например
bytecode
(когда она закончится) илиbyteplay
(до тех пор, или для более старых версий Python), вместо того, чтобы делать это вручную. Даже для чего-то такого тривиальногоCodeType
инициализатор - это боль; если вам действительно нужно что-то вроде ремонтаlnotab
, то только сумасшедший сделает это вручную.Кроме того, само собой разумеется, что не все реализации Python используют объекты кода в стиле CPython. Этот код будет работать в CPython 3.7, и, вероятно, все версии вернутся по крайней мере до 2.2 с некоторыми незначительными изменениями (и не с хакерскими кодами, а с такими вещами, как выражения генератора), но он не будет работать с любой версией IronPython.
Что может пойти не так со взломом объектов кода? В основном это просто ошибки segfaults,
RuntimeError
которые поглощают весь стек, более обычныеRuntimeError
s, которые можно обработать, или значения мусора, которые, вероятно, просто вызовут aTypeError
илиAttributeError
когда вы попытаетесь их использовать. Например, попробуйте создать объект кода, вRETURN_VALUE
котором ничего не стоит в стеке (байт-кодb'S\0'
для 3.6+,b'S'
ранее), или с пустым кортежем,co_consts
когдаLOAD_CONST 0
в байт-коде есть a , или сvarnames
уменьшенным на 1, так что наивысшийLOAD_FAST
фактически загружает freevar. / Cellvar Cell. Для некоторого реального удовольствия, если вы пойметеlnotab
неправильно, ваш код будет только segfault при запуске в отладчике.Использование
bytecode
илиbyteplay
не защитит вас от всех этих проблем, но у них есть некоторые базовые проверки работоспособности и хорошие помощники, которые позволяют вам делать такие вещи, как вставка фрагмента кода, и позволяют ему беспокоиться об обновлении всех смещений и меток, чтобы вы могли ' неправильно, и так далее. (Кроме того, вам не нужно вводить этот смешной 6-строчный конструктор и отлаживать глупые опечатки, возникающие при этом.)Теперь перейдем к # 2.
Я упоминал, что объекты кода неизменны. И, конечно же, константы - это кортеж, поэтому мы не можем изменить это напрямую. И вещь в кортеже const - это строка, которую мы также не можем изменить напрямую. Вот почему я должен был создать новую строку, чтобы создать новый кортеж для создания нового объекта кода.
Но что, если бы вы могли изменить строку напрямую?
Ну, достаточно глубоко под прикрытием, все это просто указатель на некоторые данные C, верно? Если вы используете CPython, есть API C для доступа к объектам , и вы можете использовать его
ctypes
для доступа к этому API из самого Python, что является настолько ужасной идеей, что они помещают ееpythonapi
прямо вctypes
модуль stdlib . :) Самый важный трюк, который вам нужно знать,id(x)
это фактический указательx
в памяти (какint
).К сожалению, C API для строк не позволит нам безопасно попасть во внутреннее хранилище уже замороженной строки. Так что винт, давайте просто прочитаем заголовочные файлы и сами найдем это хранилище.
Если вы используете CPython 3.4 - 3.7 (он отличается от старых версий и знает, что будет в будущем), строковый литерал из модуля, который состоит из чистого ASCII, будет храниться в компактном формате ASCII, что означает структуру заканчивается рано, и буфер байтов ASCII немедленно следует в памяти. Это сломается (как, вероятно, в segfault), если вы поместите не-ASCII-символ в строку или некоторые виды не-литеральных строк, но вы можете прочитать другие 4 способа доступа к буферу для различных типов строк.
Чтобы упростить задачу, я использую
superhackyinternals
проект с моего GitHub. (Это намеренно не устанавливается в pip, потому что вы действительно не должны использовать это, кроме как для экспериментов с вашей локальной сборкой интерпретатора и т.п.).Если вы хотите поиграть с этим,
int
намного проще под покровом, чемstr
. И гораздо проще угадать, что можно сломать, изменив значение2
на1
, верно? На самом деле, забудьте о воображении, давайте просто сделаем это (используя типыsuperhackyinternals
снова):... притворимся, что кодовое поле имеет полосу прокрутки бесконечной длины.
Я попробовал то же самое в IPython, и в первый раз, когда я попытался оценить
2
в приглашении, он вошел в какой-то непрерывный бесконечный цикл. Предположительно он использует число2
для чего-то в цикле REPL, в то время как стандартный интерпретатор - нет?источник
PyUnicodeObject
другой стороны,NameError: name 'arg' is not defined
. Возможно , вы имели в виду:args = [arg.replace('cat', 'dog') if isinstance(arg, str) else arg for arg in args]
? , Возможно , лучший способ , чтобы написать это было бы:args = [str(arg).replace('cat', 'dog') for arg in args]
. Другой, еще короче, вариант:args = map(lambda a: str(a).replace('cat', 'dog'), args)
. Это имеет дополнительное преимущество, котороеargs
является ленивым (что также может быть достигнуто путем замены приведенного выше понимания списка генератором -*args
работает в любом случае).PyUnicodeObject
определение структуры, но копирование этого в ответ, я думаю, только мешает , и я думаю, что комментарии readme и / или источника наsuperhackyinternals
самом деле объясняют, как получить доступ к буферу (по крайней мере, достаточно хорошо, чтобы напомнить мне в следующий раз, когда я забочусь; не уверен, будет ли этого достаточно для кого-то еще…), в который я не хотел входить. Соответствующая часть - как добраться от живого объекта Python до егоPyObject *
черезctypes
. (И, возможно, имитирует указатель арифметики, избегая автоматическогоchar_p
преобразований и т. Д.)print
с именем. Вы также можете связать имяprint
для них:import yourmodule; yourmodule.print = badprint
.Обезьяна-патч
print
print
является встроенной функцией, поэтому она будет использоватьprint
функцию, определенную вbuiltins
модуле (или__builtin__
в Python 2). Поэтому, когда вы хотите изменить или изменить поведение встроенной функции, вы можете просто переназначить имя в этом модуле.Этот процесс называется
monkey-patching
.После этого каждый
print
вызов будет проходитьcustom_print
, даже если онprint
находится во внешнем модуле.Однако вы не хотите печатать дополнительный текст, вы хотите изменить напечатанный текст. Один из способов сделать это - заменить его в строке, которая будет напечатана:
И действительно, если вы запустите
Или, если вы пишете это в файл:
test_file.py
и импортировать его:
Так что это действительно работает как задумано.
Однако в случае, если вы хотите временно только распечатать патч обезьяны, вы можете поместить это в контекстный менеджер:
Поэтому, когда вы запускаете, это зависит от контекста, который печатается:
Так вот, как вы могли бы «взломать»
print
путем исправления обезьян.Изменить цель вместо
print
Если вы посмотрите на подпись
print
вы заметитеfile
аргумент, которыйsys.stdout
по умолчанию. Обратите внимание, что это динамический аргумент по умолчанию (он действительно отображается приsys.stdout
каждом вызовеprint
), а не обычный аргумент по умолчанию в Python. Таким образом, если вы измените,sys.stdout
print
то на самом деле печатать на другую цель будет еще удобнее, так как Python также предоставляетredirect_stdout
функцию (начиная с Python 3.4, но легко создать эквивалентную функцию для более ранних версий Python).Недостатком является то, что он не будет работать для
print
утверждений, которые не печатаются,sys.stdout
и что создание собственногоstdout
не очень просто.Однако это также работает:
Резюме
@Abarnet уже упоминал о некоторых из этих моментов, но я хотел бы изучить эти варианты более подробно. Особенно о том, как изменить его в разных модулях (используя
builtins
/__builtin__
) и как сделать это изменение только временным (используя контекстные менеджеры).источник
redirect_stdout
, так это хорошо иметь четкий ответ, который приводит к этому.Простой способ захватить весь вывод из
print
функции и затем обработать ее - изменить поток вывода на что-то другое, например, файл.Я буду использовать
PHP
соглашения об именовании ( ob_start , ob_get_contents , ...)Использование:
Будет печатать
источник
Давайте совместим это с самоанализом кадра!
Вы найдете этот трюк в начале каждого приветствия с вызывающей функцией или методом. Это может быть очень полезно для регистрации или отладки; тем более, что он позволяет «перехватывать» операторы печати в стороннем коде.
источник