27 Python Coding Tips: Lessons I Wish I Knew When I Started Coding

Livia Ellen
Python in Plain English
11 min readDec 26, 2023

--

Photo by Chris Ried on Unsplash

Starting my coding journey, delving into the world of Python felt a bit challenging. While I had the basics of coding down, figuring out how to make my code efficient was a bit like solving a puzzle. Back then, I would often refer to the Zen of Python — it’s really nice, however, it tends to be general and vague in my opinion. I needed more concrete examples, and that’s precisely why I’m crafting this guide.

Interestingly, I’ve observed a shift in how people approach learning to code, especially those who started after the ChatGPT era. Folks who learned during the ChatGPT era seem to excel in fixing errors and debugging. On the flip side, those who coded before GPT are skilled at analyzing answers, utilizing resources like Stack Overflow and documentation — maybe with a touch of copy-pasting expertise! ;)

Photo by Kelly Sikkema on Unsplash

In my seven years of coding in Python, I’ve come across some concepts that made a significant difference. In this article, I’d like to share 27 straightforward coding tips based on my own experiences. These tips not only sped up my coding but also made my code cleaner and more understandable. Stick around as I share these nuggets, and who knows, you might find some that really resonate with your coding journey!

1. F-Strings: Dynamic String Formatting

Tip: Use f-strings (Python 3.6+) for concise and dynamic string formatting.

Pros:

  • Concise and readable syntax.
  • Easily embed expressions inside strings.

Cons:

  • Limited to Python 3.6 and above.
  • Be Careful with security vulnerability; SQL Injection

Example:

name = "John"
age = 25
message = f"My name is {name}, and I am {age} years old."

2. Decorators: Enhance Functionality Dynamically

Tip: Harness decorators to dynamically extend or modify functions, enhancing code modularity.

Pros:

  • Provides a clean way to extend functions’ behavior.
  • Enhances code reusability.

Cons:

  • Overusing decorators can make code harder to understand.

Example:

import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"{func.__name__} executed in {end_time - start_time} seconds")
return result
return wrapper
@timer_decorator
def example_function():
# Function logic here

3. Leveraging the help() Function for Python Mastery

Tip: Utilize the help() function as a powerful tool to gain insights into Python modules, functions, and objects, facilitating better understanding and usage.

Pros:

  • Instant Documentation: Quickly access documentation for any Python object, module, or function within your code.
  • Interactive Learning: Ideal for exploring and learning about unfamiliar modules or functions directly from the Python interpreter or script.

Cons:

  • Limited Detail: The information provided by help() may sometimes be concise, and more detailed documentation might be necessary for complex topics. The effectiveness of help() depends on the presence and quality of docstrings in the code.

Example:

# Example: Using the help() function
def calculate_square(number):
"""
Calculates the square of a given number.Parameters:
- number (int): The input number.
Returns:
- int: The square of the input number.
"""
return number ** 2
# Accessing help for the calculate_square function
help(calculate_square)

4. List Comprehensions: Compact List Creation

Tip: Embrace list comprehensions for a concise and readable way to create lists, reducing the need for multi-line loops.

Pros:

  • Enhances readability by condensing list creation logic.
  • Often improves performance compared to traditional loops.

Cons:

  • Avoid nested list comprehensions for better readability, especially with complex logic.

Example:

# Finding squares of even numbers in a range
squares = [x**2 for x in range(10) if x % 2 == 0]

5. The else Clause in Loops

Tip: Utilize the else clause in loops to execute code when the loop completes naturally.

Pros:

  • Allows execution of code when a loop completes naturally without a break statement.
  • Ideal for scenarios where a specific action should follow a successful loop.

Cons:

  • Often overlooked or misunderstood, leading to potential logic errors.

Example:

# Finding prime numbers using the else clause
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
break
else:
print(n, "is a prime number.")

6. Lambda Functions: Quick and Anonymous Functions

Tip: Use lambda functions for quick and anonymous function definitions.

Pros:

  • Concise one-liners for simple functions.
  • No need for a formal function definition.

Cons:

  • Limited to single expressions; not suitable for complex logic.
  • Can reduce code readability if overused.

Example:

# Adding two numbers with a lambda function
add_numbers = lambda x, y: x + y
result = add_numbers(3, 5)

7. Pythonic Iteration with enumerate and zip

Tip: Embrace the enumerate() and zip() functions for more Pythonic iteration over sequences.

Pros:

  • enumerate(): Simplifies iteration with both index and value.
  • zip(): Facilitates parallel iteration over multiple lists.

Cons:

  • None significant; improves code clarity and conciseness.

Example:

# Pythonic iteration using enumerate and zip
names = ["Alice", "Bob", "Charlie"]
ages = [25, 30, 22]
# Enumerate for index and value
for index, name in enumerate(names):
print(f"Person {index + 1}: {name}")
# Zip for parallel iteration
for name, age in zip(names, ages):
print(f"{name} is {age} years old.")

8. *args and **kwargs: Flexible Function Arguments

Tip: Employ *args and **kwargs to pass a variable number of arguments to a function, providing flexibility for a broad range of use cases.

Pros:

  • Ideal for handling variable numbers of arguments.
  • Enables the creation of versatile functions and wrappers.

Cons:

  • Requires careful documentation as the function signature may not reveal all possible arguments.

Example:

# A function that multiplies all given arguments
def multiply(*args):
result = 1
for num in args:
result *= num
return result

9. Graceful Error Handling with try and except

Tip: Incorporate the try and except blocks for graceful error handling, enhancing the robustness of your code.

Pros:

  • Error Resilience, protects your program from crashing due to unexpected errors.
  • Enhanced Debugging: Provides insights into what went wrong, aiding in effective debugging.
  • User-Friendly: Allows you to communicate specific error messages to users for a better experience.

Cons:

  • Overhead: In some cases, using try and except might introduce a slight performance overhead.
  • Potential Oversights: Incorrectly catching or suppressing errors could mask underlying issues.

Example:

# Example: Graceful error handling with try and except
def divide_numbers(a, b):
try:
result = a / b
print(f"The result of {a} divided by {b} is: {result}")
except ZeroDivisionError:
print("Cannot divide by zero! Please provide a non-zero denominator.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
else:
print("Division successful!")
# Testing the function
divide_numbers(10, 2) # Normal division
divide_numbers(5, 0) # Division by zero
divide_numbers("a", 2) # Unexpected error (TypeError)

10. List Slicing: Powerful and Expressive

Tip: Utilize list slicing for concise and expressive operations on lists.

Pros:

  • Simplifies operations like extracting sublists, reversing, or skipping elements.
  • Enhances code readability and reduces the need for explicit loops.

Cons:

  • Overusing complex slicing may affect code readability.

Example:

# Extracting a sublist from index 2 to 5
original_list = [1, 2, 3, 4, 5, 6, 7]
sublist = original_list[2:6]

11. Generators: Memory-Efficient Iteration

Tip: Leverage generators to iterate over large datasets without loading everything into memory.

Pros:

  • Efficiently handles large datasets.
  • Produces items on-the-fly, saving memory.

Cons:

  • Generators are one-time iterators; once consumed, they can’t be reused.

Example:

# Fibonacci sequence generator
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b

12. Assertions: Debugging with Confidence

Tip: Make use of assertions during development to confirm assumptions about the code state.

Pros:

  • Enhances code reliability by catching potential issues early.
  • Provides a means to confirm assumptions about the code.

Cons:

  • Overuse in production code can impact performance.

Example:

# Assertion for checking if a variable is positive
num = -5
assert num > 0, "Number must be positive"

13. Deepcopy vs. Shallow Copy: Duplicate Wisely

Tip: Understand the difference between deepcopy and shallow copy for handling mutable objects.

Pros:

  • Shallow copy: Creates a new collection with references to the same objects.
  • Deepcopy: Generates an independent clone of the original object and all its contents.

Cons:

  • Using a shallow copy when a deepcopy is needed can lead to unintended modifications of the original data.

Example:

# Duplicating a nested list with both shallow and deep copies
import copy
original = [[1, 2, 3], [4, 5, 6]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

14. Random Module: Embrace Unpredictability

Tip: Embrace the random module for introducing variability or randomness into your code.

Pros:

  • Useful for simulations, games, or any scenario requiring unpredictability.
  • Offers a variety of randomization functions.

Cons:

  • Results are not truly random; they are pseudorandom.

Example:

import random
# Generating a random number between 1 and 10
random_number = random.randint(1, 10)

15. Defaultdict: Simplify Dictionary Manipulations

Tip: Employ defaultdict from the collections module to simplify dictionary manipulations.

Pros:

  • Simplifies code by providing default values for nonexistent keys.
  • Eliminates explicit key existence checks.

Cons:

  • Requires importing the collections module.

Example:

from collections import defaultdict
word = "pythonic"
letter_count = defaultdict(int)
for letter in word:
letter_count[letter] += 1

16. Walrus Operator (:=): Inline Assignments for Efficiency

Tip: Embrace the walrus operator (Python 3.8+) for inline assignments within expressions.

Pros:

  • Efficiently assigns values and uses them within the same expression.
  • Reduces redundancy in certain conditions.

Cons:

  • Overusing it can make code harder to read for those not familiar with the operator.

Example:

# Reading lines from a file until a blank line is found
with open('file.txt', 'r') as f:
while (line := f.readline().strip()):
print(line)

17. Type Hinting: Enhance Code Clarity

Tip: Embrace type hinting (Python 3.5+) to enhance code clarity, especially in larger projects.

Pros:

  • Improves code readability and maintainability.
  • Enables better IDE support and static type checking.

Cons:

  • Python remains a dynamically typed language; type hints are optional and not enforced — it’s for the human eyes ;) .

Example:

# Function with type hints
def greet(name: str) -> str:
return f"Hello, {name}!"

18. Namedtuples: Self-Documenting Data Structures

Tip: Use namedtuples to create simple, self-documenting data structures.

Pros:

  • Provides a lightweight, immutable data structure.
  • Enhances code readability by giving names to each field.

Cons:

  • Immutable; cannot be modified after creation.
  • For mutable structures, consider using data classes (Python 3.7+).

Example:

# Creating a namedtuple for a person
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
alice = Person(name="Alice", age=30)

19. Zipping and Unzipping Lists: Combine and Unpack Sequences

Tip: Use zip() to combine multiple iterables, making it easier to loop through multiple lists in parallel.

Pros:

  • Simplifies iterating over multiple lists simultaneously.
  • Handy for tasks where items from different lists need to be processed together.

Cons:

  • zip() stops at the shortest input list; for different-sized iterables, consider using itertools.zip_longest().

Example:

# Matching user inputs with corresponding answers in a quiz
names = ["Alice", "Bob"]
scores = [85, 92]
for name, score in zip(names, scores):
print(f"{name}: {score}")

20. Dictionaries — get() and setdefault(): Graceful Key Handling

Tip: Enhance dictionary manipulation with get() and setdefault() methods.

Pros:

  • get(): Retrieves a key's value, providing a default if the key doesn't exist.
  • setdefault(): Sets a default value if the key doesn't exist, preventing redundant key checks.

Cons:

  • Overlooking these methods can lead to redundant code to check key existence.

Example:

data = {"name": "Alice"}
age = data.get("age", 30)
data.setdefault("country", "USA")

21. The __main__ Guard: Script Execution Control

Tip: Use the if __name__ == "__main__": guard to control code execution when a script is run directly.

Pros:

  • Ensures that certain code only runs when a script is executed directly, not when imported.
  • Useful for utility scripts that can be both imported for functions or run directly for tasks.

Cons:

  • Forgetting to use this guard can lead to unexpected behavior when the module is imported.

Example:

if __name__ == "__main__":
print("This script is being run directly!")

22. Virtual Environments: Isolate Dependencies for Project-Specific Development

Tip: Utilize virtual environments to isolate project-specific dependencies, preventing conflicts with system-wide packages.

Pros:

  • Ensures a clean and isolated environment for each project.
  • Facilitates the management of dependencies and avoids conflicts.

Cons:

  • Forgetting to activate the virtual environment can lead to unintentional package installations in the global Python environment.

Example:

# Creating and activating a virtual environment
python -m venv my_project_env
source my_project_env/bin/activate

23. The Asterisk (*) Operator: Versatile and Powerful

Tip: Explore the versatility of the asterisk (*) operator for packing and unpacking, keyword argument unpacking, and repetition.

Pros:

  • Efficiently unpacks collections into separate elements.
  • Facilitates dynamic argument handling in functions.

Cons:

  • Overuse can reduce code readability, especially with multiple unpackings in a row.

Example:

# Passing a dynamic list of values to a function expecting separate arguments
def func(a, b, c):
return a + b + c

values = [1, 2, 3]
print(func(*values))

24. Context Managers (with statement): Resource Management Simplicity

Tip: Utilize context managers with the with statement for efficient resource management.

Pros:

  • Ensures proper setup and teardown of resources.
  • Improves code readability and reduces the likelihood of resource leaks.

Cons:

  • Forgetting to use the with statement when beneficial can lead to resource-related issues.

Example:

# Opening and reading a file using a context manager
with open('file.txt', 'r') as f:
content = f.read()

25. Python’s Underscore (_) Uses: Versatility in Naming and Loops

Tip: Leverage the underscore (_) as a throwaway variable in loops.

Pros:

  • Denotes “protected” variables in naming conventions.
  • Reuses the last result in REPL environments.
  • Acts as a throwaway variable in loops when the loop variable is not needed.

Cons:

  • Varied uses might be confusing, especially for new coders.

Example:

# Iterating a specific number of times without needing the loop counter
for _ in range(5):
print("Hello, World!")

26. Map, Filter, and Reduce: Functional Programming in Python

Tip: Incorporate map(), filter(), and reduce() for a functional approach to processing collections, reducing the need for explicit loops.

Pros:

  • map(): Applies a function to every item in a collection.
  • filter(): Selects items based on a predicate.
  • reduce(): Cumulatively applies a function, reducing the sequence to a single value.

Cons:

  • Note that in Python 3.x, map() and filter() return iterators; convert them to a list if needed.

Example:

# Using map() to convert strings to uppercase
names = ["alice", "bob", "charlie"]
upper_names = list(map(str.upper, names))

27. Merging Dictionaries: Simplify Dictionary Operations

Tip: Use the update() method or the {**dict1, **dict2} syntax to merge dictionaries.

Pros:

  • Simplifies combining the contents of multiple dictionaries.
  • Offers flexibility in choosing the merging approach.

Cons:

  • Overusing this approach may lead to unexpected results when dealing with nested dictionaries.

Example:

# Merging dictionaries using the update() method
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
dict1.update(dict2)

Congratulations! You made it to the end!

Wrapping up these Python coding tips, I hope you picked up some handy tricks for your coding toolkit. Whether you’re a coding pro or just starting, keeping things fresh with new tips is always a good move. Give these a spin, see what works for you, and enjoy the ride as your Python coding game gets a little stronger. Happy coding!

Let’s Connect

  • 🌐 Follow me on LinkedIn
  • 📬 Subscribe to my Medium newsletter for email updates!
  • 🚀 Please clap, underline, share, and comment on this article if you found this useful
  • 👏 Oh Hey! you can clap more than one in 1 article — that will help me get more coffee to write articles with ;)
  • ☕ or just buy me a real coffee

Stay in the loop for more coding insights and updates! Happy coding!

Ellen

PlainEnglish.io 🚀

Thank you for being a part of the In Plain English community! Before you go:

--

--

Data Scientist, Machine Learning and Augmented Reality, simplifying complex codes. Teach DS & AI @ Le Wagon 🪂 Paraglider https://www.linkedin.com/in/liviaellen