Timer
Use timer as a standalone class so you have full control on when to call a lap (most useful in while loops)…
N = 50
t = Timer(N)
info = None
for i in range (N):
time.sleep(0.01 )
t(info= info) # Lap and present the time
if i == N // 2 :
print ()
info = f"My Info: { i* 3.122 } "
26/50 (0.32s - 0.30s remaining - 80.72 iters/s)
My Info: 78.05 50/50 (0.62s - 0.00s remaining - 80.88 iters/s)
Track2
… or use track2 to directly track a for loop
N = 50
info = None
for i in (tracker := track2(range (N), total= N)):
time.sleep(0.01 )
info = f"My Info: { i* 3.122 :.2f} "
if i == N // 2 :
print ()
if i >= N // 2 :
tracker.send(info)
25/50 (0.30s - 0.30s remaining - 83.74 iters/s)
My Info: 152.98 50/50 (0.60s - 0.00s remaining - 83.91 iters/s)
Warning! NEVER RUN tracker.send(None)
as this will skip variables silently
Print execution time
@timeit
decorates and prints time taken to execute a function as an info log
@timeit
def foo(a, b= None ):
if b is None :
return a + 1
else :
time.sleep(2 )
return a + b
Print IO
@io
will decorate to show inputs and outputs (along with time taken to execute) as a debug log
@io
def foo(a, b= None ):
if b is None :
return a + 1
else :
time.sleep(2 )
return a + b
with debug_mode():
foo(10 )
foo(10 , b= 20 )
[09/08/24 21:15:26] DEBUG 3679299354.py : <module>:11
0.00 seconds to execute `foo`
args ()
0 - 10 ( 🏷️ int)
kwargs
outputs - 11 ( 🏷️ int)
[09/08/24 21:15:28] DEBUG 3679299354.py : <module>:12
2.01 seconds to execute `foo`
args ()
0 - 10 ( 🏷️ int)
kwargs
b - 20 ( 🏷️ int)
outputs - 30 ( 🏷️ int)
@io
can be forced to print as log/trace if needed
@io (level= "trace" )
def foo(a, b= None ):
if b is None :
return a + 1
else :
time.sleep(2 )
return a + b
with trace_mode():
foo(10 )
import time
time.sleep(1 )
foo(10 , b= 20 )
[10/02/24 18:38:57] TRACE d=87126;file://<ipython-input-1-57a17d8aa123>:11\<ipython-input-1-57a17d8aa123>;;\:d=156948;file://<ipython-input-1-57a17d8aa123>:11#<module>:11\<module>:11;;\
0.00 seconds to execute `foo`
args()
0 - 10 (🏷️ int)
kwargs
outputs - 11 (🏷️ int)
[10/02/24 18:39:00] TRACE d=636125;file://<ipython-input-1-57a17d8aa123>:15\<ipython-input-1-57a17d8aa123>;;\:d=988312;file://<ipython-input-1-57a17d8aa123>:15#<module>:15\<module>:15;;\
2.00 seconds to execute `foo`
args()
0 - 10 (🏷️ int)
kwargs
b - 20 (🏷️ int)
outputs - 30 (🏷️ int)
Source path:... <ipython-input-1-57a17d8aa123>
Starting var:.. a = 10
Starting var:.. b = None
18:38:57.286088 call 1 SOURCE IS UNAVAILABLE
18:38:57.286204 line 3 SOURCE IS UNAVAILABLE
18:38:57.286225 line 4 SOURCE IS UNAVAILABLE
18:38:57.286239 return 4 SOURCE IS UNAVAILABLE
Return value:.. 11
Elapsed time: 00:00:00.000390
Source path:... <ipython-input-1-57a17d8aa123>
Starting var:.. a = 10
Starting var:.. b = 20
18:38:58.307768 call 1 SOURCE IS UNAVAILABLE
18:38:58.307885 line 3 SOURCE IS UNAVAILABLE
18:38:58.307915 line 6 SOURCE IS UNAVAILABLE
18:39:00.308346 line 7 SOURCE IS UNAVAILABLE
18:39:00.308487 return 7 SOURCE IS UNAVAILABLE
Return value:.. 30
Elapsed time: 00:00:02.001016
Try Catch with a single line
@tryy
def do(a, b, c):
return 1 / 0
x = do(1 , 2 , c= 10 )
assert x is None # tryy returns None by default
[09/08/24 21:15:31] WARNING Error for `do` with 2770652618.py : inner:26
args ()
0 - 1 ( 🏷️ int)
1 - 2 ( 🏷️ int)
kwargs
c - 10 ( 🏷️ int)
ZeroDivisionError: division by zero
Use your own default on failure
@tryy (output_to_return_on_fail= "😔" )
def do(a, b, c):
return 1 / 0
do(1 , 2 , c= 10 )
WARNING Error for `do` with 2770652618.py : inner:26
args ()
0 - 1 ( 🏷️ int)
1 - 2 ( 🏷️ int)
kwargs
c - 10 ( 🏷️ int)
ZeroDivisionError: division by zero
Optionally print the full stacktrace if needed
@tryy (print_traceback= True , output_to_return_on_fail= "😔" )
def do(a, b, c):
return 1 / 0
do(1 , 2 , c= 10 )
WARNING Error for `do` with 2770652618.py : inner:34
args ()
0 - 1 ( 🏷️ int)
1 - 2 ( 🏷️ int)
kwargs
c - 10 ( 🏷️ int)
Traceback ( most recent call last) :
File "/var/folders/1_/71dqv9vx2750gmyz77q_f45w0000gn/T/ipykernel_19659/2770652618.py" , line 21 , in inner
return f ( *args, **kwargs)
^^^^^^^^^^^^^^^^^^
File "/var/folders/1_/71dqv9vx2750gmyz77q_f45w0000gn/T/ipykernel_19659/580638143.py" , line 3 , in do
return 1 / 0
~~^~~
ZeroDivisionError: division by zero
Traceback ( most recent call last) :
File "/var/folders/1_/71dqv9vx2750gmyz77q_f45w0000gn/T/ipykernel_19659/2770652618.py" , line 21 , in inner
return f ( *args, **kwargs)
File "/var/folders/1_/71dqv9vx2750gmyz77q_f45w0000gn/T/ipykernel_19659/580638143.py" , line 3 , in do
return 1 / 0
ZeroDivisionError: division by zero
╭───────────────────────────────────────── Traceback (most recent call last) ──────────────────────────────────────────╮
│ in inner :21 │
│ │
│ 18 │ │ │
│ 19 │ │ def inner (*args, **kwargs): │
│ 20 │ │ │ try : │
│ ❱ 21 │ │ │ │ return f(*args, **kwargs) │
│ 22 │ │ │ except Exception as e: │
│ 23 │ │ │ │ if not silence_errors: │
│ 24 │ │ │ │ │ if not print_traceback: │
│ │
│ in do :3 │
│ │
│ 1 @tryy (print_traceback=True , output_to_return_on_fail="😔" ) │
│ 2 def do (a, b, c): │
│ ❱ 3 │ return 1 / 0 │
│ 4 │
│ 5 │
│ 6 do(1 , 2 , c=10 ) │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
ZeroDivisionError: division by zero
You can also silence the errors completely
@tryy (silence_errors= True , output_to_return_on_fail= "😔" )
def do(a, b, c):
return 1 / 0
do(1 , 2 , c= 10 )
You can collect all your errors in a list
import random
errors = []
@tryy (silence_errors= True , store_errors= errors)
def do(a, b, c):
if random.randint(0 , 100 ) < 50 :
return 1 / 0
else :
raise NotImplementedError ("🤔" )
for _ in range (4 ):
do(1 , random.randint(0 , 10 ), c= random.randint(0 , 100 ))
print (errors)
[
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 6 (🏷️ int)
kwargs
c - 23 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - NotImplementedError (🏷️ str)
```
,
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 9 (🏷️ int)
kwargs
c - 100 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - NotImplementedError (🏷️ str)
```
,
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 6 (🏷️ int)
kwargs
c - 60 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - ZeroDivisionError (🏷️ str)
```
,
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 2 (🏷️ int)
kwargs
c - 32 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - NotImplementedError (🏷️ str)
```
]
There’s onlly one usecase where you would want to send in a list by yourself - when you want to append your errors to an existing list. The sensible default is to always store the errors, especially because this is a debugging tool.
Just access all the errors in a dataframe like so
import random
random.seed(10 )
@tryy (silence_errors= True )
def do(a, b, c):
if c < 50 :
return 1 / 0
else :
raise NotImplementedError ("🤔" )
for _ in range (4 ):
do(1 , random.randint(0 , 10 ), c= random.randint(0 , 100 ))
do.error_summary()
0
do
(1, 9)
{'c': 4}
None
ZeroDivisionError
1
do
(1, 6)
{'c': 61}
None
NotImplementedError
2
do
(1, 9)
{'c': 1}
None
ZeroDivisionError
3
do
(1, 3)
{'c': 59}
None
NotImplementedError
and the actual list of errors like so
[
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 9 (🏷️ int)
kwargs
c - 4 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - ZeroDivisionError (🏷️ str)
```,
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 6 (🏷️ int)
kwargs
c - 61 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - NotImplementedError (🏷️ str)
```,
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 9 (🏷️ int)
kwargs
c - 1 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - ZeroDivisionError (🏷️ str)
```,
```↯ AttrDict ↯
func - do (🏷️ str)
args()
0 - 1 (🏷️ int)
1 - 3 (🏷️ int)
kwargs
c - 59 (🏷️ int)
tb - None (🏷️ NoneType)
err_type - NotImplementedError (🏷️ str)
```]
Finally, you want to run the function (without try) to reproduce the error and actually start debugging. Just use the .F
attribute to access the original function that you created
ix = 2
data = do.error_store[ix]
try :
do.F(* data.args, ** data.kwargs)
except Exception as e:
print (f"ERROR: " , e)