Pregunta Establecer el punto de interrupción del depurador al final de una función sin retorno


Estoy depurando el método f() eso no tiene return en eso.

class A(object):

    def __init__(self):
        self.X = []

    def f(self):            
        for i in range(10):
            self.X.append(i)

Necesito ver cómo este método modifica variable X justo después de que se llama. Para hacer eso, inserto un return al final del método, y establecer el punto de interrupción allí:

enter image description here

De esta forma, tan pronto como el método llegue a su return, Puedo ver el valor de mi variable X.


Esto hace el trabajo, pero estoy bastante seguro de que hay una mejor manera. La edición de un método o función cada vez que necesito depurarlo parece una tontería.

Pregunta:
¿Hay alguna manera diferente (por ejemplo, una opción en el depurador) para establecer un punto de interrupción al final de un método que no tiene un return?

(Tenga en cuenta que establecer un punto de interrupción en la llamada de función y el uso Paso sobre no mostraría X cuando mouseovering, ya que la función se llama desde un módulo diferente.)


32
2018-04-29 08:47


origen


Respuestas:


Puede agregar un punto de interrupción condicional en la última línea y configurar la condición para que sea algo que ocurra solo en la última iteración.

En este caso, la condición es muy fácil ya que es solo i == 9, pero puede ser mucho más complejo dependiendo de su condición de ciclo, por lo que a veces agregar una instrucción al final será la solución más fácil.

Conditional breakpoint in IntelliJ IDEA

Esa captura de pantalla es de IntelliJ IDEA y parece que su captura de pantalla es del mismo IDE, por lo que simplemente haga clic con el botón derecho en el punto de interrupción para mostrar el cuadro de diálogo e ingresar su condición.

Si está utilizando algún otro IDE, estoy seguro de que hay capacidad para hacer un punto de corte condicional.

Actualizar:

No hay soporte para romper al final de un método en el Depurador de Python, solo al comienzo de un método:

b (reak) [[nombre de archivo:] lineno | función [, condición]]

Con un argumento de Lineno, establezca un corte en el archivo actual. Con un argumento de función, establezca un corte en la primera instrucción ejecutable dentro de esa función. El número de línea puede tener un prefijo con un nombre de archivo y dos puntos, para especificar un punto de interrupción en otro archivo (probablemente uno que aún no se haya cargado). El archivo se busca en sys.path. Tenga en cuenta que a cada punto de interrupción se le asigna un número al que hacen referencia todos los demás comandos de punto de interrupción.

Si hay un segundo argumento presente, es una expresión que debe evaluarse como verdadera antes de que se respete el punto de interrupción.

Sin argumentos, enumere todos los saltos, incluso para cada punto de interrupción, la cantidad de veces que se ha alcanzado ese punto de interrupción, el recuento de ignorar actual y la condición asociada, si la hay.


5
2018-05-01 09:00



Hay una solución rápida y sucia que funciona en alguna lenguaje que admite el monopatching (Python, Ruby, ObjC, etc.). Honestamente, no recuerdo haberlo necesitado alguna vez en Python, pero lo hice bastante en SmallTalk y ObjC, así que tal vez te sea útil.

Simplemente envolver dinámicamente A.f en una función, como esta:

real_A_f = A.f
def wrap_A_f(self, *args, **kwargs):
    result = real_A_f(self, *args, **kwargs)
    return result
A.f = wrap_A_f

En la mayoría de los depuradores de secuencias de comandos, debe poder escribir un script que lo haga automáticamente para un método por nombre. En pdb, que te permite ejecutar código Python normal directamente en el depurador, es especialmente simple.

Ahora puedes poner un punto de interrupción en eso return result, y está garantizado para golpear inmediatamente después de la real A.f regresa (incluso si regresa en el medio o cae del final sin una return declaración).

Algunas cosas que quizás quiera agregar:

  • Si también quieres atrapar A.f elevar, poner un try: y except: raise alrededor del código, y agrega un punto de interrupción en el raise.
  • Para Python 2.x, es posible que desee envolverlo con types.MethodType para hacer un método unbound real.
  • Si solo quieres un punto de interrupción en un específico A instancia, puede usar un punto de corte condicional que verifique self is a, o usar types.MethodType para crear una instancia enlazada y almacenar eso como a.f.
  • Es posible que desee utilizar functools.wraps si desea ocultar el contenedor del resto del código (y de su depuración, excepto en los casos en los que realmente desea verlo).
  • Dado que pdb le permite ejecutar código dinámico directamente en el espacio de nombres en vivo, puede poner un wrap_method función en algún lugar de su proyecto que hace esto, y luego, en el prompt, escriba p utils.wrap_method(A, 'f'). Pero si ajusta varios métodos de esta manera, van a compartir los mismos puntos de interrupción (dentro de la función de contenedor definida en el interior) wrap_method) Aquí creo que un punto de corte condicional es la única opción razonable.
  • Si quieres acceso a los locales reales de A.f desde el punto de ruptura del contenedor, eso es mucho más difícil. Puedo pensar en algunas opciones muy hacky (por ejemplo, exec(real_A_f.__code__, real_A_f.globals()), pero nada con lo que estaría feliz.

3
2018-05-01 09:26



Tu IDE está ocultando lo que hay debajo del capó. Es decir, algo así como

import pdb

se antepone a su secuencia de comandos y

pdb.set_trace()

se inserta antes de la línea en la que colocaste tu punto de interrupción. Por lo que dices, deduzco que a PyCharm no le gusta colocar puntos de interrupción en líneas vacías. sin embargo pdb.set_trace() puede colocarse perfectamente al final de un método.

Entonces puede insertarlos usted mismo (o escribir una macro) y ejecutar python -m pdb para comenzar la depuración

(Editar) ejemplo

import pdb

class A(object):

    def __init__(self):
        self.X = []

    def f(self):
        for i in range(10):
            self.X.append(i)
        pdb.set_trace()

if __name__ == '__main__':
    a = A()
    a.f()

Depurar con

$ python -m pdb test.py 
> /dev/test.py(1)<module>()
----> 1 import pdb
      2 
      3 class A(object):

ipdb> cont
--Return--
> /dev/test.py(11)f()->None
-> pdb.set_trace()
(Pdb) self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
(Pdb)

ipdb se puede usar en lugar de pdb.


3
2018-05-01 22:39



Con pdb puedes usar una buena combinación de break function y until lineno:

Sin argumentos, continúe la ejecución hasta la línea con un número   mayor que el actual se alcanza.

Con un número de línea, continúe la ejecución hasta una línea con un número   mayor o igual a eso se alcanza. En ambos casos, también se detiene cuando el   el marco actual regresa

Modificado en la versión 3.2: permite dar un número de línea explícito.

Usted puede lograr lo que necesita.

Modifiqué un poco tu ejemplo (para que veas que la instrucción se ejecuta aunque pdb lo informe como "siguiente instrucción"):

01: class A(object):
02: 
03:     def __init__(self):
04:         self.X = []
05:         
06:     def f(self):         
07:         print('pre exec')
08:         for i in range(10):
09:             self.X.append(i)
10:         print('post exec')
11:             
12: a = A()
13: a.f()
14: print('Game is over')
15:

Y resultado de correr con python -m pdb test.py dice así:

Comience a depurar y ejecútelo justo después de la declaración de clase (para que pueda agregar un punto de interrupción con nombre):

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 11
> d:\tmp\stack\test.py(12)<module>()
-> a = A()

Ahora, rompe al comienzo de la función:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:6

Simplemente continúa con la ejecución hasta que llegue al punto de interrupción:

(Pdb) continue
> d:\tmp\stack\test.py(7)f()
-> print('pre exec')

Tomar ventaja de "también se detiene cuando vuelve el marco actual":

(Pdb) until 14
pre exec
post exec
--Return--

Como puedes ver, ambos pre ejecutivo y post exec se imprimieron, pero al ejecutar where todavía estás en f():

(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(13)<module>()
-> a.f()
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')
> d:\tmp\stack\test.py(10)f()->None
-> print('post exec')

Y todas las variables de contexto están intactas:

(Pdb) p self.X
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Ahora con tu ejemplo de vida real:

01: class A(object):
02:     def __init__(self):
03:         self.X = []
04:         
05:     def f(self):         
06:         for i in range(10):
07:             self.X.append(i)
08:             
09: a = A()
10: a.f()
11: print('Game is over')

Comience de la misma manera que antes:

> d:\tmp\stack\test.py(1)<module>()
-> class A(object):
(Pdb) until 8
> d:\tmp\stack\test.py(9)<module>()
-> a = A()
(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) cont
> d:\tmp\stack\test.py(6)f()
-> for i in range(10):

Ahora ... Punto de interrupción en f.A en realidad significa punto de corte en la primera declaración de f.A que desafortunadamente for i in... por lo que se rompería en cada ocasión.

Si realmente no inicia su código real con bucle, puede omitir esta parte.

(Pdb) disable 1
Disabled breakpoint 1 at d:\tmp\stack\test.py:5

Nuevamente, usa el until <end of file>:

(Pdb) until 10
--Return--
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):

Y de nuevo, todas las variables de cuadro están disponibles:

(Pdb) p i
9
(Pdb) w
  c:\python32\lib\bdb.py(405)run()
-> exec(cmd, globals, locals)
  <string>(1)<module>()
  d:\tmp\stack\test.py(10)<module>()
-> a.f()
> d:\tmp\stack\test.py(6)f()->None
-> for i in range(10):
(Pdb)

Lo triste aquí es que quería probar esta pieza de automatización:

(Pdb) break A.f
Breakpoint 1 at d:\tmp\stack\test.py:5
(Pdb) commands 1
(com) disable 1
(com) until 11
(com) end

Que haría todo lo que necesita automáticamente (de nuevo, disable 1 no es necesario cuando tienes al menos una declaración pre-loop), pero de acuerdo con la documentación de commands:

Al especificar cualquier comando que reanude la ejecución (actualmente continúa, paso, siguiente, retorno, salto, abandono y sus abreviaturas) finaliza la lista de comandos (como si ese comando fuera seguido inmediatamente por el final). Esto se debe a que cada vez que reanuda la ejecución (incluso con un simple siguiente o paso), puede encontrar otro punto de interrupción, que podría tener su propia lista de comandos, lo que generaría ambigüedades sobre qué lista ejecutar.

Asi que until simplemente no parece funcionar (al menos para Python 3.2.5 en Windows) y tienes que hacer esto a mano.


2
2018-05-07 11:09



Tienes algunas opciones aquí.

  1. Agregue un punto de interrupción a la última línea de la función.

En este caso, la última línea está dentro de un bucle, por lo que tendría que iterar sobre cada elemento en el bucle.

  1. Agregue un punto de corte donde se llama a la función.

Esto detendrá el depurador antes de que se llame a la función, pero puede "Pasar por encima" de la función para ver el valor de A.x después A.f() se llama.

  1. Agregue una declaración temporal al final de la función para romper en

Este truco funcionaría si su función termina en un bucle y hay múltiples lugares donde se llama a la función o si no desea rastrear la llamada a la función.

Puede agregar una instrucción simple al final de la función para fines de depuración y agregar un punto de interrupción allí.

def f(self):            
    for i in range(10):
        self.X.append(i)
    debug_break = 1

0
2018-04-29 18:50



¿Por qué no dejar el regreso allí? O un return None. Está implícito de todos modos, el intérprete / compilador hará lo mismo independientemente:

De hecho, incluso las funciones sin una declaración de retorno devuelven un valor, aunque bastante aburrido. Este valor se llama Ninguno (es un nombre incorporado).

[fuente: Tutorial de Python 4.6].


0
2018-05-08 03:27