Skip to main content Brad's PyNotes

PEP 3105: Make Print a Function

TL;DR

PEP 3105 converted Python’s print statement into a built-in function in Python 3.0. This change provided consistency with the rest of Python’s syntax and allowed features like custom separators, file output redirection, and easy replacement with logging systems.

Interesting!

The print statement was the only application-level functionality with dedicated syntax in Python. Making it a function meant treating output like any other operation, enabling straightforward mocking in tests and replacement with logging frameworks via simple string substitution.

The Problem with print Statement

Python 2’s print statement had several limitations that made it awkward compared to the rest of the language. While most Python operations used function syntax, printing required special statement syntax with unique rules.

The statement syntax created practical problems:

python code snippet start

# Python 2: Redirecting output was clunky
print >>sys.stderr, "Error:", error_msg

# Python 2: Custom separators required workarounds
print a, b, c  # Always space-separated, no alternatives

# Python 2: Replacing print required complex class definitions
# rather than simple function substitution

python code snippet end

These constraints made common tasks unnecessarily difficult. Switching from print to logging required complex code transformations, and customizing output behavior demanded understanding special syntax rules rather than standard function calls.

The Function Solution

Python 3 introduced print() as a built-in function with a clean signature:

python code snippet start

def print(*args, sep=' ', end='\n', file=None)

python code snippet end

This enabled natural, pythonic syntax for common operations:

python code snippet start

# Python 3: Clean file redirection
print("Error:", error_msg, file=sys.stderr)

# Python 3: Custom separators
print(2025, 11, 18, sep='-')  # Output: 2025-11-18

# Python 3: Different line endings
print("Loading", end='... ')
print("Done")  # Output: Loading... Done

# Python 3: Multiple items with custom formatting
values = ['apple', 'banana', 'cherry']
print(*values, sep=' | ')  # Output: apple | banana | cherry

python code snippet end

The function approach treated printing like any other Python operation, making it composable and extensible.

Key Benefits

The transformation from statement to function provided several advantages:

Flexibility: Keyword arguments enabled customization without special syntax. The sep, end, and file parameters covered common use cases elegantly.

Maintainability: Converting print to logging became straightforward:

python code snippet start

# Before: Using print
print("Processing", filename)

# After: Simple string replacement
logging.info("Processing", filename)

python code snippet end

Testability: Functions can be mocked and replaced easily in tests:

python code snippet start

from unittest.mock import patch

with patch('builtins.print') as mock_print:
    my_function()
    mock_print.assert_called_with("Expected output")

python code snippet end

Future Evolution: The function syntax left room for extensions. The proposal noted that adding variants like printf() would be more natural with print as a function.

Breaking Changes

The transition was one of Python 3’s most visible breaking changes. Existing code with print statements stopped working:

python code snippet start

# Python 2
print "Hello, world"  # Valid statement

# Python 3
print "Hello, world"  # SyntaxError: Missing parentheses

python code snippet end

More subtle was the behavioral change with parentheses. Code that worked in Python 2 produced different output in Python 3:

python code snippet start

# Python 2: Prints a tuple
print("Hello", "world")  # Output: ('Hello', 'world')

# Python 3: Prints separate arguments
print("Hello", "world")  # Output: Hello world

python code snippet end

This incompatibility was deliberate. The Python 3 clean slate philosophy prioritized long-term language consistency over short-term compatibility.

The Python 3 Philosophy

PEP 3105 exemplified Python 3’s design approach: remove special cases and inconsistencies, even at the cost of breaking existing code. Print’s unique statement syntax violated the principle that syntax should serve the compiler, not application logic.

By making print a function, Python 3 became more internally consistent. Developers learned one set of rules for functions that applied everywhere, including output operations.

PEP 3134: Exception Chaining was another Python 3 improvement that enhanced error handling and debugging.

Reference: PEP 3105 – Make print a function