This Functions section began with the question of what functions are and why they matter. Over the previous twenty-seven articles, you have learned to define functions, pass arguments, return values, manage scope, design for purity and reusability, organize code across files, and test your work. This final article puts those skills together by walking through the design and implementation of a small utility library, a collection of related functions that you can use across multiple projects. Building a library is the natural endpoint of learning functions because a library is nothing more than a set of well-designed functions organized into modules and packaged for reuse.
The library in this article is a set of text manipulation utilities: functions for working with strings in ways that Python's built-in str methods do not directly support. Text processing is a universal need, and the functions are simple enough to implement clearly while being complex enough to demonstrate the design principles from the preceding articles. You can follow along with the code or adapt the structure to build a library in a domain that matters to your own work.
Designing the library's purpose and scope
Before writing any code, define what the library will do. A focused library that does one category of thing well is more valuable than a sprawling collection of unrelated utilities. For a text manipulation library, a reasonable scope includes functions for truncating strings to a maximum length with an ellipsis, normalizing whitespace by collapsing multiple spaces into one, converting between common casing conventions like snake_case and camelCase, and extracting substrings between delimiters. Each function addresses a specific, recurring need in text processing.
Name the library texttools. The name is short, descriptive, and unlikely to collide with existing Python packages. The module structure will be a single file for the initial version because the library is small enough that splitting into multiple files would add organizational overhead without benefit. If the library grows to include distinct categories of text operations, such as casing, whitespace, and extraction, splitting into submodules will follow naturally from the principles in the article on organizing Python functions across files.
Implementing the functions
Each function follows the design principles from this section. It has a single responsibility expressed in a clear verb-based name. It accepts its inputs as explicit parameters, with sensible defaults where appropriate. It returns its result rather than printing it or modifying global state. And it handles edge cases explicitly, documenting in its docstring what happens when inputs are empty, None, or otherwise at the boundaries:
def truncate(text, max_length, ellipsis="..."):
"""Shorten text to max_length, appending an ellipsis if needed."""
if max_length < len(ellipsis):
raise ValueError("max_length must be at least as long as ellipsis")
if len(text) <= max_length:
return text
return text[: max_length - len(ellipsis)] + ellipsisThe truncate function takes a text string, a max_length, and an optional ellipsis string. If max_length is less than the ellipsis length, it raises a ValueError. If the text fits within max_length, it returns it unchanged. Otherwise it truncates and appends the ellipsis, keeping the final result no longer than max_length. Every other function in the library follows this same shape: a clear name, explicit parameters with defaults where useful, a docstring, and a return value instead of a side effect.
Organizing the module
The library file, texttools.py, contains the functions grouped logically. Related functions, such as those that deal with whitespace, appear near each other. Each function is separated by two blank lines as recommended by PEP 8. The file begins with a module-level docstring that describes the library's purpose:
"""Text manipulation utilities for common string processing tasks.
This module provides functions for truncating text, normalizing
whitespace, converting between case conventions, and extracting
substrings, designed to complement Python's built-in str methods.
"""The module docstring is the first thing a user sees when they call help(texttools) or read the source code. It sets expectations for what the module contains and how the functions relate to each other. A good module docstring is short, specific, and focused on what the module provides, not on implementation details or usage instructions that belong in individual function docstrings.
Testing the library
Tests for the library live in a separate file, test_texttools.py, following the pattern from the article on testing Python functions. Each function gets multiple test cases covering normal inputs, edge cases, and error conditions. The test for truncate verifies that short text passes through unchanged, that long text is truncated with the default ellipsis, that the edge case where max_length matches the ellipsis length works correctly, and that an invalid max_length raises ValueError. The tests are plain assert statements, guarded by if name == "main" so they run when the file is executed directly but not when imported.
Using and evolving the library
Once the library is written and tested, you use it by importing it into your other projects:
import texttools
result = texttools.truncate("a very long string", 12)
print(result) # "a very lo..."Copy the texttools.py file into each project that needs it, or place it in a shared directory that you add to Python's module search path. As you use the library, you will discover functions that are missing, edge cases you did not handle, and opportunities to improve the existing functions. Each improvement benefits every project that uses the library, which is the compounding value of writing reusable code.
The library you build today does not need to be perfect. It needs to solve a real problem, be correct for the cases you have tested, and be organized well enough that you can find and improve functions as your needs evolve. The skills you developed across this Functions section, defining, parameterizing, scoping, organizing, documenting, and testing, are what let you build a library that improves over time rather than one that becomes harder to maintain with each change.
Rune AI
Key Insights
- Start with a clear purpose: your library should solve a specific, coherent problem you actually encounter.
- Design each function with a single responsibility, clear parameters, and a predictable return value.
- Organize functions into modules named by their area of responsibility; keep related functions together.
- Document every public function with a docstring describing its purpose, parameters, return value, and edge cases.
- Write tests alongside your functions; a library without tests cannot be trusted or maintained over time.
Frequently Asked Questions
What kind of utility library should I build as a beginner?
How do I share my Python utility library with others?
How many functions should a utility library contain?
Conclusion
Building a utility library is the capstone that connects every concept in this Functions section. You design functions with clear responsibilities, organize them into modules, document them with docstrings, and test them with assert statements. The result is not just a collection of code. It is a reusable toolkit that you will reach for in future projects, and it is tangible proof that you have mastered the skills this section set out to teach.
More in this topic
Define and Call Python Functions
Learn the exact syntax for defining and calling Python functions with the def keyword, including naming rules, the function body, and how the call stack works.
Python Function Parameters and Arguments
Learn the difference between parameters and arguments, how to pass values by position and by keyword, and the rules for default parameter values.
Python Functions Explained
Learn what Python functions are, why they matter for organizing code, and how they form the foundation of non-trivial programs.