Можно ли изменить возвращаемое значение функции с помощью pdb?

Допустим, у меня есть следующий код:

def returns_false():
  breakpoint()
  return False

assert(returns_false())
print("Hello world")

Существует ли последовательность команд pdb, которая будет печатать «Hello world» без предварительного запуска AssertionError?

Я не могу изменить ни одного символа этого исходного файла, я только ищу, чего я могу достичь, пока он уже запущен.

Что я пробовал:

  • return True
  • s, чтобы вернуться в режим возврата, а затем либо retval=True, либо locals()['__return__']=True
  • interact, а затем return True, но это вызывает исключение

Но ничто из этого не меняет фактическое возвращаемое значение.

Вы пробовали это? stackoverflow.com/questions/27741387/…

Vardan Grigoryants 03.07.2024 22:16

Да, но когда вы находитесь в функции, вне функции нет строки, к которой можно было бы перейти, и при выходе из функции, кажется, что утверждение оценивается до того, как я смогу перейти. Так что в основном s s s j 6 не работает :-(

C4stor 04.07.2024 12:01

Идея состоит в том, чтобы перепрыгнуть через функцию, вам не нужно заходить внутрь нее.

Vardan Grigoryants 04.07.2024 12:43

Однако точка останова() находится внутри функции, поэтому именно здесь она и прервется.

C4stor 04.07.2024 12:53

Что ж, тогда вы пытались опустить такое утверждение, например: python -Om pdb <Your_Module_Name>.py

Vardan Grigoryants 04.07.2024 13:47

Я прочитал это. Я довольно много играл с sys.settrace(), и мой опыт показывает, что если переназначить значение args при событиях возврата, это действительно повлияет на новое значение переменной, но это значение не отразится на том, что фактически вернулся к следующему вызову. Конечно, возможно, я неправильно понял, поэтому задаю вопросы по SO ^^, но я думаю, что на данный момент вы можете поверить, что я серьезно изучил этот вопрос и проявил должную осмотрительность в меру своих возможностей. возможности (которые они собой представляют :shrugh: )

C4stor 09.07.2024 15:59

Чего я не понимаю, так это того, что ты выглядишь особенно недовольным. Я принял ответ на этот вопрос, это для тебя проблема?

C4stor 09.07.2024 16:00
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
7
324
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Для решения вашей проблемы существует как минимум 6 различных методов, каждый из которых имеет свои плюсы и минусы. Однако все эти методы требуют некоторых изменений в коде и не используют pdb. Существует метод с использованием pdb, который позволяет минимизировать изменения в коде. Поскольку метод pdb можно применять только в определенных ситуациях и он менее питоничен, я поставлю его последним.

Самое очевидное и простое решение — изменить порядок расположения assert и print, например:

def returns_false():
  breakpoint()
  return False

print("Hello world")
assert(returns_false())

Еще один простой способ:

def returns_false():
  breakpoint()
  return True

assert(returns_false())
print("Hello world")

Однако, если вы действительно не хотите менять код, рассмотрите возможность запуска файла Python в командной строке следующим образом:

python -O test.py

Однако это отключит все assert в файле Python. Если вы все еще этого не хотите, вы можете попробовать еще три варианта:

  1. Используйте блок try/except, чтобы поймать AssertionError, например:
def returns_false():
  breakpoint()
  return False

try:
    assert(returns_false())
except AssertionError:
    print('Assertion Error caught')
print("Hello world")

Это будет подавлять только это assert, а не других.

  1. Используйте контекстный менеджер, действующий как try/except:
class AssertSuppressor():
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        return True

with AssertSuppressor():
    assert (returns_false())
print("Hello world")

Это подавит assert так же, как try/except. Проблема этого подхода в том, что print должен располагаться за пределами блока with. Более причудливый подход решит эту проблему:

class AssertSuppressorFancy():
    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs
    def __enter__(self):
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.func(*self.args, **self.kwargs)
        # return True here if you still want to suppress the assert, but I don't think it is necessary

with AssertSuppressorFancy(print, 'Hello World'):
    assert(returns_false())

Приведенный выше контекстный менеджер принимает вызываемый аргумент func и аргументы (как ключевые слова, так и позиционные) для передачи вызываемому объекту. Он вызывает func с *args и **kwargs, когда блок with завершен, независимо от того, есть исключение или нет.

  1. Наконец, реализация оболочки также является хорошей идеей:
def wrapper(func):
    def inner(*args, **kwargs):
        ret = func(*args, **kwargs)
        if ret:
            return ret
        return True
    return inner

@wrapper
def returns_false():
  breakpoint()
  return False
assert(returns_false())
print("Hello world")

Здесь я использовал вложенную функцию, чтобы можно было использовать функцию wrapper в качестве декоратора. Конечно, wrapper можно использовать напрямую, не применяя его в качестве декоратора. Эта функция изменяет возвращаемое значение return_false на True. Он будет работать с любой функцией, поскольку проверяет, возвращает ли она истинное значение; если да, то это не беспокоит; если нет, возвращается True.

Запустить print('Hello World') без запуска AssertionError лучше всего с помощью методов, указанных выше; то есть либо а). порядок assert и print поменять местами (код 1); б). изменить возвращаемое значение функции (код 2); или в). одна из перечисленных выше реализаций (try/except, менеджер контекста, декоратор/обертка). Конечно, установка флага -O — еще один жизнеспособный вариант, но если вы хотите, чтобы другие assert в вашем коде работали, то это может быть не лучшим выбором.

Наконец, хотя я настоятельно рекомендую один из вышеперечисленных методов, на самом деле есть способ пропустить assert с помощью pdb. Теперь, если по какой-то причине вам действительно нужно использовать только команды pdb, попробуйте:

import pdb

def returns_false():
  # breakpoint() not needed here
  return False

pdb.set_trace()
assert(returns_false())
print("Hello world")

Затем в консоли сделайте следующее:

> /home/repl/673e9267-383f-4c6f-9338-ccf0a50572a3/main.py(8)<module>()
-> assert(returns_false())
(Pdb) 
l
  3     def returns_false():
  4       # breakpoint() not needed here
  5       return False
  6     
  7     pdb.set_trace()
  8  -> assert(returns_false())
  9     print("Hello world")
[EOF]
(Pdb) 
j 9
> /home/repl/673e9267-383f-4c6f-9338-ccf0a50572a3/main.py(9)<module>()
-> print("Hello world")
(Pdb) 
c
Hello world

Альтернативно, вы не хотите import pdb и pdb.set_trace(), есть аналогичный второй метод достижения цели с использованием breakpoint():

def returns_false():
  # breakpoint() not needed here
  return False

breakpoint()
assert(returns_false())
print("Hello world")

а затем это в консоли:

> /home/repl/f0e3ca08-1383-492f-9245-a1cc19b7f28d/main.py(6)<module>()
-> assert(returns_false())
(Pdb) 
l
  1     def returns_false():
  2       # breakpoint() not needed here
  3       return False
  4     
  5     breakpoint()
  6  -> assert(returns_false())
  7     print("Hello world")
[EOF]
(Pdb) 
j 7
> /home/repl/f0e3ca08-1383-492f-9245-a1cc19b7f28d/main.py(7)<module>()
-> print("Hello world")
(Pdb) 
c
Hello world

Первоначально я думал, что использование только pdb не сработает, потому что, когда я попробовал описанный выше метод (и pdb.set_trace(), и breakpoint()), все, что я получил, это:

*** Jump failed: can only jump from a 'line' trace event

Он был напечатан после того, как я ввел команду j (аббревиатура jump). Однако позже я узнал, что между pdb, который здесь используется, и ipdb, потомком ipdb, который был установлен на моем компьютере, была какая-то проблема. Проще говоря, эти двое просто не ладят. Итак, будьте осторожны: если на вашем компьютере установлен ipdb, приведенный выше код НЕ будет работать и приведет к появлению загадочного сообщения об ошибке. Но если у вас нет ipdb, то приведенный выше код будет работать нормально. (Примечание. Я запустил приведенный выше код в онлайн-интерпретаторе Python, потому что не могу заставить его работать на своем компьютере. Я все еще пытаюсь найти проблему с ipdb, но пока не могу найти причину.)

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

C4stor 04.07.2024 12:00

@C4stor А как насчет других методов? Или вы действительно не можете изменить ни одну строчку кода? Если да, то извините, но я думаю, что перепрыгнуть assert невозможно. Вероятно, вам следует попытаться опустить все assert, используя метод python -O test.py, указанный в моем посте.

Luke L 04.07.2024 20:06

@C4stor В своем описании вы упомянули, что хотели бы получить подробное описание структуры выполнения Python. Я думаю, что могу добавить это как еще одно решение (потому что мое исходное решение предполагает, что вы можете изменить хотя бы немного кода. Кроме того, оно уже слишком длинное). Хотите, чтобы я улучшил существующий ответ или добавил новый?

Luke L 04.07.2024 20:23

@ C4stor Я исследовал всевозможные методы, но ни один из них, похоже, не работает. Не могли бы вы объяснить вашу проблему немного подробнее? Например, является ли ваш пример реальным кодом, с которым вы имеете дело? И правда ли, что вы действительно не можете изменить ни одной строчки кода? Если да, то объясните, почему. Я нашел массу методов, которые работали бы, если бы можно было изменить хотя бы несколько строк кода. Кроме того, почему вы можете использовать breakpoint() и pdb, если вы не можете изменить какой-либо код? Оно уже есть? Добавление этих деталей действительно ОЧЕНЬ поможет.

Luke L 05.07.2024 07:47

Привет :-) Я максимально упростил задачу, чтобы ее можно было воспроизвести. На практике происходит следующее: - я могу запустить python -m pdb с нуля - я начинаю отладку - я ввожу функцию f() в используемую нами библиотеку (поэтому я не могу прикасаться к коду) - метод завершается on return(lib_call()) , и мне нужно смоделировать, что происходит с различными возвращаемыми значениями в lib_call() - поэтому на этом этапе я бы хотел заставить f() возвращать любое значение, которое я хочу, и двигаться вперед. Вопрос предназначен для правильной эмуляции этих условий без огромной настройки.

C4stor 05.07.2024 09:09

Я также много чего пробовал, сейчас проверяю, могу ли я изменить байт-код в Inspect.currentframe().f_code.co_code , но, похоже, это не работает. Я также попробовал использовать новый sys.monitoring с обратным вызовом на Py_RETURN, обратный вызов вызывается, но изменение retval ничего не дает :-( (также я подозреваю, что если retval является ссылкой, а не просто «False», возможно, это сработает)

C4stor 05.07.2024 09:11

@C4stor Однако новое решение по-прежнему добавляет строку ret_val=True в код функции. Это правда, что если ret_val является ссылкой на переменную, то методы pdb будут работать, и мое новое решение основано именно на этом. Я попробовал несколько раз, и получилось хорошо. В настоящее время я изучаю другие методы, например, изменение байт-кодов.

Luke L 07.07.2024 05:50

Если вы не хотите менять положение breakpoint(), проверьте это:

def return_false():
    breakpoint()
    ret_val = False # must define ret_val here
    return ret_val


assert(return_false())
print('Hello World')

Затем в консоли:

> /home/repl/aa5ee727-4230-4aa9-b79d-9b3ca437e234/main.py(3)return_false()
-> ret_val = False
(Pdb) 
l
  1     def return_false():
  2         breakpoint()
  3  ->     ret_val = False
  4         return ret_val
  5     
  6     
  7     assert(return_false())
  8     print('Hello World')
[EOF]
(Pdb) 
j 4
> /home/repl/aa5ee727-4230-4aa9-b79d-9b3ca437e234/main.py(4)return_false()
-> return ret_val
(Pdb) 
ret_val=True
(Pdb) 
c
Hello World

Однако приведенный выше метод по-прежнему меняет код внутри функции. Если добавление строки ret_val=True неприемлемо, то это вам не подойдет. Однако существует высокая вероятность того, что подобная строка в коде уже будет. В любом случае, попробуйте приведенный выше код.

Опять же, приведенный выше код выполняется в онлайн-интерпретаторе Python, поскольку он не работает на моем компьютере.

Ответ принят как подходящий

Это действительно возможно в динамическом языке, таком как Python, где код интерпретируется построчно и может быть самоизменяющимся.

Вам придется много печатать, но вот что вы можете ввести в отладчик:

import ctypes
tup = returns_false.__code__.co_consts
obj = ctypes.py_object(tup)
pos = ctypes.c_ssize_t(1)
o = ctypes.py_object(True)
ref_count = ctypes.c_long.from_address(id(tup))
original_count = ref_count.value
ref_count.value = 1
ctypes.pythonapi.Py_IncRef(o)
ctypes.pythonapi.PyTuple_SetItem(obj, pos, o)
ref_count.value = original_count
c

Это изменяет само возвращаемое значение в памяти и приведет к тому, что returns_false вернет True вместо False. Последующее утверждение после выхода из pdb пройдет. Здесь нет никакой ловкости рук и никаких изменений исходных исходных файлов, мы буквально меняем возвращаемое значение во время выполнения.

Последняя строка здесь «c» — это ярлык для «продолжить» в pdb и завершает отладчик.

Объяснение

Здесь я использую Python 3.12.4. Некоторые детали реализации могут отличаться в других версиях Python, но должна работать та же основная техника.

Для исходного исходного кода:

def returns_false():
  breakpoint()
  return False

assert(returns_false())
print("Hello world")

Рассмотрим дизассемблирование вашей функции с использованием модуля stdlib dis:

>>> import dis
>>> dis.dis(returns_false)
  1           0 RESUME                   0

  2           2 LOAD_GLOBAL              1 (NULL + breakpoint)
             12 CALL                     0
             20 POP_TOP

  3          22 RETURN_CONST             1 (False)

Последняя строка указывает возвращаемое значение RETURN_CONST (False). В последней строке дизассемблирования вы также увидите три цифры:

  • 3 относится к номеру строки оператора возврата в исходном файле.
  • 22 — это смещение этой инструкции в байт-коде.
  • 1 — это аргумент операции RETURN_CONST.

Последний пункт интересен. На самом деле это означает, что функция возвращает элемент 1 из таблицы констант объекта функции. dis услужливо указал в скобках, что этот элемент имеет значение «False», но он просто отображает любой элемент 1 в таблице констант:

>>> returns_false.__code__.co_consts
(None, False)

Таблица констант будет длиннее, если в теле функции будет больше констант, например, если вы добавили строку x = 1234 внутри функции, которую ожидали увидеть 1234 в таблице констант, и возвращаемое значение теперь будет находиться по индексу 2. вместо этого (pos в моем примере также придется соответствующим образом изменить).

Итак, таблица констант представляет собой кортеж, который является неизменяемым типом, но что, если бы мы могли изменить этот кортеж (в Python все изменяемо, если вы знаете, где искать). Изменит ли изменение элемента с индексом 1 возвращаемое значение функции? Действительно, так и было бы.

Частью C API является PyTuple_SetItem

int PyTuple_SetItem(PyObject *p, Py_ssize_t pos, PyObject *o)

Вставьте ссылку на объект o в позицию pos кортежа, на который указывает p. Верните 0 в случае успеха. Если pos выходит за пределы, верните -1 и установите исключение IndexError.

И разработчики CPython даже настолько полезны, что предоставляют Python API в ctypes.pythonapi для использования таких функций, как PyTuple_SetItem, непосредственно из среды выполнения.

Остальная часть ответа — это вопрос техники и некоторых ноу-хау в реализации, стараясь не вызвать сегфолт или не испортить подсчет ссылок.

Обратите внимание, что дизассемблирование «взломанной» функции будет учитывать обновления таблицы констант и теперь будет отображать RETURN_CONST с (True):

>>> returns_false.__code__.co_consts
(None, False)
>>> import ctypes
... tup = returns_false.__code__.co_consts
... obj = ctypes.py_object(tup)
... pos = ctypes.c_ssize_t(1)
... o = ctypes.py_object(True)
... ref_count = ctypes.c_long.from_address(id(tup))
... original_count = ref_count.value
... ref_count.value = 1
... ctypes.pythonapi.Py_IncRef(o)
... ctypes.pythonapi.PyTuple_SetItem(obj, pos, o)
... ref_count.value = original_count
... 
>>> returns_false.__code__.co_consts
(None, True)
>>> returns_false()
> /private/tmp/p.py(3)returns_false()
-> return False
(Pdb) c
True
>>> import dis
>>> dis.dis(returns_false)
  1           0 RESUME                   0

  2           2 LOAD_GLOBAL              1 (NULL + breakpoint)
             12 CALL                     0
             20 POP_TOP

  3          22 RETURN_CONST             1 (True)

Наконец, я упомяну, что RETURN_CONST — не единственный код операции возврата для функции, фактически он является новым в Python 3.12. RETURN_VALUE, вероятно, встречается чаще. В других Python 3.x (я проверял 3.6-3.11) ваш код будет использовать RETURN_VALUE op, но тот же патч, дословно введенный в отладчике, будет работать, потому что он просто будет возвращать значение, которое было ранее загружено из констант. таблицу в стек. Детали дизассемблирования будут выглядеть иначе в Python 3.6-3.10, 3.11 и 3.12.

Вас также могут заинтересовать вопросы и ответы можно ли манипулировать локальную переменную, введенную в тело функции? где я продемонстрировал аналогичный метод изменения локальных переменных функции. В этом ответе я также изменил возврат функции, которая использовала код операции RETURN_VALUE.

Не тот, кто голосовал против, но я полагаю, что отрицательный голос возник из-за многословия и хрупкости хака ctypes, но я считаю, что это хорошее чтение. Проголосовал за восстановление и +1 для вас.

blhsing 08.07.2024 05:42

Обратите внимание, что изменение co_consts работает только в том случае, если функция вообще возвращает константу, и этот подход не будет работать, если функция возвращает результат выражения, например, с return x == y.

blhsing 08.07.2024 05:56

@blhsing Я могу ответить на вопрос только так, как он задан. Подобный метод всегда возможен в ctypes или даже непосредственно в Python — в этом случае вы можете использовать MonkeyPatch для type(x).__eq__ или просто заменить значение x непосредственно в отладчике.

wim 08.07.2024 05:58

Это чрезвычайно хрупко и на самом деле не является способом изменить возвращаемые значения. Он изменяет кортеж констант функции, что влияет на все случаи использования этой константы в любом месте кода функции. Если функция использует константу не только для возвращаемого значения, это повлияет не только на то, о чем просит вопрос, а если функция не возвращает константу (например, return sum(l)), она вообще не будет работать. . Изменение кортежа констант оказывается достаточным для этой очень специфической функции, но оно не обобщает. Кроме того, это влияет на все будущие вызовы, а не только на текущий вызов.

user2357112 08.07.2024 05:59

ОП упоминает, что он/она ищет «общеприменимое решение», поэтому я думаю, что ошибка все еще заметна.

blhsing 08.07.2024 05:59

@user2357112 user2357112 Если вы не хотите, чтобы это повлияло на другое использование таблицы consts, вы переходите к оператору return перед применением исправления, а затем сразу же после этого отменяете исправление.

wim 08.07.2024 06:03

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

C4stor 08.07.2024 10:42

Это специальный хак, который не отвечает на вопрос, можно ли стандартными средствами выйти из исполняемой функции в произвольном месте, вернув произвольное значение.

Vitalizzare 08.07.2024 12:13

Возврат константы — самый трудный случай для Monkeypatch. Переопределить возвращаемые значения переменных в сравнении легко — вы можете просто заменить переменную в отладчике. Если код вызывающего абонента можно изменить, проблема тривиальна (например, ock.patch ). В каком-то смысле C4stor сформулировал вопрос самым сложным образом.

wim 08.07.2024 19:29

@Vitalizzare Я согласен, но вопрос сформулирован не так. Мне все равно было бы очень любопытно получить ответ на более общий вопрос, поэтому я подготовлю новый вопрос с лучшей формулировкой. Я думаю, что этот вопрос сам по себе заслуживает внимания, и хотя ответ на него ситуативный, из него еще есть чему поучиться (по крайней мере, я научился). У меня достаточно репутации, чтобы выставить больше наград в погоне за Граалем :-D

C4stor 09.07.2024 11:41

@wim Что бы вы сделали, если бы все вычисления были помещены после return, вроде def f(x): return [x for x in range(x)]? Не проще ли просто запустить модуль с помощью pdb до интересующей линии, пропустить ее, прыгнув, и продолжить?

Vitalizzare 09.07.2024 13:15

@Vitalizzare Хороший вопрос! Установите другую награду и я буду охотиться за ней ;)

wim 09.07.2024 17:09

@wim Этот вопрос был всего лишь контрпримером к утверждению, что замена константы — самое сложное. Если вы считаете, что вопрос заслуживает полного ответа, вы можете спросить его сами.

Vitalizzare 10.07.2024 00:09

@Vitalizzare Вы правы, это случай, который я не учел, делая заявление. Даже def f(): return [] кажется, что обезьянку патчить будет сложно.

wim 10.07.2024 00:58

Вы можете установить точку останова на строке, которая вызывает returns_false, когда вы загружаете скрипт с помощью pdb, чтобы вы могли ввести перерыв перед вызовом, после чего вы можете исправить функцию, чтобы она возвращалась True.

Пример сеанса с вашим примером сценария в качестве входных данных:

# python -mpdb test.py
> /root/test.py(1)<module>()
-> def returns_false():
(Pdb) b 5
Breakpoint 1 at /root/test.py:5
(Pdb) r
> /root/test.py(5)<module>()
-> assert(returns_false())
(Pdb) !returns_false = lambda: True
(Pdb) r
Hello world
--Return--
> /root/test.py(6)<module>()->None
-> print("Hello world")
(Pdb)

В случае, если returns_false вызывается последующим кодом, вы можете сохранить исходную функцию и восстановить returns_false с оригиналом после вызова функции-заглушки:

# python -mpdb test.py
> /root/test.py(1)<module>()
-> def returns_false():
(Pdb) b 5
Breakpoint 1 at /root/test.py:5
(Pdb) b 6
Breakpoint 2 at /root/test.py:6
(Pdb) r
> /root/test.py(5)<module>()
-> assert(returns_false())
(Pdb) !orig_returns_false = returns_false; returns_false = lambda: True
(Pdb) r
> /root/test.py(6)<module>()
-> print("Hello world")
(Pdb) !returns_false = orig_returns_false
(Pdb) r
Hello world
--Return--
> /root/test.py(6)<module>()->None
-> print("Hello world")
(Pdb)

Сначала вам нужно запустить скрипт:

python -m pdb your_script.py

Когда скрипт достигнет точки останова, введите следующее:

(Pdb) import code

(Pdb) co = returns_false.__code__

(Pdb) new_co = code.CodeType(co.co_argcount, co.co_posonlyargcount, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize, co.co_flags, co.co_code[:-2] + b'\x01', co.co_consts, co.co_names, co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars)

(Pdb) returns_false.__code__ = new_co

(Pdb) c

На третьем этапе создается новый объект кода с теми же свойствами, что и исходный, за исключением последних двух байтов атрибута co_code, который представляет собой байт-код для возврата False. Вам необходимо заменить последний байт на b'\x01', который является байт-кодом для возврата True.

Мне нравится эта идея, но, похоже, она не работает: 1. CodeType не является частью модуля кода, а является частью модуля типов, и это достаточно легко исправить. 2. Я попробовал это, и это не сработало, потому что две причины: а. Пока метод return_false() работает, он кажется безразличным к изменению b. Если я «обмажу» и отредактирую код метода перед его запуском, он больше не сможет работать (я полагаю, что конструктор неправильный, по крайней мере, не в Python 3.9 и 3.12).

C4stor 08.07.2024 09:50

Эта идея не работает, существующий код в памяти будет продолжать использоваться — ничего не будет следить за тем, чтобы вы динамически переназначили __code__.

wim 08.07.2024 19:17

Другие вопросы по теме