Session 12

Published

November 7, 2023

Topics Covered
  • Optional Static Typing
  • Organizing Python Projects
  • Handling Dependencies
  • Best Practices

Questions

Q: What is the difference between a docstring and a comment?

def square(x):
    # computes square of a number
    return x*x
help(square)
Help on function square in module __main__:

square(x)
def square(x):
    """computes square of a number
    """
    return x*x
help(square)
Help on function square in module __main__:

square(x)
    computes square of a number

Comments are ignored by Python. Docstrings are attached to the function/module/class.

square.__doc__
'computes square of a number\n    '
sq = square
x = 10
y = 20
x + y
30
x.__add__(y)
30

Refactoring

%%file split.py
import argparse
import os

parser = argparse.ArgumentParser()
parser.add_argument("filename", help="Path to the file")
parser.add_argument("lines", type=int, help="Number of lines per smaller file")
args = parser.parse_args()

input_filename = args.filename
lines_per_file = args.

def get_filename(part_number):
    base_filename = os.path.splitext(input_filename)[0]
    return f'{base_filename}-part{part_number}.txt'

def write_file(filename, lines):
    with open(filename, 'w') as f:
        f.writelines(lines)

def splitlines(lines, n):
    # TODO
    pass

lines = open(input_filename).readlines()
part_number = 1
for chunk in splitlines(lines, lines_per_file):
    filename = get_filename(part_number)
    write_file(filename, chunk)
    part_number += 1



# with open(input_filename, 'r') as input_file:
#     file_count = 0
#     lines = []

#     for line in input_file:
#         lines.append(line)
            
#         if len(lines) >= lines_per_file:
#             file_count += 1
#             output_filename = get_filename(file_count)
#             write_file(output_filename, lines)
#             lines = []

#     if lines:
#         file_count += 1
#         output_filename = get_filename(file_count)
#         write_file(output_filename, lines)
Overwriting split.py

Type Annotations

def square(n: int) -> int:
    return n*n
%%file sq.py
import sys

def square(n: int) -> int:
    return n*n

if __name__ == "__main__":
    n = sys.argv[1]
    print(square(n))
Overwriting sq.py
!python sq.py 5
Traceback (most recent call last):
  File "/home/jupyter-anand/book/sq.py", line 8, in <module>
    print(square(n))
  File "/home/jupyter-anand/book/sq.py", line 4, in square
    return n*n
TypeError: can't multiply sequence by non-int of type 'str'
!mypy sq.py
sq.py:7: error: Argument 1 to "square" has incompatible type "str"; expected "int"  [arg-type]
Found 1 error in 1 file (checked 1 source file)
!pydoc sq
Help on module sq:

NAME
    sq

FUNCTIONS
    square(n: int) -> int

FILE
    /home/jupyter-anand/book/sq.py

Declaring types for functions.

def add(x: float, y: float) -> float:
    return x+y
x: float = 5.0
x
5.0
def squares(numbers: list[int]) -> list[int]:
    return [n*n for n in numbers]
def read_hosts_file(filename: str) -> dict[str, str]:
    """Reads a file in /etc/hosts format and returns
    a dictionary mapping from hostname to ip address.
    """
    pass
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int
def closest_point(p: Point, points: list[Point]) -> Point:
    """Returns the closest point to p from the given points.
    """
%load_problem type-annotations
Problem: Add Type Annotations

Add type annotations to all the functions in the code.py.

You can verify your solution using:

%verify_problem type-annotations


def square(n):
    """Computes square of an integer number.

        >>> square(3)
        9
    """
    return n*n

def vector_add(v1, v2):
    """Adds two vectors.

        >>> vector_add([1, 2, 3, 4], [10, 20, 30, 40])
        [11, 22, 33, 44]
    """
    return [a+b for a, b in zip(v1, v2)]

def mean(numbers):
    """Computes the mean of a list of numbers.

        >>> mean([1.0, 2.0, 3.0, 4.0])
        2.5
    """
    return sum(numbers) / len(numbers)

def group(values, n):
    """
    Takes a list of values and splits into smaller lists of given size.

        >>> group([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
        [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

        >>> group([1, 2, 3, 4, 5, 6, 7, 8, 9], 4)
        [[1, 2, 3, 4], [5, 6, 7, 8], [9]]
    """
    return [values[i:i+n] for i in range(0, len(values), n)]

Structured Pattern Matching

commands = [
    "deposit 50",
    "withdraw 5",
    "show-balance",
    "withdraw 5",
    "show-balance",
]
balance = 0
for cmd in commands:
    match cmd.split():
        case ["deposit", amount]: 
            balance += int(amount)
        case ["withdraw", amount]:
            balance -= int(amount)
        case ["show-balance"]:
            print("balance is", balance)
        case _:
            print("Invalid command")
balance is 45
balance is 40

Example: Exploring github API

url = "https://api.github.com/users/simonw/repos"
import requests
result = requests.get(url).json()
# result[0]
def process_repo(data):
    match data:
        case {
            "full_name": full_name,
            "owner": {
                "login": owner
            },
            "language": lang
        }:
            print(owner, full_name, lang)
for repo in result:
    process_repo(repo)
simonw simonw/.github None
simonw simonw/2018.djangocon.us HTML
simonw simonw/2022.djangocon.us None
simonw simonw/24ways-datasette Jupyter Notebook
simonw simonw/act None
simonw simonw/action-transcription Python
simonw simonw/action-transcription-demo Python
simonw simonw/action-transcription-prototype Python
simonw simonw/advent-of-code-2022-in-rust Rust
simonw simonw/aiosqlite Python
simonw simonw/airtable-export Python
simonw simonw/ajquery.js None
simonw simonw/annotating_proxy Python
simonw simonw/apib2swagger JavaScript
simonw simonw/apio_django None
simonw simonw/archive-program None
simonw simonw/asgi-auth-github Python
simonw simonw/asgi-cors Python
simonw simonw/asgi-csrf Python
simonw simonw/asgi-debug Python
simonw simonw/asgi-gzip Python
simonw simonw/asgi-log-to-sqlite Python
simonw simonw/asgi-proxy-lib Python
simonw simonw/asgi-replay Python
simonw simonw/asgi-scope Python
simonw simonw/asgiref Python
simonw simonw/assume-aws-role-action None
simonw simonw/asyncinject Python
simonw simonw/awesome-asgi None
simonw simonw/awesome-db-tools None

Another example:

@dataclass
class Point:
    x: float
    y: float
    
@dataclass
class Circle:
    center: Point
    radius: float

@dataclass
class Rectangle:
    center: Point
    width: float
    height: float

Shape = Circle | Rectangle
def show(shape: Shape):
    match shape:
        case Circle(Point(x, y), r):
            print(f"Circle with center ({x}, {y}) and radius {r}")
        case Rectangle(Point(x, y), w, h):
            print(f"Rectangle with center ({x}, {y}) and size {w}x{h}")
        case _:
            print("Invalid shape")
show(Circle(Point(10, 10), 50))
Circle with center (10, 10) and radius 50
show(Rectangle(Point(10, 10), 50, 100))
Rectangle with center (10, 10) and size 50x100

Organizing Python Code

https://notes.pipal.in/notes/organizing-python-code/

Feedback

Feedback Form →