From 7d053ce7cf3053e24bd8dfc3180a0bab8f005af9 Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Thu, 5 Jun 2025 16:13:43 +0200 Subject: [PATCH 1/2] Sample code for the article on namedtuple --- python-namedtuple/README.md | 3 ++ python-namedtuple/employees.csv | 6 +++ python-namedtuple/employees.py | 9 ++++ .../namedtuple_dataclass_memory.py | 21 ++++++++ .../namedtuple_dataclass_time.py | 36 +++++++++++++ python-namedtuple/namedtuple_dict_memory.py | 12 +++++ python-namedtuple/namedtuple_dict_time.py | 39 ++++++++++++++ python-namedtuple/performance.py | 51 +++++++++++++++++++ python-namedtuple/person_dataclass.py | 13 +++++ python-namedtuple/subclass.py | 26 ++++++++++ python-namedtuple/tuple_namedtuple_time.py | 32 ++++++++++++ python-namedtuple/typed_namedtuple_memory.py | 19 +++++++ python-namedtuple/typed_namedtuple_time.py | 40 +++++++++++++++ 13 files changed, 307 insertions(+) create mode 100644 python-namedtuple/README.md create mode 100644 python-namedtuple/employees.csv create mode 100644 python-namedtuple/employees.py create mode 100644 python-namedtuple/namedtuple_dataclass_memory.py create mode 100644 python-namedtuple/namedtuple_dataclass_time.py create mode 100644 python-namedtuple/namedtuple_dict_memory.py create mode 100644 python-namedtuple/namedtuple_dict_time.py create mode 100644 python-namedtuple/performance.py create mode 100644 python-namedtuple/person_dataclass.py create mode 100644 python-namedtuple/subclass.py create mode 100644 python-namedtuple/tuple_namedtuple_time.py create mode 100644 python-namedtuple/typed_namedtuple_memory.py create mode 100644 python-namedtuple/typed_namedtuple_time.py diff --git a/python-namedtuple/README.md b/python-namedtuple/README.md new file mode 100644 index 0000000000..5f71bc3218 --- /dev/null +++ b/python-namedtuple/README.md @@ -0,0 +1,3 @@ +# Write Pythonic and Clean Code With namedtuple + +This folder provides the code examples for the Real Python tutorial [Write Pythonic and Clean Code With namedtuple](https://realpython.com/python-namedtuple/). diff --git a/python-namedtuple/employees.csv b/python-namedtuple/employees.csv new file mode 100644 index 0000000000..84d411f13a --- /dev/null +++ b/python-namedtuple/employees.csv @@ -0,0 +1,6 @@ +name,job,email +"Linda","Technical Lead","linda@example.com" +"Joe","Senior Web Developer","joe@example.com" +"Lara","Project Manager","lara@example.com" +"David","Data Analyst","david@example.com" +"Jane","Senior Python Developer","jane@example.com" diff --git a/python-namedtuple/employees.py b/python-namedtuple/employees.py new file mode 100644 index 0000000000..c757c25425 --- /dev/null +++ b/python-namedtuple/employees.py @@ -0,0 +1,9 @@ +import csv +from collections import namedtuple + +with open("employees.csv", "r", encoding="utf-8") as csv_file: + reader = csv.reader(csv_file) + Employee = namedtuple("Employee", next(reader), rename=True) + for row in reader: + employee = Employee(*row) + print(employee.name, employee.job, employee.email) diff --git a/python-namedtuple/namedtuple_dataclass_memory.py b/python-namedtuple/namedtuple_dataclass_memory.py new file mode 100644 index 0000000000..e5b98df3f8 --- /dev/null +++ b/python-namedtuple/namedtuple_dataclass_memory.py @@ -0,0 +1,21 @@ +from collections import namedtuple +from dataclasses import dataclass + +from pympler import asizeof + +PointNamedTuple = namedtuple("PointNamedTuple", "x y z") + + +@dataclass +class PointDataClass: + x: int + y: int + z: int + + +namedtuple_memory = asizeof.asizeof(PointNamedTuple(x=1, y=2, z=3)) +dataclass_memory = asizeof.asizeof(PointDataClass(x=1, y=2, z=3)) +gain = 100 - namedtuple_memory / dataclass_memory * 100 + +print(f"namedtuple: {namedtuple_memory} bytes ({gain:.2f}% lower)") +print(f"data class: {dataclass_memory} bytes") diff --git a/python-namedtuple/namedtuple_dataclass_time.py b/python-namedtuple/namedtuple_dataclass_time.py new file mode 100644 index 0000000000..f7bbed64c8 --- /dev/null +++ b/python-namedtuple/namedtuple_dataclass_time.py @@ -0,0 +1,36 @@ +from collections import namedtuple +from dataclasses import dataclass +from time import perf_counter + + +def average_time(structure, test_func): + time_measurements = [] + for _ in range(1_000_000): + start = perf_counter() + test_func(structure) + end = perf_counter() + time_measurements.append(end - start) + return sum(time_measurements) / len(time_measurements) * int(1e9) + + +def time_structure(structure): + structure.x + structure.y + structure.z + + +PointNamedTuple = namedtuple("PointNamedTuple", "x y z", defaults=[3]) + + +@dataclass +class PointDataClass: + x: int + y: int + z: int + + +namedtuple_time = average_time(PointNamedTuple(x=1, y=2, z=3), time_structure) +dataclass_time = average_time(PointDataClass(x=1, y=2, z=3), time_structure) + +print(f"namedtuple: {namedtuple_time:.2f} ns") +print(f"data class: {dataclass_time:.2f} ns") diff --git a/python-namedtuple/namedtuple_dict_memory.py b/python-namedtuple/namedtuple_dict_memory.py new file mode 100644 index 0000000000..3a7c74ef44 --- /dev/null +++ b/python-namedtuple/namedtuple_dict_memory.py @@ -0,0 +1,12 @@ +from collections import namedtuple +from pympler import asizeof + +Point = namedtuple("Point", "x y z") +point = Point(1, 2, 3) + +namedtuple_size = asizeof.asizeof(point) +dict_size = asizeof.asizeof(point._asdict()) +gain = 100 - namedtuple_size / dict_size * 100 + +print(f"namedtuple: {namedtuple_size} bytes ({gain:.2f}% smaller)") +print(f"dict: {dict_size} bytes") diff --git a/python-namedtuple/namedtuple_dict_time.py b/python-namedtuple/namedtuple_dict_time.py new file mode 100644 index 0000000000..388c1b3777 --- /dev/null +++ b/python-namedtuple/namedtuple_dict_time.py @@ -0,0 +1,39 @@ +from collections import namedtuple +from time import perf_counter + + +def average_time(structure, test_func): + time_measurements = [] + for _ in range(1_000_000): + start = perf_counter() + test_func(structure) + end = perf_counter() + time_measurements.append(end - start) + return sum(time_measurements) / len(time_measurements) * int(1e9) + + +def time_dict(dictionary): + "x" in dictionary + "missing_key" in dictionary + 2 in dictionary.values() + "missing_value" in dictionary.values() + dictionary["y"] + + +def time_namedtuple(named_tuple): + "x" in named_tuple._fields + "missing_field" in named_tuple._fields + 2 in named_tuple + "missing_value" in named_tuple + named_tuple.y + + +Point = namedtuple("Point", "x y z") +point = Point(x=1, y=2, z=3) + +namedtuple_time = average_time(point, time_namedtuple) +dict_time = average_time(point._asdict(), time_dict) +gain = dict_time / namedtuple_time + +print(f"namedtuple: {namedtuple_time:.2f} ns ({gain:.2f}x faster)") +print(f"dict: {dict_time:.2f} ns") diff --git a/python-namedtuple/performance.py b/python-namedtuple/performance.py new file mode 100644 index 0000000000..6ffcaa113c --- /dev/null +++ b/python-namedtuple/performance.py @@ -0,0 +1,51 @@ +from collections import namedtuple + +STest = namedtuple("TEST", "a b c") +a = STest(a=1, b=2, c=3) + + +class Test(object): + __slots__ = ["a", "b", "c"] + + def __init__(self) -> None: + self.a = 1 + self.b = 2 + self.c = 3 + + +b = Test() + +c = {"a": 1, "b": 2, "c": 3} + +d = (1, 2, 3) +e = [1, 2, 3] +f = (1, 2, 3) +g = [1, 2, 3] +key = 2 + +if __name__ == "__main__": + from timeit import timeit + + print("Named tuple with a, b, c:") + print(timeit("z = a.c", "from __main__ import a")) + + print("Named tuple, using index:") + print(timeit("z = a[2]", "from __main__ import a")) + + print("Class using __slots__, with a, b, c:") + print(timeit("z = b.c", "from __main__ import b")) + + print("Dictionary with keys a, b, c:") + print(timeit("z = c['c']", "from __main__ import c")) + + print("Tuple with three values, using a constant key:") + print(timeit("z = d[2]", "from __main__ import d")) + + print("List with three values, using a constant key:") + print(timeit("z = e[2]", "from __main__ import e")) + + print("Tuple with three values, using a local key:") + print(timeit("z = d[key]", "from __main__ import d, key")) + + print("List with three values, using a local key:") + print(timeit("z = e[key]", "from __main__ import e, key")) diff --git a/python-namedtuple/person_dataclass.py b/python-namedtuple/person_dataclass.py new file mode 100644 index 0000000000..0327f116e1 --- /dev/null +++ b/python-namedtuple/person_dataclass.py @@ -0,0 +1,13 @@ +from dataclasses import astuple, dataclass + + +@dataclass +class Person: + name: str + age: int + height: float + weight: float + country: str = "Canada" + + def __iter__(self): + return iter(astuple(self)) diff --git a/python-namedtuple/subclass.py b/python-namedtuple/subclass.py new file mode 100644 index 0000000000..a9d49c57b0 --- /dev/null +++ b/python-namedtuple/subclass.py @@ -0,0 +1,26 @@ +from collections import namedtuple +from datetime import date + +BasePerson = namedtuple( + "BasePerson", "name birthdate country", defaults=["Canada"] +) + + +class Person(BasePerson): + """A namedtuple subclass to hold a person's data.""" + + __slots__ = () + + def __repr__(self): + return f"Name: {self.name}, age: {self.age} years old." + + @property + def age(self): + return (date.today() - self.birthdate).days // 365 + + +Person.__doc__ + +jane = Person("Jane", date(1996, 3, 5)) +print(jane.age) +print(jane) diff --git a/python-namedtuple/tuple_namedtuple_time.py b/python-namedtuple/tuple_namedtuple_time.py new file mode 100644 index 0000000000..72b0fdb11e --- /dev/null +++ b/python-namedtuple/tuple_namedtuple_time.py @@ -0,0 +1,32 @@ +from collections import namedtuple +from time import perf_counter + + +def average_time(test_func): + time_measurements = [] + for _ in range(1_000): + start = perf_counter() + test_func() + end = perf_counter() + time_measurements.append(end - start) + return sum(time_measurements) / len(time_measurements) * int(1e9) + + +def time_tuple(): + tuple([1] * 1000) + + +fields = [f"a{n}" for n in range(1000)] +TestNamedTuple = namedtuple("TestNamedTuple", fields) + + +def time_namedtuple(): + TestNamedTuple(*([1] * 1000)) + + +namedtuple_time = average_time(time_namedtuple) +tuple_time = average_time(time_tuple) +gain = namedtuple_time / tuple_time + +print(f"tuple: {tuple_time:.2f} ns ({gain:.2f}x faster)") +print(f"namedtuple: {namedtuple_time:.2f} ns") diff --git a/python-namedtuple/typed_namedtuple_memory.py b/python-namedtuple/typed_namedtuple_memory.py new file mode 100644 index 0000000000..8ae1ebc8a0 --- /dev/null +++ b/python-namedtuple/typed_namedtuple_memory.py @@ -0,0 +1,19 @@ +from collections import namedtuple +from typing import NamedTuple + +from pympler import asizeof + +PointNamedTuple = namedtuple("PointNamedTuple", "x y z") + + +class PointTypedNamedTuple(NamedTuple): + x: int + y: int + z: int + + +namedtuple_memory = asizeof.asizeof(PointNamedTuple(x=1, y=2, z=3)) +typed_namedtuple_memory = asizeof.asizeof(PointTypedNamedTuple(x=1, y=2, z=3)) + +print(f"namedtuple: {namedtuple_memory} bytes") +print(f"typing.NamedTuple: {typed_namedtuple_memory} bytes") diff --git a/python-namedtuple/typed_namedtuple_time.py b/python-namedtuple/typed_namedtuple_time.py new file mode 100644 index 0000000000..b0fa6c46ce --- /dev/null +++ b/python-namedtuple/typed_namedtuple_time.py @@ -0,0 +1,40 @@ +from collections import namedtuple +from time import perf_counter +from typing import NamedTuple + + +def average_time(structure, test_func): + time_measurements = [] + for _ in range(1_000_000): + start = perf_counter() + test_func(structure) + end = perf_counter() + time_measurements.append(end - start) + return sum(time_measurements) / len(time_measurements) * int(1e9) + + +def time_structure(structure): + "x" in structure._fields + "missing_field" in structure._fields + 2 in structure + "missing_value" in structure + structure.y + + +PointNamedTuple = namedtuple("PointNamedTuple", "x y z") + + +class PointTypedNamedTuple(NamedTuple): + x: int + y: int + z: int + + +namedtuple_time = average_time(PointNamedTuple(x=1, y=2, z=3), time_structure) +typed_namedtuple_time = average_time( + PointTypedNamedTuple(x=1, y=2, z=3), time_structure +) +gain = typed_namedtuple_time / namedtuple_time + +print(f"namedtuple: {namedtuple_time:.2f} ns") +print(f"typing.NamedTuple: {typed_namedtuple_time:.2f} ns") From 521a9210e2848a2707ef641cb9d9d05a020e62cb Mon Sep 17 00:00:00 2001 From: Leodanis Pozo Ramos Date: Thu, 5 Jun 2025 16:15:42 +0200 Subject: [PATCH 2/2] Linter issues --- python-namedtuple/namedtuple_dict_memory.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python-namedtuple/namedtuple_dict_memory.py b/python-namedtuple/namedtuple_dict_memory.py index 3a7c74ef44..f1d2ade446 100644 --- a/python-namedtuple/namedtuple_dict_memory.py +++ b/python-namedtuple/namedtuple_dict_memory.py @@ -1,4 +1,5 @@ from collections import namedtuple + from pympler import asizeof Point = namedtuple("Point", "x y z")