Safer Python Codes


Avoid Mutable Default Arguments


# Bad 
def append_to(val, lst=[]):
    lst.append(val)
    return lst
 
# Good 
def append_to(val, lst=None):
    if lst is None:
        lst = []
    lst.append(val)
    return lst
  • Why: Avoids shared state(in this case lst) across calls, this ensures functions behave consistently without modifying shared state

Use Docstrings


# Bad
def compute():
    pass
 
# Good
def compute():
    """<Summary of the function>
 
	Args:
	    <input_name> (<input_type>): <input_description>
 
    Returns:
        <output_type>: <output_description>
 
	Examples(optional):
 
	Notes(optional):
    """
    pass
  • Why: Improves code discoverability and auto-documentation

Use Type Hints


# Bad
def add(a, b):
    return a + b
 
# Good
def add(a: int, b: int) -> int:
    return a + b
  • Why: Improves code clarity and enables static analysis

Keyword Arguments over Positional Arguments for Optional Parameters


# Bad
def create_user(name, age, is_admin=False):
    ...
 
create_user("Alice", 30, True)
 
 
# Good (Use keyword for clarity and flexibility)
def create_user(name, age, is_admin=False):
    ...
create_user("Alice", 30, is_admin=True) # An admin
create_user("Alice", 30) # Not an Admin
  • Why: Improves readability and reduces errors, especially when functions have multiple optional parameters. Keyword arguments make the intent clearer and future-proof the code when defaults or parameter order change

Avoid Magic Numbers or Strings


# Bad
if status == 3:
    ...
 
# Good
PENDING_APPROVAL = 3
if status == PENDING_APPROVAL:
    ...
  • Why: Increases readability and maintainability

Use Comprehensions


# Bad
squares = []
for x in range(10):
    squares.append(x * x)
 
# Good
squares = [x * x for x in range(10)]
  • Why: More concise and expressive

Avoid Excessive Nesting


# Bad
def check(user):
    if user:
        if user.active:
            if user.has_permission():
                return True
    return False
 
# Good
def check(user):
    if not user or not user.active or not user.has_permission():
        return False
    return True
  • Why: Reduces indentation and improves clarity

Limit Function Responsibility


# Bad
def process(data):
    # does 10 things
    pass
 
# Good
def validate(): ...
def store(): ...
def notify(): ...
  • Why: Improves testability and separation of concerns

Use get() with Dicts to Avoid Errors


# Bad
name = user['name'] # returns KeyError if name not found
 
# Good
name = user.get('name') # returns None if name not found
  • Why: Prevents KeyError exception if key is missing

Format Strings with f-Strings


# Bad
name = "Alice"
greeting = "Hello, %s" % name
 
# Good
name = "Alice"
greeting = f"Hello, {name}"
  • Why: Cleaner and easier to read

Use Constants for Configurable Values


# Bad
retry_limit = 3
timeout = 5
 
# Good
RETRY_LIMIT = 3
TIMEOUT = 5
  • Why: Makes the intention of fixed values explicit

Use is for None


# Bad
if var == None:
 
# Good
if var is None:
  • Why: is is the preferred way to compare against None in Python

Use List Unpacking Instead of Indexing


# Bad
first = point[0]
second = point[1]
 
# Good
first, second = point
  • Why: Cleaner and more readable

Use with for Resources


# Bad
f = open('file.txt')
data = f.read()
f.close()
 
# Good
with open('file.txt') as f:
    data = f.read()
  • Why: Ensures auto cleanup and safety

Catch Specific Exceptions


# Bad
try:
    risky_operation()
except:
    handle_error()
 
# Good
try:
    risky_operation()
except ValueError:
    handle_error()
  • Why: Avoids masking unexpected bugs and improves clarity

Don’t Catch Exception Unless You Have To


# Bad
try:
    risky()
except:
    handle()
 
# Good
try:
    risky()
except Exception:
    handle()
  • Why: Catches only actual exceptions, avoids swallowing keyboard interrupts and system exits

Prefer Tuple Unpacking for Swapping


# Bad 
temp = a
a = b
b = temp
 
# Good
a, b = b, a
  • Why: More concise and idiomatic

Prefer Exceptions to Returning None for Errors


# Bad
def find(id):
    if id not in db:
        return None
    return db[id]
 
# Good
def find(id):
    if id not in db:
        raise KeyError(f"{id} not found")
    return db[id]
  • Why: Makes error handling explicit

Avoid Long Parameter Lists


# Bad
def create(x, y, z, a, b, c):
    ...
 
# Good
def create(config):
    ...

Prefer Boolean Flags Over Multiple ifs


# Bad
if mode == 'read':
    read()
elif mode == 'write':
    write()
 
# Good
modes = {
    'read': read,
    'write': write
}
modes.get(mode, default_func)()
  • Why: Easier to extend and avoids duplication

Use join() for String Concatenation


# Bad
s = ""
for part in parts:
    s += part
 
# Good
s = "".join(parts)
  • Why: Faster and avoids repeated memory allocation