Python is a high-level, interpreted programming language known for its readability and versatility. It emphasizes code readability with its notable use of significant indentation and supports multiple programming paradigms.
Use this file to discover all available pages before exploring further.
Python Anti-Patterns Overview
Python, despite its elegance and readability, has several common anti-patterns that can lead to bugs, performance issues, and maintenance problems. Here are the most important anti-patterns to avoid when writing Python code.
Using Mutable Default Arguments
# Anti-pattern: Mutable default argumentdef append_to(element, to=[]): to.append(element) return to# This will not work as expectedappend_to(1) # Returns [1]append_to(2) # Returns [1, 2], not [2]# Better approachdef append_to(element, to=None): if to is None: to = [] to.append(element) return to
Default arguments in Python are evaluated only once at function definition time, not each time the function is called. This can lead to unexpected behavior with mutable objects like lists, dictionaries, or sets.
Not Using Context Managers
# Anti-pattern: Not using context managersf = open('file.txt', 'r')data = f.read()# What if an exception occurs here?f.close()# Better approach: Use context managerswith open('file.txt', 'r') as f: data = f.read()# File is automatically closed, even if an exception occurs
Always use context managers (with statement) for resource management to ensure resources are properly cleaned up, even if exceptions occur.
Using Wildcard Imports
# Anti-pattern: Wildcard importsfrom module import *# Better approach: Import only what you needfrom module import specific_function, AnotherFunction# Orimport module
Wildcard imports pollute the namespace, can lead to name conflicts, and make it difficult to track where functions and classes are coming from.
Ignoring Exceptions Too Broadly
# Anti-pattern: Catching all exceptionstry: do_something()except Exception: pass # Silently ignore all errors# Better approach: Catch specific exceptionstry: do_something()except ValueError as e: logger.error(f"Value error occurred: {e}")except IOError as e: logger.error(f"IO error occurred: {e}")
Catching exceptions too broadly can hide bugs and make debugging difficult. Always catch specific exceptions and handle them appropriately.
Modifying Lists During Iteration
# Anti-pattern: Modifying a list during iterationnumbers = [1, 2, 3, 4, 5]for i in range(len(numbers)): if numbers[i] % 2 == 0: numbers.remove(numbers[i]) # This will skip elements# Better approach: Create a new list or use list comprehensionnumbers = [1, 2, 3, 4, 5]numbers = [n for n in numbers if n % 2 != 0]# Orodd_numbers = []for n in numbers: if n % 2 != 0: odd_numbers.append(n)
Modifying a list while iterating over it can lead to skipped elements or index errors. Create a new list or use list comprehension instead.
Using type() Instead of isinstance()
# Anti-pattern: Using type() for type checkingif type(obj) == list: # Do something with list# Better approach: Use isinstance() for type checkingif isinstance(obj, list): # Do something with list
Using type() doesn’t account for inheritance. isinstance() checks if an object is an instance of a class or any of its subclasses, which is usually what you want.
Not Using Generator Expressions
# Anti-pattern: Loading entire dataset into memorydata = [process(x) for x in large_dataset]# Better approach: Use generator expressionsdata = (process(x) for x in large_dataset)
List comprehensions load the entire result into memory. For large datasets, use generator expressions to process items one at a time, saving memory.
Using Global Variables
# Anti-pattern: Using global variablescounter = 0def increment_counter(): global counter counter += 1# Better approach: Use class or function scopeclass Counter: def __init__(self): self.value = 0 def increment(self): self.value += 1
Global variables make code harder to test, debug, and maintain. Use class attributes or function parameters instead.
Not Using enumerate()
# Anti-pattern: Manual indexingitems = ['a', 'b', 'c']for i in range(len(items)): print(f"Item {i}: {items[i]}")# Better approach: Use enumerate()for i, item in enumerate(items): print(f"Item {i}: {item}")
When you need both the index and value during iteration, use enumerate() instead of manual indexing for cleaner, more readable code.
String Concatenation in Loops
# Anti-pattern: String concatenation in loopsresult = ""for i in range(1000): result += str(i)# Better approach: Use join() or list comprehensionresult = ''.join(str(i) for i in range(1000))
String concatenation in loops is inefficient because strings are immutable. Use join() on a generator expression or list comprehension instead.
Not Using Proper Data Structures
# Anti-pattern: Using plain tuples for structured dataperson = ('John', 'Doe', 30)print(person[0], person[1]) # Unclear what these indices mean# Better approach: Use namedtuple or dataclassfrom collections import namedtuplePerson = namedtuple('Person', ['first_name', 'last_name', 'age'])person = Person('John', 'Doe', 30)print(person.first_name, person.last_name)# Or with dataclasses (Python 3.7+)from dataclasses import dataclass@dataclassclass Person: first_name: str last_name: str age: int
Use appropriate data structures like namedtuples or dataclasses for better code readability and maintainability.
Reinventing the Wheel
# Anti-pattern: Implementing functionality that exists in standard librarydef get_unique_items(items): unique = [] for item in items: if item not in unique: unique.append(item) return unique# Better approach: Use built-in setdef get_unique_items(items): return list(set(items))
Python’s standard library is extensive. Before implementing functionality, check if it already exists in the standard library or a well-maintained package.
Not Using Virtual Environments
# Anti-pattern: Installing packages globally# pip install some-package # Affects all projects# Better approach: Use virtual environments# python -m venv myenv# source myenv/bin/activate # On Windows: myenv\Scripts\activate# pip install some-package # Only affects this environment
Always use virtual environments to isolate project dependencies and avoid conflicts between different projects.
Using Bare Except Clauses
# Anti-pattern: Bare except clausetry: do_something()except: # Catches all exceptions, including KeyboardInterrupt, SystemExit handle_error()# Better approach: Specify exceptions or use Exceptiontry: do_something()except Exception as e: # Still broad, but doesn't catch KeyboardInterrupt, SystemExit handle_error(e)
Bare except: clauses catch all exceptions, including those you might not want to catch like KeyboardInterrupt and SystemExit. Always specify which exceptions to catch.
Not Using pathlib
# Anti-pattern: Using os.path for file operationsimport osfile_path = os.path.join('dir', 'subdir', 'file.txt')if os.path.exists(file_path): with open(file_path, 'r') as f: content = f.read()# Better approach: Use pathlibfrom pathlib import Pathfile_path = Path('dir') / 'subdir' / 'file.txt'if file_path.exists(): content = file_path.read_text()
For Python 3.4+, use pathlib for file operations. It provides an object-oriented interface and simplifies many common file operations.
Not Using f-strings
# Anti-pattern: Old-style string formattingname = "John"age = 30message = "Hello, %s! You are %d years old." % (name, age)# Ormessage = "Hello, {}! You are {} years old.".format(name, age)# Better approach: Use f-stringsmessage = f"Hello, {name}! You are {age} years old."
For Python 3.6+, use f-strings for string formatting. They are more readable, concise, and often faster than older formatting methods.
Using eval() or exec()
# Anti-pattern: Using eval for dynamic code executionuser_input = "2 + 2"result = eval(user_input) # Security risk!# Better approach: Use safer alternativesimport astdef safe_eval(expr): try: return ast.literal_eval(expr) # Only evaluates literals except (ValueError, SyntaxError): raise ValueError("Invalid expression")
Avoid eval() and exec() as they can execute arbitrary code and pose security risks. Use safer alternatives like ast.literal_eval() when needed.
Not Using Proper Logging
# Anti-pattern: Using print for debuggingdef process_data(data): print("Processing data:", data) # Process data... print("Done processing")# Better approach: Use the logging moduleimport logginglogging.basicConfig(level=logging.INFO)def process_data(data): logging.info(f"Processing data: {data}") # Process data... logging.info("Done processing")
Use the logging module instead of print() statements for debugging and monitoring. It offers more flexibility, including log levels, formatting, and output destinations.
For command-line applications, use the argparse module instead of manually parsing sys.argv. It provides help messages, type conversion, and validation.
Not Using Proper Testing
# Anti-pattern: Manual testingdef add(a, b): return a + b# Manual testresult = add(2, 3)if result != 5: print("Test failed!")# Better approach: Use unittest or pytestimport unittestclass TestAddFunction(unittest.TestCase): def test_add(self): self.assertEqual(add(2, 3), 5) self.assertEqual(add(-1, 1), 0)if __name__ == '__main__': unittest.main()
Use proper testing frameworks like unittest or pytest instead of manual testing. They provide test discovery, assertions, setup/teardown, and reporting.