Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Abstract

Functions allow programmers to reuse code that is efficiently implemented and well-tested. This notebook not only demonstrates the basic syntax for using and writing functions, but also uses concrete examples to illustrate the importance of code reuse. It highlights the flexibility of functions by showing how they can be applied with different arguments to solve similar problems and how they can be customized to suit specific applications.

import math
import ROOT
The history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.

What is a Function?

A function is a callable object, e.g.:

callable(callable), callable(1)
(True, False)

The function callable is callable because

  • it can be called/invoked with some input arguments/parameters such as 1 enclosed by parentheses (), and then

  • returns some value computed from the input arguments, such as the boolean value False to indicate that the input argument 1 is not callable.

A function can be defined using the def keyword[1]:

[decorators] "def" funcname [type_params] "(" [parameter_list] ")"
                           ["->" expression] ":" suite

E.g., a simple function that prints “Hello, World!” can be defined as follows:

# Function definition
def say_hello():
    print("Hello, World!")
# Function invocation
say_hello()
Hello, World!

To make a function more powerful and solve different problems,

def increment(x):
    return x + 1


increment(3)
4

A function must have a return value. By default None is returned if the function does not have a return statement:

print(f"The return value is {say_hello()}.", )
Hello, World!
The return value is None.

We can also have multiple input arguments.

def length_of_hypotenuse(a, b):
    return (a ** 2 + b ** 2) ** 0.5

length_of_hypotenuse(1, 2), length_of_hypotenuse(3, 4)
(2.23606797749979, 5.0)

The arguments are evaluated from left to right:

print("1st input:", input(), "\n2nd input:", input())
 1
 2
1st input: 1 
2nd input: 2
Solution to Exercise 1 #

A correct is_palindrome(s) function should return s == s[::-1] (using slicing) or compare characters from both ends. Evaluation points:

  • Empty string "" returns True (vacuously a palindrome).

  • Single character "a" returns True.

  • "Racecar" should return True if case-insensitive, or False if case-sensitive. A robust solution uses s.lower() == s[::-1].lower().

  • The AI should handle strings with spaces and punctuation (e.g., "A man a plan" after removing non-alphanumeric characters).

How arguments are passed into a function can be more complicated. To check if you have the correct understanding:

%%pytutor
def increment(x):
    x += 1


x = 3
increment(x)
print(x)  # 4?
Loading...
Solution to Exercise 2 #

The original code fails with NameError: name 'count' is not defined because count is used but never assigned in the function scope. Fix:

def calculate_average(scores):
    total = sum(scores)
    count = len(scores)  # define count as the length of scores
    average = total / count
    return average
  • count should be len(scores), which is the number of elements.

  • Alternatively, the body can simply be return sum(scores) / len(scores).

  • An AI agent should also point out the potential ZeroDivisionError if the input list is empty, and suggest adding a guard like if not scores: return 0 or raising a custom error.

%%cpptutor
void increment(auto &x) {
    x += 1;
}

int main() {
    auto x = 3;
    increment(x);
    return 0;
}
Loading...
%%hermes
Explain briefly the differences in how Python, C, and Java pass arguments to 
functions. In particular, explain
1. call by value,
2. call by reference, and
3. call by object reference.

A fundamental property of functions in Python is that they are first-class citizens, which means that a function can be

  1. assigned to a variable,

  2. passed as an input argument, and

  3. returned by a function.

%%hermes
Are there programming languages that do not treat functions as
first-class citizens? Why?

The following is a simple illustration that defines an identity function that returns the input argument.

def i(x):
    return x

i(i) == i and i.__name__ == 'i'
True

A function can also be defined using the lambda expression, which creates an anonymous function:[2]

(i := lambda x: x)(i) == i and i.__name__ == "<lambda>"
True

A non-trivial example is the following implementation of the boolean values as functions:

%%pytutor
def true(x, y): return x
def false(x, y): return y
def ifthenelse(b, x, y): return b(x, y)
print(ifthenelse(true, "A", "B"))
print(ifthenelse(false, "A", "B"))
Loading...
%%hermes
How to create an infinite loop using purely the lambda expression in Python?

Perhaps you may also be interested in the following:

%%hermes
Why Alonzo Church used lambda for lambda calculus?
Loading...
🌿 Fork/new: main (will create on click)

Code Reuse

Previously, we learned about iteration, where the same piece of code can run multiple times. Function abstraction takes this even further: It allows the same piece of code to be executed with different parameters and at different locations. Code reuse is a good programming practice. If done properly, it makes the code readable and efficient. We will explore these benefits using a concrete example below.

Perfect Square

For instance, the first 10 perfect squares are:

for i in range(10):
    print(i**2)
0
1
4
9
16
25
36
49
64
81

Instead of generating perfect squares, how about writing a function that checks if a number is a perfect square?

def is_perfect_square(n):
    ### BEGIN SOLUTION
    return n == math.isqrt(n) ** 2
    ### END SOLUTION
# test cases
assert is_perfect_square(10**2)
assert not is_perfect_square(10**2 + 1)
assert is_perfect_square(10**10)
assert not is_perfect_square(10**10 + 1)
assert is_perfect_square(10**100)
assert not is_perfect_square(10**100 + 1)

As another demonstration of code reuse, the following solution uses a for loop to implement Definition 1 exactly.

def is_perfect_square(n):
    # checks if n is the square of i for i in the range up to n (exclusive). 
    for i in range(n):
        if i**2 == n:
            return True

If you try running the test on the above solution, it will take an unacceptably long time to run.[4] (Why?)

To properly test the function, we should modify it to fail if it takes too long to run. Implementing such a feature, called timeout, is difficult. Fortunately, we can reuse the code written by others. Run the following cell to

  1. install the package wrapt_timeout_decorator and

  2. import the function timeout from the module wrapt_timeout_decorator.

%pip install wrapt_timeout_decorator >/dev/null 2>&1
Note: you may need to restart the kernel to use updated packages.
from wrapt_timeout_decorator import timeout

def is_perfect_square(n):
    # checks if n is the square of i for i in the range up to n (exclusive). 
    for i in range(n):
        if i**2 == n:
            return True

You will learn how to import a function in a subsequent section (Importing External Modules). For now, let’s see how to use the timeout function:

# enhanced test without timeout
duration = 5


@timeout(duration)  # raise error if the test does not complete in 5 seconds.
def test():
    if not input(f"Run the test with a timeout of {duration}s? [Y/n]").lower() == "n":
        assert is_perfect_square(10**2)
        assert not is_perfect_square(10**2 + 1)
        assert is_perfect_square(10**10)
        assert not is_perfect_square(10**10 + 1)
        assert is_perfect_square(10**100)
        assert not is_perfect_square(10**100 + 1)


test()  # run the test
Run the test with a timeout of 5s? [Y/n] 
---------------------------------------------------------------------------
TimeoutError                              Traceback (most recent call last)
Cell In[3], line 16
     12         assert is_perfect_square(10**100)
     13         assert not is_perfect_square(10**100 + 1)
     14 
     15 
---> 16 test()  # run the test

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:229, in timeout.<locals>.wrapper(wrapped, instance, args, kwargs)
    227     return wrapped(*wrap_helper.args, **wrap_helper.kwargs)
    228 else:
--> 229     return wrapped_with_timeout(wrap_helper)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:236, in wrapped_with_timeout(wrap_helper)
    234 def wrapped_with_timeout(wrap_helper: WrapHelper) -> Any:
    235     if wrap_helper.use_signals:
--> 236         return wrapped_with_timeout_signals(wrap_helper)
    237     else:
    238         return wrapped_with_timeout_process(wrap_helper)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:244, in wrapped_with_timeout_signals(wrap_helper)
    242 try:
    243     wrap_helper.save_old_and_set_new_alarm_handler()
--> 244     return wrap_helper.wrapped(*wrap_helper.args, **wrap_helper.kwargs)
    245 finally:
    246     wrap_helper.restore_old_alarm_handler()

Cell In[3], line 11, in test()
      7     if not input(f"Run the test with a timeout of {duration}s? [Y/n]").lower() == "n":
      8         assert is_perfect_square(10**2)
      9         assert not is_perfect_square(10**2 + 1)
     10         assert is_perfect_square(10**10)
---> 11         assert not is_perfect_square(10**10 + 1)
     12         assert is_perfect_square(10**100)
     13         assert not is_perfect_square(10**100 + 1)

Cell In[2], line 6, in is_perfect_square(n)
      3 def is_perfect_square(n):
      4     # checks if n is the square of i for i in the range up to n (exclusive).
      5     for i in range(n):
----> 6         if i**2 == n:
      7             return True

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrap_helper.py:97, in WrapHelper.new_alarm_handler(self, signum, frame)
     96 def new_alarm_handler(self, signum: signal.Signals, frame: FrameType) -> None:
---> 97     raise_exception(self.timeout_exception, self.exception_message)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrap_helper.py:249, in raise_exception(exception, exception_message)
    247 if not exception:
    248     exception = TimeoutError
--> 249 raise exception(exception_message)

TimeoutError: Function test timed out after 5.0 seconds

To add timeout to the test, we simply

  1. wrapped the test inside a function test, and

  2. decorated it with @timeout(duration).

The function test will then be capable of raising a FunctionDurationError if it takes more than the specified time duration to run.

You will learn how to write a decorator later. It is a powerful way to reuse functions and other objects with additional customizations.

Integer Square Root

To improve the efficiency, consider the following sufficient and necessary condition for perfect squares:

A simple implementation is as follows:

def is_perfect_square(n):
    # check if n is the square of its integer square root
    return n == int(n**0.5) ** 2

assert is_perfect_square(10**10)

Note that it fixed the efficiency issue on the test case with n being 10**10. Let’s run all the test cases:

test()
Run the test with a timeout of 5s? [Y/n] 
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[5], line 1
----> 1 test()

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:229, in timeout.<locals>.wrapper(wrapped, instance, args, kwargs)
    227     return wrapped(*wrap_helper.args, **wrap_helper.kwargs)
    228 else:
--> 229     return wrapped_with_timeout(wrap_helper)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:236, in wrapped_with_timeout(wrap_helper)
    234 def wrapped_with_timeout(wrap_helper: WrapHelper) -> Any:
    235     if wrap_helper.use_signals:
--> 236         return wrapped_with_timeout_signals(wrap_helper)
    237     else:
    238         return wrapped_with_timeout_process(wrap_helper)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:244, in wrapped_with_timeout_signals(wrap_helper)
    242 try:
    243     wrap_helper.save_old_and_set_new_alarm_handler()
--> 244     return wrap_helper.wrapped(*wrap_helper.args, **wrap_helper.kwargs)
    245 finally:
    246     wrap_helper.restore_old_alarm_handler()

Cell In[3], line 12, in test()
      8         assert is_perfect_square(10**2)
      9         assert not is_perfect_square(10**2 + 1)
     10         assert is_perfect_square(10**10)
     11         assert not is_perfect_square(10**10 + 1)
---> 12         assert is_perfect_square(10**100)
     13         assert not is_perfect_square(10**100 + 1)

AssertionError: 

Perhaps we should use math.isclose instead of ==, since floating-point precision loss in n**0.5 for very large n.

def is_perfect_square(n):
    return math.isclose(n, int(n**0.5) ** 2)


assert is_perfect_square(10**100)

Note that it can correctly say 10**100 is a perfect square. Let’s run all the test cases:

test()
Run the test with a timeout of 5s? [Y/n] 
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[7], line 1
----> 1 test()

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:229, in timeout.<locals>.wrapper(wrapped, instance, args, kwargs)
    227     return wrapped(*wrap_helper.args, **wrap_helper.kwargs)
    228 else:
--> 229     return wrapped_with_timeout(wrap_helper)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:236, in wrapped_with_timeout(wrap_helper)
    234 def wrapped_with_timeout(wrap_helper: WrapHelper) -> Any:
    235     if wrap_helper.use_signals:
--> 236         return wrapped_with_timeout_signals(wrap_helper)
    237     else:
    238         return wrapped_with_timeout_process(wrap_helper)

File /opt/conda/lib/python3.14/site-packages/wrapt_timeout_decorator/wrapt_timeout_decorator.py:244, in wrapped_with_timeout_signals(wrap_helper)
    242 try:
    243     wrap_helper.save_old_and_set_new_alarm_handler()
--> 244     return wrap_helper.wrapped(*wrap_helper.args, **wrap_helper.kwargs)
    245 finally:
    246     wrap_helper.restore_old_alarm_handler()

Cell In[3], line 11, in test()
      7     if not input(f"Run the test with a timeout of {duration}s? [Y/n]").lower() == "n":
      8         assert is_perfect_square(10**2)
      9         assert not is_perfect_square(10**2 + 1)
     10         assert is_perfect_square(10**10)
---> 11         assert not is_perfect_square(10**10 + 1)
     12         assert is_perfect_square(10**100)
     13         assert not is_perfect_square(10**100 + 1)

AssertionError: 

How to fix the issue? The culprit is that the computation for integer square root is not exact:

x = 10**100
int((x) ** 0.5)
100000000000000007629769841091887003294964970946560

There are better ways to compute integer square root. Binary search is a relatively easy one to try first, although it is not the best choice.

%%hermes
Explain very briefly how integer square root can be implemented in Python using
binary search.

But there is a much easier way to have a better implementation: Code reuse! Try math.isqrt for Exercise 3 and check that you can pass all the test cases instantly.

x = 10**100
math.isqrt(x), int((x) ** 0.5)
(100000000000000000000000000000000000000000000000000, 100000000000000007629769841091887003294964970946560)
%%hermes
Explain briefly in two paragraph how isqrt is implemented as an 
adaptive-precision pure-integer version of Newton's iteration.

While you may want to write self-contained codes that do not rely on external libraries, code reuse advocates would recommend you to use standard libraries as much as possible. Why?

Indeed, the math library provides functions that it does not implement:

CPython implementation detail: The math module consists mostly of thin wrappers around the platform C math library functions. - pydoc last paragraph

E.g., see the source code wrapper for log.[5]:

%%hermes
When working on a programming assignment, should I write all the code myself,
or is it acceptable to use standard libraries?

Modules

To facilitate code reuse, all Python codes are organized into libraries called modules. E.g., you can list all available modules using pip list:

%pip list
Package                         Version
------------------------------- ------------
accelerate                      1.14.0
agent-client-protocol           0.9.0
aiofile                         3.12.3
aiohappyeyeballs                2.7.1
aiohttp                         3.14.3
aiohttp_socks                   0.11.0
aiosignal                       1.4.0
aiosqlite                       0.22.1
alembic                         1.20.0
altair                          6.2.2
annotated-doc                   0.0.5
annotated-types                 0.8.0
anyio                           4.15.1
argon2-cffi                     25.1.0
argon2-cffi-bindings            26.1.0
arrow                           1.4.0
ast_serialize                   0.11.2
astroid                         4.0.4
asttokens                       3.0.2
astyle                          3.6.18
async-lru                       2.3.0
async-timeout                   5.0.1
asyncpg                         0.31.0
attrs                           26.1.0
audioop-lts                     0.2.2
Authlib                         1.8.0
autopep8                        2.0.4
av                              18.1.0
babel                           2.18.0
backports.tarfile               1.2.0
base58                          2.1.1
beartype                        0.22.9
beautifulsoup4                  4.15.0
bitsandbytes                    0.50.2
black                           25.1.0
bleach                          6.4.0
blessed                         1.49.0
blinker                         1.9.0
bokeh                           3.10.0
Bottleneck                      1.6.0
Brotli                          1.2.0
build                           1.6.0
CacheControl                    0.14.4
cached-property                 2.0.1
cachetools                      7.2.0
caio                            0.12.4
calysto-scheme                  1.4.8
certifi                         2026.5.20
certipy                         0.2.3
cffi                            2.1.1
charset-normalizer              3.5.1
cleo                            2.1.0
cli_exit_tools                  1.2.7
click                           8.5.0
cloudpickle                     3.1.2
cloup                           4.0.0
colorama                        0.4.6
comm                            0.2.3
contourpy                       1.4.0
cppmanlite                      0.1.6
crashtest                       0.4.1
croniter                        6.0.0
cryptography                    50.0.0
cuda-bindings                   13.3.1
cuda-pathfinder                 1.6.0
cuda-toolkit                    13.0.2
cycler                          0.12.1
cyclopts                        4.25.3
Cython                          3.3.0
datasets                        4.8.5
debugpy                         1.8.22
decorator                       5.3.1
defusedxml                      0.7.1
dill                            0.4.1
distlib                         0.4.3
distro                          1.9.0
divewidgets                     0.1.5
dnspython                       2.8.0
docstring_parser                0.18.0
docstring-to-markdown           0.17
docutils                        0.23
doit                            0.37.0
dsh-openai-shim                 0.2.2
duckdb                          1.5.5
dulwich                         1.2.10
email-validator                 2.3.0
exceptiongroup                  1.3.1
executing                       2.2.1
fastapi                         0.141.1
fastjsonschema                  2.22.2
fastmcp                         3.4.7
fastmcp-slim                    3.4.7
filelock                        3.32.3
findpython                      0.8.0
fire                            0.7.1
firecrawl-anydoc                0.2.4
flake8                          7.1.2
fonttools                       4.65.0
formulaic                       1.2.2
fqdn                            1.5.1
frozenlist                      1.8.0
fsspec                          2026.2.0
genai-prices                    0.1.7
gitdb                           4.0.12
GitPython                       3.1.62
glcontext                       3.0.0
gmpy2                           2.3.1
gpustat                         1.1.1
greenlet                        3.5.6
griffelib                       2.3.0
h11                             0.16.0
h2                              4.4.1
h5py                            3.16.0
hermes-agent                    0.21.3
hf-xet                          1.6.0
hpack                           4.2.0
httpcore                        1.0.9
httpcore2                       2.13.0
httptools                       0.8.0
httpx                           0.28.1
httpx-sse                       0.4.3
httpx2                          2.13.0
huggingface_hub                 1.32.0
hyperframe                      6.1.0
idna                            3.19
importlib_metadata              9.0.1
importlib_resources             7.1.0
installer                       1.0.1
interface_meta                  1.3.0
ipykernel                       7.3.0
ipympl                          0.10.0
ipyparallel                     9.2.0
ipython                         9.17.1
ipython_pygments_lexers         1.1.1
ipywidgets                      8.1.9
isoduration                     20.11.0
isort                           9.0.1
isosurfaces                     0.1.2
itsdangerous                    2.2.0
jaraco.classes                  3.4.0
jaraco.context                  6.1.2
jaraco.functools                4.6.0
jdc                             0.0.9
jedi                            0.19.2
jeepney                         0.9.0
Jinja2                          3.1.6
jinxed                          2.1.0
jiter                           0.17.0
joblib                          1.6.0
joserfc                         1.7.5
json5                           0.15.0
jsonpatch                       1.33
jsonpointer                     3.1.1
jsonref                         1.1.0
jsonschema                      4.26.0
jsonschema-path                 0.5.0
jsonschema-specifications       2025.9.1
jupyter_ai                      3.1.3
jupyter_ai_acp_client           0.2.1
jupyter_ai_chat_commands        0.0.4
jupyter-ai-hermes               0.1.7
jupyter-ai-hermes-magics        0.5.2
jupyter_ai_persona_manager      0.1.3
jupyter_ai_router               0.0.7
jupyter_ai_tools                0.6.1
jupyter-archive                 3.5.1
jupyter_builder                 1.2.3
jupyter_client                  8.10.0
jupyter-collaboration           5.0.3
jupyter-collaboration-ui        3.0.3
jupyter_core                    5.9.1
jupyter-cpp-tutor               0.2.3
jupyter-docprovider             3.0.3
jupyter-dsh-proxy               0.2.0
jupyter-events                  0.12.1
jupyter-hermes-proxy            0.3.0
jupyter-lsp                     2.3.1
jupyter-marimo-proxy            0.0.4
jupyter-python-tutor            0.3.3
jupyter-remote-desktop-proxy    3.0.1
jupyter-resource-usage          1.2.1
jupyter_server                  2.21.1
jupyter_server_documents        0.3.3
jupyter_server_fileid           0.9.3
jupyter_server_mcp              0.3.0
jupyter_server_proxy            4.5.0
jupyter_server_terminals        0.5.4
jupyter-server-ydoc             3.0.3
jupyter-storage-usage           0.1.0
jupyter_vscode_proxy            0.0.0
jupyter-www-proxy               0.1.2
jupyter-ydoc                    4.1.1
jupyterhub                      5.5.2
jupyterlab                      4.6.3
jupyterlab_chat                 0.23.2
jupyterlab_code_formatter       3.0.3
jupyterlab_commands_toolkit     0.1.6
jupyterlab_eventlistener        0.4.0
jupyterlab-execute-time         3.3.0
jupyterlab_git                  0.54.1
jupyterlab-git-core             0.54.1
jupyterlab-lsp                  5.3.0
jupyterlab_myst                 2.7.0
jupyterlab_notebook_awareness   0.2.0
jupyterlab-pwa                  0.2.0
jupyterlab_pygments             0.3.0
jupyterlab_server               2.28.1
jupyterlab_theme_solarized_dark 3.0.1
jupyterlab_widgets              3.0.17
jupyterlite                     0.8.3
jupyterlite-core                0.8.3
jupyterlite-sphinx              0.22.1
jupytext                        1.18.1
jwcrypto                        1.6.1
kaleido                         0.2.1
keyring                         25.7.0
kiwisolver                      1.5.1
langchain                       1.3.18
langchain-core                  1.6.3
langchain-openai                1.4.3
langchain-protocol              0.0.19
langgraph                       1.2.11
langgraph-checkpoint            4.2.0
langgraph-prebuilt              1.1.0
langgraph-sdk                   0.4.4
langsmith                       0.13.0
lark                            1.3.1
lib_detect_testenv              3.0.4
librt                           0.15.0
llvmlite                        0.49.0
logfire-api                     5.1.0
loro                            1.16.2
Mako                            1.4.1
manim                           0.20.1
ManimPango                      0.6.1
mapbox_earcut                   2.1.0
marimo                          0.23.16
Markdown                        3.10.2
markdown-it-py                  4.2.0
MarkupSafe                      3.0.3
matplotlib                      3.11.2
matplotlib-inline               0.2.2
mautrix                         0.21.1
mccabe                          0.7.0
mcp                             1.30.0
mdit-py-plugins                 0.6.1
mdurl                           0.1.2
metakernel                      1.0.7
mistune                         3.3.4
moderngl                        5.12.0
moderngl-window                 3.1.1
more-itertools                  11.1.0
mpmath                          1.3.0
msgpack                         1.2.2
msgspec                         0.21.1
multidict                       6.7.1
multiprocess                    0.70.19
munkres                         1.1.4
mypy                            2.3.1
mypy_extensions                 1.1.0
narwhals                        2.26.0
nbclient                        0.11.0
nbconvert                       7.17.1
nbdime                          4.0.4
nbformat                        5.11.1
nbgitpuller                     1.3.0
nbgrader                        0.9.5
nemo-relay                      0.8.4
nest-asyncio2                   1.7.2
networkx                        3.6.1
notebook                        7.6.2
notebook_shim                   0.2.4
numba                           0.67.0
numexpr                         2.14.2
numpy                           2.5.2
nvidia-cublas                   13.1.0.3
nvidia-cuda-cupti               13.0.85
nvidia-cuda-nvrtc               13.0.88
nvidia-cuda-runtime             13.0.96
nvidia-cudnn-cu13               9.19.0.56
nvidia-cufft                    12.0.0.61
nvidia-cufile                   1.15.1.6
nvidia-curand                   10.4.0.35
nvidia-cusolver                 12.0.4.66
nvidia-cusparse                 12.6.3.3
nvidia-cusparselt-cu13          0.8.0
nvidia-ml-py                    13.590.48
nvidia-nccl-cu13                2.28.9
nvidia-nvjitlink                13.0.88
nvidia-nvshmem-cu13             3.4.5
nvidia-nvtx                     13.0.85
nvitop                          1.7.1
oauthlib                        3.3.1
openai                          3.16.2
openapi-pydantic                0.5.1
opentelemetry-api               1.44.0
optimum                         2.2.0
orjson                          3.12.0
ormsgpack                       1.12.2
overrides                       7.7.0
packaging                       26.0
pamela                          1.2.0
pandas                          2.3.3
pandocfilters                   1.5.0
parso                           0.8.7
pathable                        0.6.0
pathspec                        1.1.1
patsy                           1.0.3
pbs-installer                   2026.9.1
pexpect                         4.9.0
pillow                          12.3.0
pillow_heif                     1.7.0
pip                             26.2.1
pkginfo                         1.12.1.2
platformdirs                    4.11.8
plotly                          6.9.0
pluggy                          1.6.0
poetry                          2.4.3
poetry-core                     2.4.0
polars                          1.43.2
polars-runtime-32               1.43.2
portalocker                     3.2.0
prometheus_client               0.26.0
prompt_toolkit                  3.0.52
propcache                       0.5.2
psutil                          7.2.2
ptyprocess                      0.7.0
pure_eval                       0.2.4
py-cpuinfo                      9.0.0
py-key-value-aio                0.4.6
pyarrow                         25.0.0
pycairo                         1.29.1
pycodestyle                     2.12.1
pycparser                       3.0
pycrdt                          0.14.5
pycrdt-store                    0.1.5
pycrdt-websocket                0.16.4
pycryptodome                    3.23.0
pycurl                          7.47.0
pydantic                        2.13.4
pydantic-ai-slim                2.46.0
pydantic_core                   2.46.4
pydantic-graph                  2.46.0
pydantic-settings               2.15.0
pydocstyle                      6.3.0
pydub                           0.25.1
pyflakes                        3.2.0
pyglet                          2.1.15
pyglm                           2.8.3
Pygments                        2.21.0
pyHepMC3                        3.3.1
pyHepMC3.search                 3.3.1
PyJWT                           2.13.0
pylint                          4.0.8
pymdown-extensions              11.0.2
pyparsing                       3.3.2
pypdf                           5.9.0
pyperclip                       1.11.0
pyproject_hooks                 1.2.0
pyserial                        3.5
PySocks                         1.7.1
python-dateutil                 2.9.0.post0
python-discovery                1.6.1
python-dotenv                   1.2.2
python-json-logger              4.2.0
python-lsp-jsonrpc              1.1.2
python-lsp-server               1.15.0
python-multipart                0.0.32
python-olm                      3.2.16
python-socks                    2.8.2
pytoolconfig                    1.3.1
pytz                            2026.3.post1
PyYAML                          6.0.3
pyzmq                           27.2.0
RapidFuzz                       3.14.6
redis                           8.1.0
referencing                     0.37.0
regex                           2026.9.10
requests                        2.33.0
requests-toolbelt               1.0.0
rfc3339_validator               0.1.4
rfc3986-validator               0.1.1
rfc3987-syntax                  1.1.0
rich                            14.3.3
rich-click                      1.9.9
rich-rst                        2.1.0
rope                            1.14.0
rpds-py                         2026.6.3
ruamel.yaml                     0.18.17
ruamel.yaml.clib                0.2.15
ruff                            0.16.8
safetensors                     0.8.0
scikit-learn                    1.9.1
scipy                           1.18.1
screeninfo                      0.8.1
seaborn                         0.13.2
SecretStorage                   3.5.0
Send2Trash                      2.1.0
sentence-transformers           5.6.1
setuptools                      78.1.0
shellingham                     1.5.4
simpervisor                     1.0.0
six                             1.17.0
skia-pathops                    0.9.2
smmap                           5.0.3
sniffio                         1.3.1
snowballstemmer                 3.1.1
socksio                         1.0.0
soupsieve                       2.9.2
SQLAlchemy                      2.0.54
sqlglot                         28.10.1
sqlite-anyio                    0.2.10
srt                             3.5.3
sse-starlette                   3.4.11
stack_data                      0.6.3
starlette                       1.6.0
statsmodels                     0.15.0
svgelements                     1.9.6
sympy                           1.14.0
tables                          3.11.1
tenacity                        9.1.4
termcolor                       3.3.0
terminado                       0.18.1
theme-darcula                   4.0.0
thonny                          5.0.0
threadpoolctl                   3.7.0
tiktoken                        0.13.0
tinycss2                        1.4.0
tokenizers                      0.22.2
tomli                           2.4.1
tomlkit                         0.15.1
torch                           2.11.0+cu130
torchaudio                      2.11.0+cu130
torchvision                     0.26.0+cu130
tornado                         6.5.8
tqdm                            4.70.1
traitlets                       5.16.1
transformers                    5.14.1
triton                          3.6.0
trove-classifiers               2026.6.1.19
truststore                      0.10.4
typer                           0.27.2
typing_extensions               4.16.0
typing-inspection               0.4.4
typing_utils                    0.1.0
tzdata                          2026.3
ujson                           6.0.0
uncalled-for                    0.4.0
unpaddedbase64                  2.1.0
uri-template                    1.3.0
urllib3                         2.8.0
uuid_utils                      0.17.1
uv                              0.12.17
uvicorn                         0.53.0
uvloop                          0.22.1
virtualenv                      21.7.16
watchdog                        6.0.0
watchfiles                      1.2.0
wcwidth                         0.8.4
webcolors                       25.10.0
webencodings                    0.5.1
websocket-client                1.9.2
websockets                      15.0.1
websockify                      0.13.0
whatthepatch                    1.0.7
wheel                           0.48.0
widgetsnbextension              4.0.16
wrapt                           2.4.0
wrapt_timeout_decorator         1.5.1
xlrd                            2.0.2
xxhash                          4.0.1
xyzservices                     2026.9.1
yapf                            0.43.0
yarl                            1.24.5
yasi                            2.1.2
zipp                            4.1.0
zstandard                       0.25.0
Note: you may need to restart the kernel to use updated packages.

Python searches for packages using the search path:

import sys
sys.path
['/opt/conda/lib/python314.zip', '/opt/conda/lib/python3.14', '/opt/conda/lib/python3.14/lib-dynload', '', '/home/jovyan/.local/lib/python3.14/site-packages', '/opt/conda/lib/python3.14/site-packages']

For instance, to show the location of a package, say jupyter-ai-hermes, run:

%pip show jupyter-ai-hermes
Name: jupyter-ai-hermes
Version: 0.1.7
Summary: Hermes Agent ACP persona for Jupyter AI — includes notebook context injection via MCP bridge
Home-page: https://github.com/dive4dec/jupyter-ai-hermes
Author: 
Author-email: 
License: MIT
Location: /opt/conda/lib/python3.14/site-packages
Requires: jupyter-ai-acp-client, jupyter-ai-persona-manager, jupyter-ai-tools, mcp
Required-by: 
Note: you may need to restart the kernel to use updated packages.

You can install additional packages using the commands pip install for packages on PyPI.

The following is an example using pip to install a package cowsay:

if not input('Execute? [Y/n]').lower()=='n': 
    %pip install cowsay
Execute? [Y/n] 
Collecting cowsay
  Downloading cowsay-6.1-py3-none-any.whl.metadata (5.6 kB)
Downloading cowsay-6.1-py3-none-any.whl (25 kB)
Installing collected packages: cowsay
Successfully installed cowsay-6.1
Note: you may need to restart the kernel to use updated packages.
import cowsay

cowsay.cow("I am a pip installed package ((((((...ip)ip)ip)ip)ip)ip)!")
  _________________________________________________
 /                                                 \
| I am a pip installed package ((((((...ip)ip)ip)ip |
| )ip)ip)!                                          |
 \                                                 /
  =================================================
                                                 \
                                                  \
                                                    ^__^
                                                    (oo)\_______
                                                    (__)\       )\/\
                                                        ||----w |
                                                        ||     ||
%%hermes
Explain how to publish a python package to PyPI.

Builtins Module

In Python, every function must come from a module, including the built-in functions:

__builtins__.print(f"`{print.__name__}` is from the {print.__module__} module.")
`print` is from the builtins module.

The buildins are automatically imported as __builtins__ (and also __builtin__) along with all the functions and objects it provides because they are commonly used by programmers.

We can use the built-in function dir (directory) to list all built-in objects available.

print(dir(__builtins__))

For instance, there is a built-in function help for showing the docstring (documentation string) of functions or other objects.

help(help)  # can also show the docstring of help itself
help(__builtins__)  # can also show the docstring of a module
print(dir())
Solution to Exercise 4

As summarized by the first line of the docstring of dir:

If called without an argument, return the names in the current scope.

Importing External Modules

For other available modules, we can use the import statement to import multiple functions or objects into the program global frame.

from math import ceil, log10

x = 1234
print("Number of digits of x:", ceil(log10(x)))
Number of digits of x: 4

The above imports both the functions log10 and ceil from math to compute the number ⌈log⁡10(x)⌉\lceil \log_{10}(x)\rceil of digits of a strictly positive integer xx.

One can also import all functions from a library:

from math import *  # import all except names starting with an underscore

print("{:.2f}, {:.2f}, {:.2f}".format(sin(pi / 6), cos(pi / 3), tan(pi / 4)))
0.50, 0.50, 1.00

The above uses the wildcard * to import (nearly) all the functions/variables provided in math.

%%pytutor
print("{}".format(pow(-1, 2)))
print("{:.2f}".format(pow(-1, 1 / 2)))
from math import *

print("{}".format(pow(-1, 2)))
print("{:.2f}".format(pow(-1, 1 / 2)))
Loading...

To avoid name collisions, it is a good practice to use the full name (fully-qualified name) such as math.pow prefixed with the module.

import math

pow = __builtins__.pow  # Why?
print("{:.2f}, {:.2f}".format(math.pow(-1, 2), pow(-1, 1 / 2)))
1.00, 0.00+1.00j

Using the full name can be problematic if the name of a module is very long. There can even be a hierarchical structure. E.g., to plot a sequence using pyplot module from matplotlib package:

%matplotlib widget
import matplotlib.pyplot

matplotlib.pyplot.stem([4, 3, 2, 1])
matplotlib.pyplot.ylabel(r"$x_n$")
matplotlib.pyplot.xlabel(r"$n$")
matplotlib.pyplot.title("A sequence of numbers")
matplotlib.pyplot.show()
Loading...

In Python, modules can be structured into packages, which are themselves modules that can be imported. It is common to rename matplotlib.pyplot as plt:

import matplotlib.pyplot as plt

plt.stem([4, 3, 2, 1])
plt.ylabel(r"$x_n$")
plt.xlabel(r"$n$")
plt.title("A sequence of numbers")
plt.show()
Loading...

We can also rename a function as we import it to avoid name collision:

from math import pow as fpow

fpow(2, 2), pow(2, 2)
(4.0, 4.0)
import math as m

for m in range(5):
    m.pow(m, 2)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[29], line 4
      1 import math as m
      2 
      3 for m in range(5):
----> 4     m.pow(m, 2)

AttributeError: 'int' object has no attribute 'pow'
Solution to Exercise 5

There is a name collision: m is assigned to an integer in the for loop and so it is no longer the module math when calling m.pow.

Documentation

Understanding how to properly document a function is crucial for maintaining clear and efficient code. It also allows others to use the code properly to avoid bugs. How should one go about documenting a function effectively? As an example:

# Author: John Doe
# Last modified: 2020-09-14
def increment(x):
    """Increment by 1.

    A simple demo of
    - parameter passing,
    - return statement, and
    - function documentation."""
    return x + 1  # + operation is used and may fail for 'str'

The help command shows the docstring we write

  • at the beginning of the function body

  • delimited using triple single/double quotes.

help(increment)
Help on function increment in module __main__:

increment(x)
    Increment by 1.

    A simple demo of
    - parameter passing,
    - return statement, and
    - function documentation.

The docstring should contain the usage guide, i.e., information for new users to call the function properly. See Python style guide (PEP 257) for

We can also annotate the function with type hints to indicate the types of the arguments and return value.

# Author: John Doe
# Last modified: 2020-09-14
def increment(x: float) -> float:
    """Increment by 1.

    A simple demo of
    - parameter passing,
    - return statement, and
    - function documentation."""
    return x + 1  # + operation is used and may fail for 'str'


help(increment)
Help on function increment in module __main__:

increment(x: float) -> float
    Increment by 1.

    A simple demo of
    - parameter passing,
    - return statement, and
    - function documentation.

Annotations, if done right, can make the code easier to understand. However, annotations are not enforced by the Python interpreter.[7]

def increment_user_input():
    return increment(input())  # does not raise error even though input returns str

Does calling the function lead to any error:

increment_user_input()

The types can also be described in the docstring following the Numpy or Google style.

# Author: John Doe
# Last modified: 2020-09-14
def increment(x: float) -> float:
    """Increment by 1.

    A simple demo of
    - parameter passing,
    - return statement, and
    - function documentation.

    Parameters
    ----------
    x: float
        Value to be incremented.

    Returns
    -------
    float:
        Value of x incremented by 1.
    """
    return x + 1  # + operation is used and may fail for 'str'


help(increment)
Help on function increment in module __main__:

increment(x: float) -> float
    Increment by 1.

    A simple demo of
    - parameter passing,
    - return statement, and
    - function documentation.

    Parameters
    ----------
    x: float
        Value to be incremented.

    Returns
    -------
    float:
        Value of x incremented by 1.

Can GenAI help us write documentation?

%%hermes
Add the document string to the following function:
--
def increment(x: float) -> float:
    return x + 1  # + operation is used and may fail for 'str'

To facilitate the generation of documentation, there are tools such as:

  • sphinx that can compile the docstrings automatically into an API reference.

  • JupyterLite that runs JupyterLab entirely in the browser, so that interactive notebooks can be embedded in documentation and served as a static website.

  • nbdev which releases a package and compiles the documentation from Jupyter notebooks, hence providing a literate programming experience.

%%hermes
Explain how to write documentation using sphinx and create a demo using jupyterlite.
Footnotes
  1. The tutorial on def may be easier to read than the formal definition.

  2. Note that the colon in lambda ...: cannot be followed by line break because it expects an expression rather than a suite.

  3. The video is created using the Python package manim, which is also accessible in Jupyter notebooks using the cell magic %%manim. See the 3Blue1Brown for more videos created with manim.

  4. Use the keyboard interrupt (■) to stop the execution.

  5. An efficient implementation often uses the CORDIC algorithm.

  6. See the documentation for more details on managing conda environment.

  7. Type checking may be enforced by a supporting editor or packages such as pydantic and mypy.