This project has been created as part of the 42 curriculum by Edson Baptista Finda.
The exercise-by-exercise journey — problem statements, my reasoning, and the concepts each one was designed to teach — is fully documented on Notion. This README is a lighter, GitHub-native companion to that: a concept-first map of what each module covers and which exercises apply it, so the code is legible on its own without needing the full write-up open.
- Description
- module00 — Fundamentals
- module01 — OOP basics: classes, encapsulation & inheritance
- module02 — Exception handling
- module03 — CLI arguments & core data structures
- module04 — File I/O & streams
- module05 — Abstraction: ABCs, protocols & dunder methods
- module06 — Modules & packages
- module07 — Design patterns in Python OOP
- module08 — Environments & dependency management
- module09 — Data validation with Pydantic
- module10 — Functional programming
This repository holds my solutions to a Python Piscine: eleven modules (module00 through module10) that go from the language's basics to writing idiomatic, modern Python (3.10–3.14) across OOP, packaging, external libraries, and functional-style code. Each module wraps its exercises in a small theme — a garden, an alchemy grimoire, a monster-battling RPG, the Matrix — but the theming is a skin over a specific concept each exercise is built to force.
Every exercise lives in its own exN/ folder as a runnable, self-contained script. The sections below aren't a file listing — they walk through the concept each module targets and point to the exercises that apply it. For the deeper narrative behind why a given approach was chosen over another, that's what the Notion documentation is for.
The warm-up: functions, input()/f-strings, type casting, conditionals, loops, and a first look at recursion — before any of it is wrapped in a class.
ex0–ex3: A function per exercise (ft_hello_garden,ft_garden_name,ft_plot_area,ft_harvest_total), moving from a staticprintto reading and casting user input (int(input(...))) and formatting output with f-strings.ex4/ex5:if/elsebranching on user input (ft_plant_age,ft_water_reminder).ex6: The same problem solved two ways —ft_count_harvest_iterativewith aforloop,ft_count_harvest_recursivewith a nested helper function and default parameter values, comparing iteration against recursion for the same task.ex7:ft_seed_inventorytakes multiple typed parameters (with type hints) and dispatches on one of them, plus string methods like.capitalize().
#module01--oop-basics-classes-encapsulation--inheritance
The first real class, then everything that follows from it: private-by-convention attributes, classmethod/staticmethod, and single and multi-level inheritance.
ex1—Plant: The core class. Attributes prefixed with_(Python's encapsulation convention), constructor-level validation, getters/setters that reject bad state, a@classmethodfactory (make_anonymous) that builds aPlantwithout normal arguments, a@staticmethod(is_older_than_1y) that doesn't need an instance at all, and a nested class (Stats) tracking how many times each method has been called.ex2/ex3: ImportingPlantacross files and using it in a loop (ft_plant_growth) and in a batch of instances (ft_plant_factory).ex4:ft_garden_security— proving the encapsulation fromex1actually holds by trying (and failing) to force aPlantinto an invalid state.ex5—Flower/Tree/Vegetable: Single inheritance fromPlant, each subclass callingsuper().__init__()and overridingshow()while adding its own behavior and state.ex6—Seed: A second level of inheritance (Seed → Flower → Plant), extending inherited state (self._stats.insert(...)) rather than replacing it.
try/except/finally used for real control flow, escalating from catching a single built-in exception to a small custom exception hierarchy.
ex0: A firsttry/except ValueError.ex1—TempError: A custom exception with__str__overridden for a readable message, caught alongside a built-in one via a tuple:except (ValueError, TempError).ex2: Deliberately triggering four different built-in exceptions (ValueError,ZeroDivisionError,FileNotFoundError,TypeError) from one dispatch function to practice catching each by type.ex3—GardenError/PlantError/WaterError: A small exception hierarchy — two specific exceptions inheriting from a common base — caught either individually or, just as validly, by catching the shared base class.ex4:*argsfor a variadic function, and afinallyblock that guarantees cleanup ("closing watering system") runs whether or not an exception was raised.
#module03--cli-arguments--core-data-structures
Reading real input from sys.argv, then a tour of Python's core containers — tuples, sets, dicts — plus generators and comprehensions.
ex0/ex1:sys.argv,sys.exit, parsing CLI arguments into a list of numbers and computing aggregates (sum,min,max).ex2: Tuple unpacking (x, y, z = ...), Python's structural pattern matching (match/case) to distinguishKeyboardInterruptfromEOFError, and the distinction between catchingExceptionvs the broaderBaseException.ex3:setoperations — union (|), intersection (&), difference (-) — used to compare which achievements different players share or are missing.ex4:dictconstruction from parsed input,dict.update(), and computing per-key percentages of a whole.ex5: Generators — an infinite generator (while True: yield) for random events, and a second generator that mutates the list it's consuming from (events.pop(index)) as it's iterated withnext().ex6: List and dict comprehensions, including a comprehension with a filtering condition.
Opening, reading, writing, and safely closing files — ending in the idiomatic way to do all of it.
ex0:open()/.read()/.close(), withOSErrorcaught around theopen()call.ex1: A reusableft_safe_open()helper, the walrus operator (if file := ft_safe_open(path):) to open and check in one expression, iterating a file line by line, and writing transformed content to a new file.ex2: Doing the same job withsys.stdin.readline()/sys.stdout.write()/sys.stdout.flush()instead ofinput()/print(), to work directly with the stream objects.ex3: The context manager form,with open(file_name, mode=action) as file:, which guarantees the file is closed even if an exception is raised inside the block — plus returning atuple[bool, str]as a functional alternative to raising an exception for an expected failure case.
#module05--abstraction-abcs-protocols--dunder-methods
Where Python's abstraction tools stop being theoretical: an abstract base class enforced at runtime, then a second way to achieve the same polymorphism without inheritance at all.
ex0—DataProcessor(ABC):abc.ABCand@abstractmethodforceNumericProcessor,TextProcessor, andLogProcessorto each implementvalidate()/ingest(), while sharing the base'soutput()behavior.ex1—DataStream: Dunder methods (__len__,__str__) that make custom objects work withlen()andprint()naturally, and composition —DataStreamholds asetofDataProcessorinstances and dispatches to whichever one validates a given item, all through the shared abstract interface.ex2—ExportPlugin(Protocol):typing.Protocolfor structural typing —CSVExportPluginandJSONExportPluginsatisfy the plugin interface just by having a matchingprocess_output()method, with no inheritance fromExportPluginrequired at all. The clearest contrast in the repo between nominal typing (ex0'sABC) and structural typing (Protocol).
Not language syntax so much as Python's import system: the difference between a module and a package, and exactly what __init__.py does and doesn't expose.
ft_alembic_0–ft_alembic_5:import xvsfrom x import y, first against a single-file module (elements.py), then against a package (alchemy/) — and what changes oncealchemy/__init__.pyre-exports specific names (ft_alembic_4deliberately tries to reach a function__init__.pydoesn't re-export, to show it fails).ft_distillation_0/1: Reaching a function directly through its submodule vs through the package's re-exported (and aliased —heal = healing_potion) name.ft_kaboom_0/1:alchemy/grimoire/__init__.pyonly re-exportslight_spellbook, notdark_spellbook—ft_kaboom_1importsdark_spellbookdirectly to show that submodules not surfaced by__init__.pyare still reachable by their full dotted path, they're just not part of the package's public surface.ft_transmutation_0–2: A subpackage (alchemy/transmutation/) reached three different ways — direct submodule import, subpackage import, and through the top-level package — to make the dotted-path resolution rules concrete.
#module07--design-patterns-in-python-oop
Classic OOP design patterns, implemented Python-style — including the multiple inheritance Python allows that C++-style single-inheritance languages don't make as easy.
ex0—Creature/CreatureFactory: An Abstract Factory —FlameFactory/AquaFactoryeach produce a matching base/evolved pair (Flameling→Pyrodon,Aquabub→Torragon) without the caller needing to know the concrete classes.ex1—HealCapability/TransformCapability: Multiple inheritance as mixins —Sproutling(Creature, HealCapability)combines two unrelated abstract bases, each contributing its own behavior, with both__init__s called explicitly where needed.ex2—BattleStrategy: The Strategy pattern —NormalStrategy/AggressiveStrategy/DefensiveStrategyeach implementact()differently, usingisinstance()checks against theex1mixins (andtyping.cast) to validate that a given creature actually supports the strategy being applied to it.
#module08--environments--dependency-management
Python outside the language itself: isolating a project's dependencies, managing them, and configuring an app without hardcoding secrets.
ex0—construct.py: Detecting whether the script is running inside a virtual environment by comparingsys.prefixtosys.base_prefix, and locating the environment's package install path viasite.getsitepackages().ex1—loading.py:importlib.import_module()for checking optional/third-party dependencies (numpy,pandas,matplotlib,requests) at runtime with a friendly error instead of a crash, alongside both dependency-management styles side by side —requirements.txt(pip) andpyproject.toml(Poetry).ex2—oracle.py: Loading configuration from a.envfile viapython-dotenv, validating that required variables are present and well-formed, and masking secrets in output depending on whetherMATRIX_MODEisdevelopmentorproduction.
#module09--data-validation-with-pydantic
Runtime, type-safe data validation with Pydantic — moving field validation out of scattered if checks and into declarative model definitions.
ex0—SpaceStation: Apydantic.BaseModelwith per-field constraints (Field(ge=..., le=..., min_length=..., max_length=...)), and an invalid instantiation caught as aValidationErrorwith a structured, field-level error message.ex1—AlienContact: AStrEnumfor a constrained set of valid values, automatic type coercion (passing'8.5'into afloatfield), and@model_validator(mode="after")for cross-field business rules a singleFieldconstraint can't express (e.g. physical contact reports must be verified; strong signals require a received message), raised as aPydanticCustomError.ex2—SpaceMission/CrewMember: Nested Pydantic models — aSpaceMissionholding alist[CrewMember], each validated independently — plus amodel_validatorenforcing rules across the whole nested structure (a mission needs at least one Commander/Captain, and long missions need enough experienced crew).
#module10--functional-programming
Python's functional side: lambdas, closures, functools, and decorators, each building on the last.
ex0:lambdaas thekey=argument tosorted/max/min, and withfilter/map.ex1: Higher-order functions — functions that take other functions as arguments and/or return new functions (spell_combiner,power_amplifier,conditional_caster), typed withCallabletype aliases.ex2: Closures —mage_counter,spell_accumulator, andmemory_vaulteach return an inner function (or dict of them) that keeps its own private state alive between calls via thenonlocalkeyword, with no class involved.ex3:functools.reduce,functools.partial(pre-filling arguments),@functools.lru_cache(memoizing a recursive Fibonacci), and@functools.singledispatch(dispatching a function's behavior based on its argument's runtime type).ex4: Decorators — a plain decorator (@spell_timer), a parameterized decorator factory (@power_validator(10),@retry_spell(3)),functools.wrapsto preserve the wrapped function's__name__/metadata, and a decorator applied to a function defined inside a class method.