7.4. Better Outputs

7.4.1. How to Strip Outputs and Execute Interactive Code in a Python Script

!pip install strip-interactive

Have you ever seen a tutorial with an interactive Python code and wished to execute it in a Python script like above?

It might be time-consuming to delete all >>> symbols and remove all outputs, especially when the code is long. That is why I created strip-interactive.

from strip_interactive import run_interactive

code = """
>>> import numpy as np
>>> print(np.array([1,2,3]))
[1 2 3]
>>> print(np.array([4,5,6]))
[4 5 6]
"""

clean_code = run_interactive(code)
[1 2 3]
[4 5 6]

Link to the article about strip-interactive.

Link to strip-interactive.

7.4.2. rich.inspect: Produce a Beautiful Report on any Python Object

!pip install rich 

If you want to quickly see which attributes and methods of a Python object are available, use rich’s inspect method.

rich’s inspect method allows you to create a beautiful report for any Python object, including a string.

from rich import inspect

print(inspect('hello', methods=True))
╭────────────────────────────────────── <class 'str'> ──────────────────────────────────────╮
 str(object='') -> str                                                                     
 str(bytes_or_buffer[, encoding[, errors]]) -> str                                         
                                                                                           
 ╭───────────────────────────────────────────────────────────────────────────────────────╮ 
  'hello'                                                                                
 ╰───────────────────────────────────────────────────────────────────────────────────────╯ 
                                                                                           
   capitalize = def capitalize(): Return a capitalized version of the string.              
     casefold = def casefold(): Return a version of the string suitable for caseless       
                comparisons.                                                               
       center = def center(width, fillchar=' ', /): Return a centered string of length     
                width.                                                                     
        count = def count(...) S.count(sub[, start[, end]]) -> int                         
       encode = def encode(encoding='utf-8', errors='strict'): Encode the string using the 
                codec registered for encoding.                                             
     endswith = def endswith(...) S.endswith(suffix[, start[, end]]) -> bool               
   expandtabs = def expandtabs(tabsize=8): Return a copy where all tab characters are      
                expanded using spaces.                                                     
         find = def find(...) S.find(sub[, start[, end]]) -> int                           
       format = def format(...) S.format(*args, **kwargs) -> str                           
   format_map = def format_map(...) S.format_map(mapping) -> str                           
        index = def index(...) S.index(sub[, start[, end]]) -> int                         
      isalnum = def isalnum(): Return True if the string is an alpha-numeric string, False 
                otherwise.                                                                 
      isalpha = def isalpha(): Return True if the string is an alphabetic string, False    
                otherwise.                                                                 
      isascii = def isascii(): Return True if all characters in the string are ASCII,      
                False otherwise.                                                           
    isdecimal = def isdecimal(): Return True if the string is a decimal string, False      
                otherwise.                                                                 
      isdigit = def isdigit(): Return True if the string is a digit string, False          
                otherwise.                                                                 
 isidentifier = def isidentifier(): Return True if the string is a valid Python            
                identifier, False otherwise.                                               
      islower = def islower(): Return True if the string is a lowercase string, False      
                otherwise.                                                                 
    isnumeric = def isnumeric(): Return True if the string is a numeric string, False      
                otherwise.                                                                 
  isprintable = def isprintable(): Return True if the string is printable, False           
                otherwise.                                                                 
      isspace = def isspace(): Return True if the string is a whitespace string, False     
                otherwise.                                                                 
      istitle = def istitle(): Return True if the string is a title-cased string, False    
                otherwise.                                                                 
      isupper = def isupper(): Return True if the string is an uppercase string, False     
                otherwise.                                                                 
         join = def join(iterable, /): Concatenate any number of strings.                  
        ljust = def ljust(width, fillchar=' ', /): Return a left-justified string of       
                length width.                                                              
        lower = def lower(): Return a copy of the string converted to lowercase.           
       lstrip = def lstrip(chars=None, /): Return a copy of the string with leading        
                whitespace removed.                                                        
    maketrans = def maketrans(...) Return a translation table usable for str.translate().  
    partition = def partition(sep, /): Partition the string into three parts using the     
                given separator.                                                           
      replace = def replace(old, new, count=-1, /): Return a copy with all occurrences of  
                substring old replaced by new.                                             
        rfind = def rfind(...) S.rfind(sub[, start[, end]]) -> int                         
       rindex = def rindex(...) S.rindex(sub[, start[, end]]) -> int                       
        rjust = def rjust(width, fillchar=' ', /): Return a right-justified string of      
                length width.                                                              
   rpartition = def rpartition(sep, /): Partition the string into three parts using the    
                given separator.                                                           
       rsplit = def rsplit(sep=None, maxsplit=-1): Return a list of the words in the       
                string, using sep as the delimiter string.                                 
       rstrip = def rstrip(chars=None, /): Return a copy of the string with trailing       
                whitespace removed.                                                        
        split = def split(sep=None, maxsplit=-1): Return a list of the words in the        
                string, using sep as the delimiter string.                                 
   splitlines = def splitlines(keepends=False): Return a list of the lines in the string,  
                breaking at line boundaries.                                               
   startswith = def startswith(...) S.startswith(prefix[, start[, end]]) -> bool           
        strip = def strip(chars=None, /): Return a copy of the string with leading and     
                trailing whitespace removed.                                               
     swapcase = def swapcase(): Convert uppercase characters to lowercase and lowercase    
                characters to uppercase.                                                   
        title = def title(): Return a version of the string where each word is titlecased. 
    translate = def translate(table, /): Replace each character in the string using the    
                given translation table.                                                   
        upper = def upper(): Return a copy of the string converted to uppercase.           
        zfill = def zfill(width, /): Pad a numeric string with zeros on the left, to fill  
                a field of the given width.                                                
╰───────────────────────────────────────────────────────────────────────────────────────────╯
None

7.4.3. Rich’s Console: Debug your Python Function in One Line of Code

!pip install rich 

Sometimes, you might want to know which elements in the function created a certain output. Instead of printing every variable in the function, you can simply use Rich’s Console object to print both the output and all the variables in the function.

from rich import console
from rich.console import Console 
import pandas as pd 

console = Console()

data = pd.DataFrame({'a': [1, 2, 3], 'b': [4, 5, 6]})

def edit_data(data):
    var_1 = 45
    var_2 = 30
    var_3 = var_1 + var_2
    data['a'] = [var_1, var_2, var_3]
    console.log(data, log_locals=True)

edit_data(data)
[08:12:24]     a  b                                                          1165738010.py:14
           0  45  4                                                                          
           1  30  5                                                                          
           2  75  6                                                                          
           ╭───── locals ─────╮                                                              
             data =     a  b                                                               
                    0  45  4                                                               
                    1  30  5                                                               
                    2  75  6                                                               
            var_1 = 45                                                                     
            var_2 = 30                                                                     
            var_3 = 75                                                                     
           ╰──────────────────╯                                                              

Link to my article about rich.

Link to rich.

7.4.4. loguru: Print Readable Traceback in Python

!pip install loguru 

Sometimes, it is difficult to understand the traceback and to know which inputs cause the error. Is there a way that you can print a more readable traceback?

That is when loguru comes in handy. By adding decorator logger.catch to a function, loguru logger will print a more readable trackback and save the traceback to a separate file like below

from sklearn.metrics import mean_squared_error
import numpy as np
from loguru import logger

logger.add("file_{time}.log", format="{time} {level} {message}")

@logger.catch
def evaluate_result(y_true: np.array, y_pred: np.array):
    mean_square_err = mean_squared_error(y_true, y_pred)
    root_mean_square_err = mean_square_err ** 0.5

y_true = np.array([1, 2, 3])
y_pred = np.array([1.5, 2.2])
evaluate_result(y_true, y_pred)
2021-09-12 08:13:20.710 | ERROR    | __main__:<module>:14 - An error has been caught in function '<module>', process 'MainProcess' (174022), thread 'MainThread' (139812013745984):
Traceback (most recent call last):

  File "/usr/lib/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
           │         │     └ {'__name__': '__main__', '__doc__': 'Entry point for launching an IPython kernel.\n\nThis is separate from the ipykernel pack...
           │         └ <code object <module> at 0x7f2884c85be0, file "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel_launcher.py", lin...
           └ <function _run_code at 0x7f2884c680d0>
  File "/usr/lib/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
         │     └ {'__name__': '__main__', '__doc__': 'Entry point for launching an IPython kernel.\n\nThis is separate from the ipykernel pack...
         └ <code object <module> at 0x7f2884c85be0, file "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel_launcher.py", lin...
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel_launcher.py", line 16, in <module>
    app.launch_new_instance()
    │   └ <bound method Application.launch_instance of <class 'ipykernel.kernelapp.IPKernelApp'>>
    └ <module 'ipykernel.kernelapp' from '/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/kernelapp.py'>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/traitlets/config/application.py", line 845, in launch_instance
    app.start()
    │   └ <function IPKernelApp.start at 0x7f2881184dc0>
    └ <ipykernel.kernelapp.IPKernelApp object at 0x7f2884d8e040>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/kernelapp.py", line 667, in start
    self.io_loop.start()
    │    │       └ <function BaseAsyncIOLoop.start at 0x7f2882144550>
    │    └ <tornado.platform.asyncio.AsyncIOMainLoop object at 0x7f2881149520>
    └ <ipykernel.kernelapp.IPKernelApp object at 0x7f2884d8e040>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/tornado/platform/asyncio.py", line 199, in start
    self.asyncio_loop.run_forever()
    │    │            └ <function BaseEventLoop.run_forever at 0x7f288257fb80>
    │    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
    └ <tornado.platform.asyncio.AsyncIOMainLoop object at 0x7f2881149520>
  File "/usr/lib/python3.8/asyncio/base_events.py", line 570, in run_forever
    self._run_once()
    │    └ <function BaseEventLoop._run_once at 0x7f2882583700>
    └ <_UnixSelectorEventLoop running=True closed=False debug=False>
  File "/usr/lib/python3.8/asyncio/base_events.py", line 1859, in _run_once
    handle._run()
    │      └ <function Handle._run at 0x7f28826304c0>
    └ <Handle <TaskWakeupMethWrapper object at 0x7f286251cbe0>(<Future finis...040>, ...],))>)>
  File "/usr/lib/python3.8/asyncio/events.py", line 81, in _run
    self._context.run(self._callback, *self._args)
    │    │            │    │           │    └ <member '_args' of 'Handle' objects>
    │    │            │    │           └ <Handle <TaskWakeupMethWrapper object at 0x7f286251cbe0>(<Future finis...040>, ...],))>)>
    │    │            │    └ <member '_callback' of 'Handle' objects>
    │    │            └ <Handle <TaskWakeupMethWrapper object at 0x7f286251cbe0>(<Future finis...040>, ...],))>)>
    │    └ <member '_context' of 'Handle' objects>
    └ <Handle <TaskWakeupMethWrapper object at 0x7f286251cbe0>(<Future finis...040>, ...],))>)>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/kernelbase.py", line 457, in dispatch_queue
    await self.process_one()
          │    └ <function Kernel.process_one at 0x7f28811ccca0>
          └ <ipykernel.ipkernel.IPythonKernel object at 0x7f28811a6280>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/kernelbase.py", line 446, in process_one
    await dispatch(*args)
          │         └ ([<zmq.sugar.frame.Frame object at 0x7f28811a1250>, <zmq.sugar.frame.Frame object at 0x7f286208f720>, <zmq.sugar.frame.Frame ...
          └ <bound method Kernel.dispatch_shell of <ipykernel.ipkernel.IPythonKernel object at 0x7f28811a6280>>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/kernelbase.py", line 353, in dispatch_shell
    await result
          └ <coroutine object Kernel.execute_request at 0x7f282fc3e4c0>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/kernelbase.py", line 648, in execute_request
    reply_content = await reply_content
                          └ <coroutine object IPythonKernel.do_execute at 0x7f282fc3e840>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/ipkernel.py", line 345, in do_execute
    res = shell.run_cell(code, store_history=store_history, silent=silent)
          │     │        │                   │                     └ False
          │     │        │                   └ True
          │     │        └ '\nfrom sklearn.metrics import mean_squared_error\nimport numpy as np\nfrom loguru import logger\n\nlogger.add("file_{time}.l...
          │     └ <function ZMQInteractiveShell.run_cell at 0x7f28811e1f70>
          └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/ipykernel/zmqshell.py", line 532, in run_cell
    return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs)
                 │                    │               │       └ {'store_history': True, 'silent': False}
                 │                    │               └ ('\nfrom sklearn.metrics import mean_squared_error\nimport numpy as np\nfrom loguru import logger\n\nlogger.add("file_{time}....
                 │                    └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
                 └ <class 'ipykernel.zmqshell.ZMQInteractiveShell'>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 2898, in run_cell
    result = self._run_cell(
             │    └ <function InteractiveShell._run_cell at 0x7f2881c925e0>
             └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 2944, in _run_cell
    return runner(coro)
           │      └ <coroutine object InteractiveShell.run_cell_async at 0x7f28620ae0c0>
           └ <function _pseudo_sync_runner at 0x7f2881c7ec10>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/IPython/core/async_helpers.py", line 68, in _pseudo_sync_runner
    coro.send(None)
    │    └ <method 'send' of 'coroutine' objects>
    └ <coroutine object InteractiveShell.run_cell_async at 0x7f28620ae0c0>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3169, in run_cell_async
    has_raised = await self.run_ast_nodes(code_ast.body, cell_name,
                       │    │             │        │     └ '/tmp/ipykernel_174022/1865479429.py'
                       │    │             │        └ [<_ast.ImportFrom object at 0x7f2861fe2c40>, <_ast.Import object at 0x7f282fc527c0>, <_ast.ImportFrom object at 0x7f282fc5237...
                       │    │             └ <_ast.Module object at 0x7f2861fe2ee0>
                       │    └ <function InteractiveShell.run_ast_nodes at 0x7f2881c928b0>
                       └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3361, in run_ast_nodes
    if (await self.run_code(code, result,  async_=asy)):
              │    │        │     │               └ False
              │    │        │     └ <ExecutionResult object at 7f286208acd0, execution_count=9 error_before_exec=None error_in_exec=None info=<ExecutionInfo obje...
              │    │        └ <code object <module> at 0x7f27958d45b0, file "/tmp/ipykernel_174022/1865479429.py", line 14>
              │    └ <function InteractiveShell.run_code at 0x7f2881c929d0>
              └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/IPython/core/interactiveshell.py", line 3441, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
         │         │    │               │    └ {'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, ...
         │         │    │               └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
         │         │    └ <property object at 0x7f2881c85810>
         │         └ <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f28811a6430>
         └ <code object <module> at 0x7f27958d45b0, file "/tmp/ipykernel_174022/1865479429.py", line 14>

> File "/tmp/ipykernel_174022/1865479429.py", line 14, in <module>
    evaluate_result(y_true, y_pred)
    │               │       └ array([1.5, 2.2])
    │               └ array([1, 2, 3])
    └ <function evaluate_result at 0x7f279588f430>

  File "/tmp/ipykernel_174022/1865479429.py", line 9, in evaluate_result
    mean_square_err = mean_squared_error(y_true, y_pred)
                      │                  │       └ array([1.5, 2.2])
                      │                  └ array([1, 2, 3])
                      └ <function mean_squared_error at 0x7f27958bfca0>

  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/utils/validation.py", line 63, in inner_f
    return f(*args, **kwargs)
           │  │       └ {}
           │  └ (array([1, 2, 3]), array([1.5, 2.2]))
           └ <function mean_squared_error at 0x7f27958bfb80>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/metrics/_regression.py", line 335, in mean_squared_error
    y_type, y_true, y_pred, multioutput = _check_reg_targets(
            │       │                     └ <function _check_reg_targets at 0x7f27958b7af0>
            │       └ array([1.5, 2.2])
            └ array([1, 2, 3])
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/metrics/_regression.py", line 88, in _check_reg_targets
    check_consistent_length(y_true, y_pred)
    │                       │       └ array([1.5, 2.2])
    │                       └ array([1, 2, 3])
    └ <function check_consistent_length at 0x7f279676e040>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/utils/validation.py", line 319, in check_consistent_length
    raise ValueError("Found input variables with inconsistent numbers of"

ValueError: Found input variables with inconsistent numbers of samples: [3, 2]
> File "/tmp/ipykernel_174022/1865479429.py", line 14, in <module>
    evaluate_result(y_true, y_pred)
    │               │       └ array([1.5, 2.2])
    │               └ array([1, 2, 3])
    └ <function evaluate_result at 0x7f279588f430>

  File "/tmp/ipykernel_174022/1865479429.py", line 9, in evaluate_result
    mean_square_err = mean_squared_error(y_true, y_pred)
                      │                  │       └ array([1.5, 2.2])
                      │                  └ array([1, 2, 3])
                      └ <function mean_squared_error at 0x7f27958bfca0>

  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/utils/validation.py", line 63, in inner_f
    return f(*args, **kwargs)
           │  │       └ {}
           │  └ (array([1, 2, 3]), array([1.5, 2.2]))
           └ <function mean_squared_error at 0x7f27958bfb80>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/metrics/_regression.py", line 335, in mean_squared_error
    y_type, y_true, y_pred, multioutput = _check_reg_targets(
            │       │                     └ <function _check_reg_targets at 0x7f27958b7af0>
            │       └ array([1.5, 2.2])
            └ array([1, 2, 3])
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/metrics/_regression.py", line 88, in _check_reg_targets
    check_consistent_length(y_true, y_pred)
    │                       │       └ array([1.5, 2.2])
    │                       └ array([1, 2, 3])
    └ <function check_consistent_length at 0x7f279676e040>
  File "/home/khuyen/book/venv/lib/python3.8/site-packages/sklearn/utils/validation.py", line 319, in check_consistent_length
    raise ValueError("Found input variables with inconsistent numbers of"

ValueError: Found input variables with inconsistent numbers of samples: [3, 2]

Link to loguru.

7.4.5. Icrecream: Never use print() to debug again

!pip install icecream

If you use print or log to debug your code, you might be confused about which line of code creates the output, especially when there are many outputs.

You might insert text to make it less confusing, but it is time-consuming.

from icecream import ic

def plus_one(num):
    return num + 1

print('output of plus_on with num = 1:', plus_one(1))
print('output of plus_on with num = 2:', plus_one(2))
output of plus_on with num = 1: 2
output of plus_on with num = 2: 3

Try icecream instead. Icrecream inspects itself and prints both its own arguments and the values of those arguments like below.

ic(plus_one(1))
ic(plus_one(2))
ic| plus_one(1): 2
ic| plus_one(2): 3
3

Output:

ic| plus_one(1): 2
ic| plus_one(2): 3

Link to icecream

Link to my article about icecream

7.4.6. Pyfiglet: Make Large and Unique Letters Out of Ordinary Text in Python

!pip install pyfiglet

If you want to make large and unique letters out of ordinary text using Python, try pyfiglet. Below are some outputs of pyfiglet:

import pyfiglet
from termcolor import colored, cprint

out = pyfiglet.figlet_format("Hello")
print(out)
 _   _      _ _       
| | | | ___| | | ___  
| |_| |/ _ \ | |/ _ \ 
|  _  |  __/ | | (_) |
|_| |_|\___|_|_|\___/ 
                      
out = pyfiglet.figlet_format("Hello", font='slant')
print(out)
    __  __     ____    
   / / / /__  / / /___ 
  / /_/ / _ \/ / / __ \
 / __  /  __/ / / /_/ /
/_/ /_/\___/_/_/\____/ 
                       
cprint(pyfiglet.figlet_format('Hello', font='bell'), 'blue')
 __  __         .    .         
 |   |    ___   |    |     __. 
 |___|  .'   `  |    |   .'   \
 |   |  |----'  |    |   |    |
 /   /  `.___, /\__ /\__  `._.'
                               

This could be used as the welcome message for your Python package 🙂

Link to pyfiglet.

Link to termcolor.

7.4.7. heartrate — Visualize the Execution of a Python Program in Real-Time

!pip install heartrate 

If you want to visualize which lines are executed and how many times they are executed, try heartrate.

You only need to add two lines of code to use heartrate.

import heartrate 
heartrate.trace(browser=True)

def factorial(x):
    if x == 1:
        return 1
    else:
        return (x * factorial(x-1))


if __name__ == "__main__":
    num = 5
    print(f"The factorial of {num} is {factorial(num)}")
 * Serving Flask app 'heartrate.core' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
The factorial of 5 is 120
Opening in existing browser session.

You should see something similar to the below when opening the browser:

image

Link to heartrate.

!pip install typer 

The last thing you want to happen is to have users dig into your code to run it. Is there a way that users can insert arguments into your code on the command line?

That is when Typer comes in handy. Typer allows you to build a command-line interface in a few lines of code based on Python-type hints.

For example, in a file named typer_example, write:

import typer 

def process_data(data: str, version: int):
    print(f'Processing {data},' 
          f'version {version}')

if __name__ == '__main__':
    typer.run(process_data)

On your terminal, type:

python typer_example.py data 1

And you should see an output like below:

!python typer_example.py data 1
Processing data,version 1

Link to Typer.

My full article about Typer.