Published on

The Zen of Python: 19 Principles for Writing Pythonic Code with Examples

The Zen of Python: 19 Principles for Writing Pythonic Code with Examples

Introduction

The Zen of Python is a collection of 19 guiding principles for writing "Pythonic" code—code that is readable, straightforward, and concise. Written by longtime Python developer Tim Peters in 1999, these principles serve as a philosophical manifesto for Python's design and a guide for developers seeking to write better code.

You can access the Zen of Python anytime by typing import this in your Python interpreter. In this comprehensive guide, we'll explore each principle with practical code examples that demonstrate how to apply them in real-world scenarios.

If you're new to Python, check out our Python Cheat Sheet for a quick reference guide.

The 19 Principles of Zen Python

1. Beautiful is Better Than Ugly

Write code that is aesthetically pleasing and easy on the eyes. Clean, well-formatted code is easier to understand and maintain.

Ugly Code:

def calc(x,y,z):
    return x+y*z if x>0 else y-z
result=calc(5,3,2)
print(result)

Beautiful Code:

def calculate_result(base, multiplier, factor):
    """
    Calculate result based on base value.

    Args:
        base: The base number
        multiplier: Number to multiply
        factor: Multiplication factor

    Returns:
        Calculated result
    """
    if base > 0:
        return base + (multiplier * factor)
    else:
        return multiplier - factor

result = calculate_result(base=5, multiplier=3, factor=2)
print(result)

2. Explicit is Better Than Implicit

Make your intentions clear. Don't force readers to guess what your code does.

Implicit (Bad):

def process(data):
    return [x for x in data if x]

Explicit (Good):

def filter_truthy_values(data_list):
    """Remove falsy values (None, 0, False, empty strings) from list."""
    return [item for item in data_list if item is not None and item != '']

# Even better - be specific about what you're filtering
def filter_empty_strings(string_list):
    """Remove empty strings from list."""
    return [s for s in string_list if s != '']

When working with Pandas DataFrames, being explicit is crucial:

import pandas as pd

# Implicit - unclear what's happening
df = df[df['age'] > 18]

# Explicit - clear filtering logic
def filter_adult_users(user_dataframe, minimum_age=18):
    """Filter DataFrame to include only users above minimum age."""
    adult_users = user_dataframe[user_dataframe['age'] > minimum_age]
    return adult_users

df_adults = filter_adult_users(df)

3. Simple is Better Than Complex

Choose the simplest solution that solves the problem. Avoid over-engineering.

Complex:

class DataProcessor:
    def __init__(self):
        self.data = []

    def add_data(self, item):
        self.data.append(item)

    def process(self):
        result = []
        for item in self.data:
            if isinstance(item, int):
                result.append(item * 2)
        return result

processor = DataProcessor()
for num in [1, 2, 3, 4]:
    processor.add_data(num)
processed = processor.process()

Simple:

def double_integers(numbers):
    """Double all integer values in a list."""
    return [num * 2 for num in numbers if isinstance(num, int)]

processed = double_integers([1, 2, 3, 4])

4. Complex is Better Than Complicated

When complexity is necessary, keep it organized and understandable. Complex is manageable; complicated is messy.

Complicated (Avoid):

# Everything in one function - hard to test and maintain
def process_user_data(data):
    result = []
    for item in data:
        if 'user' in item and 'age' in item['user']:
            if item['user']['age'] > 18:
                if 'email' in item['user']:
                    if '@' in item['user']['email']:
                        result.append({
                            'name': item['user']['name'] if 'name' in item['user'] else 'Unknown',
                            'email': item['user']['email'],
                            'status': 'active' if item.get('active', False) else 'inactive'
                        })
    return result

Complex but Organized (Better):

from typing import Dict, List, Optional

def is_valid_email(email: str) -> bool:
    """Check if email format is valid."""
    return '@' in email and '.' in email

def is_adult(age: int, minimum_age: int = 18) -> bool:
    """Check if user is an adult."""
    return age > minimum_age

def extract_user_info(user_data: Dict) -> Optional[Dict]:
    """Extract and validate user information."""
    if 'user' not in user_data:
        return None

    user = user_data['user']

    # Validate age
    if 'age' not in user or not is_adult(user['age']):
        return None

    # Validate email
    if 'email' not in user or not is_valid_email(user['email']):
        return None

    return {
        'name': user.get('name', 'Unknown'),
        'email': user['email'],
        'status': 'active' if user_data.get('active', False) else 'inactive'
    }

def process_user_data(data: List[Dict]) -> List[Dict]:
    """Process list of user data and return valid adult users."""
    valid_users = []

    for item in data:
        user_info = extract_user_info(item)
        if user_info:
            valid_users.append(user_info)

    return valid_users

This is especially important when building data processing pipelines.

5. Flat is Better Than Nested

Avoid deep nesting. Flatten your code structure when possible.

Nested (Hard to Read):

def process_order(order):
    if order:
        if 'items' in order:
            if len(order['items']) > 0:
                if 'user' in order:
                    if order['user']['verified']:
                        return process_verified_order(order)
                    else:
                        return process_unverified_order(order)
    return None

Flat (Easy to Read):

def process_order(order):
    """Process order with early returns to reduce nesting."""
    if not order:
        return None

    if 'items' not in order or len(order['items']) == 0:
        return None

    if 'user' not in order:
        return None

    if order['user']['verified']:
        return process_verified_order(order)

    return process_unverified_order(order)

6. Sparse is Better Than Dense

Don't try to cram too much logic into one line. Readability counts.

Dense:

result = [x*2 for x in [int(i) for i in data.split(',')] if int(i) > 0 and int(i) < 100]

Sparse:

# Break down the operations
raw_values = data.split(',')
numbers = [int(value) for value in raw_values]
filtered_numbers = [num for num in numbers if 0 < num < 100]
result = [num * 2 for num in filtered_numbers]

When working with PySpark, sparse code is much more readable:

# Dense - hard to debug
df_result = df.filter(df.age > 18).select('name', 'email').groupBy('email').count().filter('count > 1')

# Sparse - each operation is clear
df_adults = df.filter(df.age > 18)
df_selected = df_adults.select('name', 'email')
df_grouped = df_selected.groupBy('email')
df_counted = df_grouped.count()
df_result = df_counted.filter('count > 1')

7. Readability Counts

Prioritize code readability over cleverness. Your code will be read more often than it's written.

Unreadable:

# Clever but confusing
def f(x): return sum(i for i in range(x) if not any(i % j == 0 for j in range(2, i)))

Readable:

def sum_of_prime_numbers(max_number):
    """
    Calculate sum of all prime numbers less than max_number.

    Args:
        max_number: Upper limit (exclusive)

    Returns:
        Sum of prime numbers
    """
    def is_prime(number):
        """Check if a number is prime."""
        if number < 2:
            return False

        for divisor in range(2, number):
            if number % divisor == 0:
                return False

        return True

    prime_numbers = [num for num in range(max_number) if is_prime(num)]
    return sum(prime_numbers)

8. Special Cases Aren't Special Enough to Break the Rules

Maintain consistency. Don't create exceptions to your coding standards without good reason.

Inconsistent:

class DataProcessor:
    def process_users(self, users):
        # Uses snake_case
        return [u.upper() for u in users]

    def ProcessOrders(self, orders):  # Breaks convention
        # Uses PascalCase
        return [o.lower() for o in orders]

    def PROCESS_ITEMS(self, items):  # Breaks convention
        # Uses UPPER_CASE
        return items

Consistent:

class DataProcessor:
    """Process various data types consistently."""

    def process_users(self, users):
        """Process user data."""
        return [user.upper() for user in users]

    def process_orders(self, orders):
        """Process order data."""
        return [order.lower() for order in orders]

    def process_items(self, items):
        """Process item data."""
        return items

This is especially important in data engineering projects where consistency helps maintain large codebases.

9. Although Practicality Beats Purity

While rules are important, pragmatic solutions are sometimes necessary. Don't let perfectionism prevent you from shipping code.

# Purist approach - might be overkill for a simple script
from abc import ABC, abstractmethod
from typing import Protocol

class DataTransformer(Protocol):
    def transform(self, data): ...

class UpperCaseTransformer(DataTransformer):
    def transform(self, data):
        return data.upper()

# Practical approach - simple and effective
def transform_to_uppercase(data):
    """Convert data to uppercase."""
    return data.upper()

# Use the simple function for simple tasks
result = transform_to_uppercase("hello world")

10. Errors Should Never Pass Silently

Always handle errors explicitly. Don't use bare except clauses.

Silent Errors (Bad):

def read_config_file(filename):
    try:
        with open(filename) as f:
            return json.load(f)
    except:
        return {}  # Silently returns empty dict

Explicit Error Handling (Good):

import json
import logging
from typing import Dict, Optional

def read_config_file(filename: str) -> Dict:
    """
    Read and parse JSON configuration file.

    Args:
        filename: Path to config file

    Returns:
        Configuration dictionary

    Raises:
        FileNotFoundError: If config file doesn't exist
        json.JSONDecodeError: If file contains invalid JSON
    """
    try:
        with open(filename, 'r') as f:
            return json.load(f)
    except FileNotFoundError:
        logging.error(f"Config file not found: {filename}")
        raise
    except json.JSONDecodeError as e:
        logging.error(f"Invalid JSON in {filename}: {e}")
        raise
    except Exception as e:
        logging.error(f"Unexpected error reading {filename}: {e}")
        raise

When working with Apache Spark, proper error handling is critical:

from pyspark.sql import SparkSession
from pyspark.sql.utils import AnalysisException

def read_spark_table(spark: SparkSession, table_name: str):
    """Read Spark table with proper error handling."""
    try:
        df = spark.table(table_name)
        return df
    except AnalysisException as e:
        logging.error(f"Table {table_name} not found: {e}")
        raise
    except Exception as e:
        logging.error(f"Error reading table {table_name}: {e}")
        raise

11. Unless Explicitly Silenced

If you must silence an error, make it explicit and document why.

import logging

def optional_feature_check():
    """Check if optional feature is available."""
    try:
        import optional_library
        return True
    except ImportError:
        # Explicitly silencing - this import is optional
        logging.info("Optional library not available, using fallback")
        return False

def load_data_with_fallback(filename):
    """Load data with fallback to default if file missing."""
    try:
        with open(filename) as f:
            return f.read()
    except FileNotFoundError:
        # Explicitly handling missing file with default
        logging.warning(f"File {filename} not found, using default data")
        return "default_data"

12. In the Face of Ambiguity, Refuse the Temptation to Guess

When something is unclear, make it explicit or raise an error rather than guessing.

Guessing (Bad):

def get_user_age(user_data):
    # Guessing what the age field might be called
    return user_data.get('age') or user_data.get('Age') or user_data.get('AGE') or 0

Explicit (Good):

def get_user_age(user_data: Dict, age_field: str = 'age') -> int:
    """
    Get user age from data.

    Args:
        user_data: Dictionary containing user information
        age_field: Name of the field containing age (default: 'age')

    Returns:
        User age

    Raises:
        KeyError: If age field is not present
        ValueError: If age is not a valid integer
    """
    if age_field not in user_data:
        raise KeyError(f"Age field '{age_field}' not found in user data")

    age = user_data[age_field]

    if not isinstance(age, int) or age < 0:
        raise ValueError(f"Invalid age value: {age}")

    return age

13. There Should Be One—and Preferably Only One—Obvious Way to Do It

Python encourages a single, clear approach to solving problems.

Multiple Ways (Confusing):

# Too many ways to do the same thing
numbers = [1, 2, 3, 4, 5]

# Method 1
squared1 = [x**2 for x in numbers]

# Method 2
squared2 = list(map(lambda x: x**2, numbers))

# Method 3
squared3 = []
for x in numbers:
    squared3.append(x**2)

One Obvious Way (Clear):

# List comprehension is the Pythonic way for simple transformations
numbers = [1, 2, 3, 4, 5]
squared = [x**2 for x in numbers]

# For more complex operations, use explicit loops
def process_number(x):
    """Complex processing logic."""
    result = x ** 2
    result = result + 10
    return result * 2

processed = [process_number(x) for x in numbers]

14. Although That Way May Not Be Obvious at First Unless You're Dutch

This is a humorous nod to Guido van Rossum, Python's creator, who is Dutch. The Pythonic way may take time to learn, but it's worth it.

# Not obvious at first, but Pythonic
# Using enumerate instead of manual indexing
items = ['a', 'b', 'c']

# Non-Pythonic (but obvious to beginners)
for i in range(len(items)):
    print(i, items[i])

# Pythonic (obvious once you know Python)
for index, item in enumerate(items):
    print(index, item)

# Swapping variables
# Non-Pythonic
temp = a
a = b
b = temp

# Pythonic
a, b = b, a

# Unpacking
# Non-Pythonic
first = my_list[0]
rest = my_list[1:]

# Pythonic
first, *rest = my_list

15. Now is Better Than Never

Don't procrastinate on implementing necessary features or fixes. Act now.

# Don't put off error handling
# Bad: "I'll add error handling later"
def process_data(data):
    result = data.transform()
    return result

# Good: Add error handling now
def process_data(data):
    """Process data with proper error handling."""
    if not data:
        raise ValueError("Data cannot be empty")

    try:
        result = data.transform()
        return result
    except AttributeError:
        raise TypeError("Data must have a transform method")
    except Exception as e:
        logging.error(f"Error processing data: {e}")
        raise

16. Although Never is Often Better Than Right Now

Don't rush into a solution without thinking. Take time to design properly.

# Don't rush into the first solution
# Rushed solution - tightly coupled
class OrderProcessor:
    def process(self, order):
        # Hardcoded email sending logic
        smtp = smtplib.SMTP('smtp.gmail.com', 587)
        smtp.send_message(order.email)
        # Hardcoded database logic
        db = mysql.connect('localhost')
        db.execute('INSERT INTO orders...')

# Better - take time to design properly
from abc import ABC, abstractmethod

class EmailService(ABC):
    @abstractmethod
    def send_email(self, to, subject, body):
        pass

class DatabaseService(ABC):
    @abstractmethod
    def save_order(self, order):
        pass

class OrderProcessor:
    def __init__(self, email_service: EmailService, db_service: DatabaseService):
        self.email_service = email_service
        self.db_service = db_service

    def process(self, order):
        """Process order with injected dependencies."""
        self.db_service.save_order(order)
        self.email_service.send_email(
            to=order.email,
            subject="Order Confirmation",
            body=f"Your order {order.id} is confirmed"
        )

This is particularly important in data engineering workflows where poor initial design can be costly.

17. If the Implementation is Hard to Explain, It's a Bad Idea

Code that requires lengthy explanations is too complex. Simplify it.

Hard to Explain (Bad):

# What does this do? Hard to explain!
def f(x, y):
    return [(i, j) for i in x for j in y if i[0] == j[1] and sum(ord(c) for c in i) > sum(ord(c) for c in j)]

Easy to Explain (Good):

def find_matching_pairs(first_list, second_list):
    """
    Find pairs where first char of first item matches last char of second item,
    and the ASCII sum of the first item is greater than the second item.

    Args:
        first_list: List of strings
        second_list: List of strings

    Returns:
        List of tuples containing matching pairs
    """
    def ascii_sum(text):
        """Calculate sum of ASCII values for all characters."""
        return sum(ord(char) for char in text)

    def first_char_matches_last(text1, text2):
        """Check if first char of text1 matches last char of text2."""
        return text1[0] == text2[-1]

    matching_pairs = []

    for item1 in first_list:
        for item2 in second_list:
            if first_char_matches_last(item1, item2):
                if ascii_sum(item1) > ascii_sum(item2):
                    matching_pairs.append((item1, item2))

    return matching_pairs

18. If the Implementation is Easy to Explain, It May Be a Good Idea

Simple, explainable implementations are often the best.

def calculate_average(numbers):
    """
    Calculate the average of a list of numbers.

    Simple implementation: sum all numbers and divide by count.
    """
    if not numbers:
        return 0

    total = sum(numbers)
    count = len(numbers)
    average = total / count

    return average

19. Namespaces are One Honking Great Idea—Let's Do More of Those!

Use namespaces to organize code and avoid conflicts. This is why Python uses modules and packages.

# Bad: Everything in global namespace
def connect():
    pass

def disconnect():
    pass

def query():
    pass

def connect():  # Conflicts with above!
    pass

# Good: Use classes/modules for namespaces
class DatabaseConnection:
    """Database connection operations."""

    def connect(self):
        """Establish database connection."""
        pass

    def disconnect(self):
        """Close database connection."""
        pass

    def query(self, sql):
        """Execute SQL query."""
        pass

class APIConnection:
    """API connection operations."""

    def connect(self):
        """Establish API connection."""
        pass

    def disconnect(self):
        """Close API connection."""
        pass

# No conflicts!
db = DatabaseConnection()
api = APIConnection()

This is crucial when working with multiple Python libraries in data engineering projects.

Practical Application: Refactoring with Zen of Python

Let's see a complete example of refactoring code to follow the Zen of Python:

Before (Non-Pythonic):

def p(d):
    r=[]
    for i in d:
        if i['a']>18:
            if i['e']:
                if '@' in i['e']:
                    r.append({'n':i.get('n','?'),'e':i['e'],'a':i['a']})
    return r

After (Pythonic):

from typing import List, Dict, Optional

def is_valid_email(email: str) -> bool:
    """
    Validate email format.

    Args:
        email: Email address to validate

    Returns:
        True if email contains @, False otherwise
    """
    return '@' in email and len(email) > 0

def is_adult(age: int, minimum_age: int = 18) -> bool:
    """
    Check if person is an adult.

    Args:
        age: Person's age
        minimum_age: Minimum age to be considered adult (default: 18)

    Returns:
        True if age meets minimum, False otherwise
    """
    return age > minimum_age

def extract_valid_user(user_dict: Dict) -> Optional[Dict]:
    """
    Extract user information if valid.

    A valid user must be an adult with a valid email address.

    Args:
        user_dict: Dictionary containing user data with keys 'a' (age),
                   'e' (email), and 'n' (name)

    Returns:
        Dictionary with user info if valid, None otherwise
    """
    # Early returns for invalid cases (flat is better than nested)
    if 'a' not in user_dict:
        return None

    if not is_adult(user_dict['a']):
        return None

    if 'e' not in user_dict:
        return None

    if not is_valid_email(user_dict['e']):
        return None

    # Extract valid user data
    return {
        'name': user_dict.get('n', 'Unknown'),
        'email': user_dict['e'],
        'age': user_dict['a']
    }

def process_users(user_data: List[Dict]) -> List[Dict]:
    """
    Process list of users and return valid adult users with emails.

    Args:
        user_data: List of dictionaries containing user information

    Returns:
        List of valid users (adults with valid emails)

    Example:
        >>> users = [
        ...     {'n': 'Alice', 'a': 25, 'e': '[email protected]'},
        ...     {'n': 'Bob', 'a': 16, 'e': '[email protected]'},
        ...     {'n': 'Carol', 'a': 30, 'e': 'invalid_email'}
        ... ]
        >>> process_users(users)
        [{'name': 'Alice', 'email': '[email protected]', 'age': 25}]
    """
    valid_users = []

    for user in user_data:
        valid_user = extract_valid_user(user)
        if valid_user:
            valid_users.append(valid_user)

    return valid_users

Zen of Python in Data Engineering

The Zen of Python is especially valuable in data engineering where code clarity and maintainability are crucial. Here's how these principles apply:

Example: Building a Data Pipeline

from typing import List, Dict, Callable
import logging

# Configure logging (Errors should never pass silently)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class DataPipeline:
    """
    A simple, explicit data pipeline.

    Follows Zen principles:
    - Beautiful is better than ugly
    - Explicit is better than implicit
    - Simple is better than complex
    - Readability counts
    """

    def __init__(self, name: str):
        """
        Initialize pipeline.

        Args:
            name: Pipeline name for logging
        """
        self.name = name
        self.transformations: List[Callable] = []

    def add_transformation(self, func: Callable) -> 'DataPipeline':
        """
        Add a transformation function to the pipeline.

        Args:
            func: Transformation function that takes data and returns transformed data

        Returns:
            Self for method chaining
        """
        self.transformations.append(func)
        return self

    def execute(self, data: List[Dict]) -> List[Dict]:
        """
        Execute all transformations in order.

        Args:
            data: Input data to transform

        Returns:
            Transformed data

        Raises:
            Exception: If any transformation fails
        """
        logger.info(f"Starting pipeline: {self.name}")

        result = data

        for index, transformation in enumerate(self.transformations):
            try:
                logger.info(f"Applying transformation {index + 1}/{len(self.transformations)}")
                result = transformation(result)
            except Exception as e:
                # Errors should never pass silently
                logger.error(f"Transformation {index + 1} failed: {e}")
                raise

        logger.info(f"Pipeline {self.name} completed successfully")
        return result

# Transformation functions (simple is better than complex)
def filter_adults(users: List[Dict]) -> List[Dict]:
    """Filter users to include only adults (age > 18)."""
    return [user for user in users if user.get('age', 0) > 18]

def add_full_name(users: List[Dict]) -> List[Dict]:
    """Add full_name field by combining first and last name."""
    for user in users:
        first = user.get('first_name', '')
        last = user.get('last_name', '')
        user['full_name'] = f"{first} {last}".strip()
    return users

def validate_emails(users: List[Dict]) -> List[Dict]:
    """Filter users with valid email addresses."""
    return [user for user in users if '@' in user.get('email', '')]

# Use the pipeline (there should be one obvious way to do it)
if __name__ == "__main__":
    # Sample data
    raw_users = [
        {'first_name': 'Alice', 'last_name': 'Smith', 'age': 25, 'email': '[email protected]'},
        {'first_name': 'Bob', 'last_name': 'Jones', 'age': 16, 'email': '[email protected]'},
        {'first_name': 'Carol', 'last_name': 'Davis', 'age': 30, 'email': 'invalid'},
    ]

    # Build and execute pipeline
    pipeline = (
        DataPipeline("User Processing")
        .add_transformation(filter_adults)
        .add_transformation(validate_emails)
        .add_transformation(add_full_name)
    )

    processed_users = pipeline.execute(raw_users)

    for user in processed_users:
        print(user)

Testing Pythonic Code

The Zen of Python also applies to testing. Here's how to write Pythonic tests:

import unittest
from typing import List

# Code to test
def calculate_statistics(numbers: List[float]) -> dict:
    """
    Calculate basic statistics for a list of numbers.

    Args:
        numbers: List of numbers

    Returns:
        Dictionary with mean, median, and range

    Raises:
        ValueError: If numbers list is empty
    """
    if not numbers:
        raise ValueError("Cannot calculate statistics for empty list")

    sorted_numbers = sorted(numbers)
    mean = sum(numbers) / len(numbers)
    median = sorted_numbers[len(sorted_numbers) // 2]
    range_value = max(numbers) - min(numbers)

    return {
        'mean': mean,
        'median': median,
        'range': range_value
    }

# Pythonic tests
class TestStatistics(unittest.TestCase):
    """Test suite for calculate_statistics function."""

    def test_basic_statistics(self):
        """Test basic statistical calculations."""
        # Explicit test data
        numbers = [1, 2, 3, 4, 5]

        result = calculate_statistics(numbers)

        # Explicit assertions
        self.assertEqual(result['mean'], 3.0)
        self.assertEqual(result['median'], 3)
        self.assertEqual(result['range'], 4)

    def test_empty_list_raises_error(self):
        """Test that empty list raises ValueError."""
        # Errors should never pass silently
        with self.assertRaises(ValueError) as context:
            calculate_statistics([])

        self.assertIn("empty list", str(context.exception))

    def test_single_number(self):
        """Test statistics with single number."""
        result = calculate_statistics([42])

        self.assertEqual(result['mean'], 42)
        self.assertEqual(result['median'], 42)
        self.assertEqual(result['range'], 0)

if __name__ == '__main__':
    unittest.main()

Zen of Python Best Practices Checklist

When writing Python code, use this checklist to ensure you're following the Zen:

  1. Is your code beautiful? - Properly formatted, well-spaced, aesthetically pleasing
  2. Is your code explicit? - Clear variable names, obvious intentions
  3. Is your code simple? - Solves the problem without unnecessary complexity
  4. Is your code flat? - Minimal nesting, early returns
  5. Is your code sparse? - Not too much logic crammed into one line
  6. Is your code readable? - Easy to understand without comments
  7. Are you handling errors properly? - No silent failures, explicit error handling
  8. Is there one obvious way? - Using Pythonic idioms and patterns
  9. Are you using namespaces? - Proper module/class organization

Tools to Help Write Pythonic Code

Several tools can help you write code that follows the Zen of Python:

# Install code quality tools
pip install black isort flake8 pylint mypy

# Format code beautifully
black your_script.py

# Sort imports properly
isort your_script.py

# Check code style
flake8 your_script.py

# Deeper code analysis
pylint your_script.py

# Type checking
mypy your_script.py

Common Anti-Patterns to Avoid

1. Using * imports

# Bad - pollutes namespace
from module import *

# Good - explicit imports
from module import specific_function, SpecificClass

2. Mutable default arguments

# Bad - mutable default argument
def add_item(item, items=[]):
    items.append(item)
    return items

# Good - use None as default
def add_item(item, items=None):
    if items is None:
        items = []
    items.append(item)
    return items

3. Not using context managers

# Bad - manual file handling
f = open('file.txt')
data = f.read()
f.close()

# Good - context manager handles cleanup
with open('file.txt') as f:
    data = f.read()

Conclusion

The Zen of Python isn't just a collection of abstract principles—it's a practical guide for writing better Python code. By following these 19 principles, you'll write code that is:

  • More readable and maintainable
  • Easier to debug and test
  • More consistent with Python community standards
  • More enjoyable for others (and future you) to work with

Remember, you can always access the Zen of Python by typing import this in your Python interpreter. Keep these principles in mind as you develop, and you'll naturally write more Pythonic code.

For more Python best practices and tutorials, check out:

Happy Pythonic coding!

Related Articles