ycecream
Project description
Do you ever use print() or log() to debug your code? If so, ycecream, or y for short, will make printing debug information a lot sweeter. And on top of that, you get some basic benchmarking functionality.
Installation
Installing ycecream with pip is easy.
$ pip install ycecream
or when you want to upgrade,
$ pip install ycecream --upgrade
Alternatively, ycecream.py can be juist copied into you current work directory from GitHub (https://github.com/salabim/ycecream).
No dependencies!
Inspect variables and expressions
Have you ever printed variables or expressions to debug your program? If you’ve ever typed something like
print(add2(1000))
or the more thorough
print("add2(1000)", add2(1000)))
or (for Python >= 3.8 only):
print(f"{add2(1000) =}")
then y() is here to help. With arguments, y() inspects itself and prints both its own arguments and the values of those arguments.
from ycecream import y def add2(i): return i + 2 y(add2(1000))
prints
y| add2(1000): 1002
Similarly,
from ycecream import y class X: a = 3 world = {"EN": "world", "NL": "wereld", "FR": "monde", "DE": "Welt"} y(world, X.a)
prints
y| world: {"EN": "world", "NL": "wereld", "FR": "monde", "DE": "Welt"}, X.a: 3
Just give y() a variable or expression and you’re done. Sweet, isn’t it?
Inspect execution
Have you ever used print() to determine which parts of your program are executed, and in which order they’re executed? For example, if you’ve ever added print statements to debug code like
def add2(i): print("enter") result = i + 2 print("exit") return result
then y() helps here, too. Without arguments, y() inspects itself and prints the calling line number and -if applicable- the file name and parent function.
from ycecream import y def add2(i): y() result = i + 2 y() return result y(add2(1000))
prints something like
y| #3 in add2() y| #5 in add2() y| add2(1000): 1002
Just call y() and you’re done. Isn’t that sweet?
Return Value
y() returns its argument(s), so y() can easily be inserted into pre-existing code.
from ycecream import y def add2(i): return i + 2 b = y(add2(1000)) y(b)
prints
y| add2(1000): 1002 y| b: 1002
Debug entry and exit of function calls
When you apply y() as a decorator to a function or method, both the entry and exit can be tracked. The (keyword) arguments passed will be shown and upon return, the return value.
from ycecream import y @y() def mul(x, y): return x * y print(mul(5, 7))
prints
y| called mul(5, 7) y| returned 35 from mul(5, 7) in 0.000006 seconds 35
It is possible to suppress the print-out of either the enter or the exit information with the show_enter and show_exit parameters, like:
inport ycecream as y @y(show_exit=False) def mul(x, y): return x * y print(mul(5, 7))
prints
y| called mul(5, 7) 35
Note that it is possible to use y as a decorator without the parentheses, like
@y def diode(x): return 0 if x<0 else x
, but this might not work correctly when the def/class definition spawns more than one line. So, always use y() or y(<parameters>) when used as a decorator.
Benchmarking with ycecream
If you decorate a function or method with y, you will be offered the duration between entry and exit (in seconds) as a bonus.
That opens the door to simple benchmarking, like:
from ycecream import y import time @y(show_enter=False,show_line_number=True) def do_sort(i): n = 10 ** i x = sorted(list(range(n))) return f"{n:9d}" for i in range(8): do_sort(i)
the ouput will show the effects of the population size on the sort speed:
y| #5 ==> returned ' 1' from do_sort(0) in 0.000027 seconds y| #5 ==> returned ' 10' from do_sort(1) in 0.000060 seconds y| #5 ==> returned ' 100' from do_sort(2) in 0.000748 seconds y| #5 ==> returned ' 1000' from do_sort(3) in 0.001897 seconds y| #5 ==> returned ' 10000' from do_sort(4) in 0.002231 seconds y| #5 ==> returned ' 100000' from do_sort(5) in 0.024014 seconds y| #5 ==> returned ' 1000000' from do_sort(6) in 0.257504 seconds y| #5 ==> returned ' 10000000' from do_sort(7) in 1.553495 seconds
It is also possible to time any code by using y as a context manager, e.g.
with y(): time.sleep(1)
wil print something like
y| enter y| exit in 1.000900 seconds
You can include parameters here as well:
with y(show_context=True, show_time=True): time.sleep(1)
will print somethink like:
y| #8 @ 13:20:32.605903 ==> enter y| #8 @ 13:20:33.609519 ==> exit in 1.003358 seconds
Finally, to help with timing code, you can request the current delta with
y().delta
or (re)set it with
y().delta = 0
So, e.g. to time a section of code:
y.delta = 0 time.sleep(1) duration = y.delta y(duration)
might print:
y| duration: 1.0001721999999997
Configuration
For the configuration, it is important to realize that y is an instance of the ycecream._Y class, which has a number of configuration attributes:
-------------------------------------------------- attribute alternative default -------------------------------------------------- prefix p "y| " output o "stderr" serialize pprint.pformat show_line_number sln False show_time st False show_delta sd False show_enter se True show_exit sx True sort_dicts *) sdi False enabled e True line_length ll 80 compact *) c False indent i 1 depth de 1000000 wrap_indent wi " " context_delimiter cd " ==> " pair_delimiter pd ", " values_only vo False return_none rn False enforce_line_length ell False decorator d False context_manager cm False -------------------------------------------------- *) ignored under Python 2.7
It is perfectly ok to set/get any of these attributes directly.
But, it is also possible to apply configuration directly in the call to y: So, it is possible to say
from ycecream import y y(12, prefix="==> ")
, which will print
==> 12
It is also possible to configure y permanently with the configure method.
y.configure(prefix="==> ") y(12)
will print
==> 12
It is arguably easier to say:
y.prefix = "==> " y(12)
or even
y.p = "==> " y(12)
to print
==> 12
Yet another way to configure y is to get a new instance of y with y.new() and the required configuration:
z = y.new(prefix="==> ") z(12)
will print
==> 12
Or, yet another possibility is to clone y (optionally with modified attributes):
yd1 = y.clone(show_date=True) yd2 = y.clone() yd2.configure(show_date=True)
After this yd1 and yd2 will behave similarly (but they are not the same!)
prefix / p
from ycecream import y y('world', prefix='hello -> ')
prints
hello -> 'world'
prefix can be a function, too.
import time from ycecream import y def unix_timestamp(): return f"{int(time.time())} " hello = "world" y = Y(prefix=unix_timestamp) y(hello)
prints
1613635601 hello: 'world'
output / o
This will allow the output to be handled by something else than the default (output being written to stderr).
The output attribute can be
a callable that accepts at least one parameter (the text to be printed)
a string or Path object that will be used as the filename
a text file that is open for writing/appending
In the example below,
from ycecream import y import sys y(1, output=print) y(2, output=sys.stdout with open("test", "a+") as f: y(3, output=f) y(4, output="")
y| 1 will be printed to stdout
y| 2 will be printed to stdout
y| 3 will be appended to the file test
y| 4 will disappear
As output may be any callable, you can even use this to automatically log any y output:
from ycecream import y import logging logging.basicConfig(level="INFO") log = logging.getLogger("demo") y.configure(output=log.info) a = {1, 2, 3, 4, 5} y(a) a.remove(4) y(a)
will print to stderr:
INFO:demo:y| a: {1, 2, 3, 4, 5} INFO:demo:y| a: {1, 2, 3, 5}
Finally, you can specify the following strings:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.