Welcome to "100+ Tips for Coding with Python." This document will guide you through the tips, tricks, and best practices that will help you elevate your Python skills and stand out as an exceptional developer. Let's get started!
Tip 1
Did you know? Python's built-in enumerate()
function can help you keep track of the index while iterating over a list or other iterable:
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits, start=1):
print(f"{index}. {fruit}")
Tip 2
Common misconception: Many people think that using a list comprehension is always faster than using a for loop. However, this is not always true, especially when the loop body is complex. In such cases, using a regular for loop with proper variable names can make your code more readable and maintainable.
Tip 3
It's always better to use Python's built-in with
statement when working with file operations, as it automatically handles opening and closing the file, even if an exception occurs:
with open('file.txt', 'r') as file:
content = file.read()
# Do something with content
Tip 4
Use the timeit
module to benchmark the performance of your code snippets:
import timeit
def slow_function():
return [x**2 for x in range(1000)]
def fast_function():
return (x**2 for x in range(1000))
slow_time = timeit.timeit(slow_function, number=1000)
fast_time = timeit.timeit(fast_function, number=1000)
print(f"slow_function: {slow_time} seconds")
print(f"fast_function: {fast_time} seconds")
Tip 5
Never use mutable objects, such as lists or dictionaries, as default arguments for functions. Instead, use None
and create the object inside the function:
def bad_append(item, default_list=[]):
default_list.append(item)
return default_list
def good_append(item, default_list=None):
if default_list is None:
default_list = []
default_list.append(item)
return default_list
Tip 6
Use the collections
module for specialized container data types, like Counter
, defaultdict
, and OrderedDict
:
from collections import Counter
word_list = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
word_count = Counter(word_list)
print(word_count)
# Output: Counter({'apple': 3, 'banana': 2, 'orange': 1})
Tip 7
Did you know? You can use the else
clause with a for
loop to execute code only when the loop completes without encountering a break
statement:
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(f"{n} equals {x} * {n//x}")
break
else:
print(f"{n} is a prime number")
Tip 8
It is always better to use the str.join()
method instead of the +
operator for concatenating strings:
words = ['This', 'is', 'a', 'sentence']
bad_sentence = words[0]
for word in words[1:]:
bad_sentence += ' ' + word
good_sentence = ' '.join(words)
Tip 9
Common misconception: Many people use is
and ==
interchangeably. However, is
checks for object identity (whether two objects are the same), while ==
checks for object equality (whether two objects have the same value)
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # Output: True
print(a is b) # Output: False
Tip 10
Never use the global
keyword unless absolutely necessary. Instead, use function parameters and return values to pass data between functions:
# Bad practice
global_variable = 0
def increment_bad():
global global_variable
global_variable += 1
# Good practice
def increment_good(variable):
return variable + 1
local_variable = 0
local_variable = increment_good(local_variable)
Tip 11
Did you know? You can use the zip()
function to iterate over multiple lists simultaneously:
names = ['Alice', 'Bob', 'Charlie']
ages = [30, 25, 35]
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
Tip 12
Always use the isinstance()
function to check if an object is an instance of a particular class:
def greet(person):
if isinstance(person, str):
print(f"Hello, {person}!")
else:
print("Hello, stranger!")
greet("Alice")
Tip 13
Common misconception: Many people think that they can't use try-except
with an else
clause. However, the else
clause is executed when no exception is raised:
try:
result = 1 / 2
except ZeroDivisionError:
print("Can't divide by zero!")
else:
print(f"The result is {result}")
Tip 14
Never use the eval()
function to evaluate untrusted input, as it can lead to security vulnerabilities:
# Bad practice
user_input = "os.system('rm -rf /')"
result = eval(user_input) # This could be dangerous!
# Good practice
from ast import literal_eval
user_input = "[1, 2, 3]"
result = literal_eval(user_input) # Safe and returns a list
Tip 15
It's always better to use list slicing to reverse a list:
original_list = [1, 2, 3, 4, 5]
reversed_list = original_list[::-1]
print(reversed_list)
# Output: [5, 4, 3, 2, 1]
Tip 16
Did you know? You can use the any()
and all()
functions to check if any or all elements in an iterable satisfy a given condition:
numbers = [2, 4, 6, 8, 10]
even_check = lambda x: x % 2 == 0
print(any(even_check(x) for x in numbers)) # Output: True
print(all(even_check(x) for x in numbers)) # Output: True
Tip 17
Always use context managers when working with external resources, like network connections or database sessions, to ensure proper cleanup:
from contextlib import contextmanager
@contextmanager
def database_connection(url):
connection = create_database_connection(url)
try:
yield connection
finally:
connection.close()
with database_connection('example-db-url') as connection:
result = connection.execute('SELECT * FROM users')
Tip 18
Common misconception: Many people think that they should always use list comprehensions instead of map()
and filter()
. However, in some cases, map()
and filter()
can be more readable and performant, especially when combined with pre-defined functions:
numbers = [1, 2, 3, 4, 5]
# Using map() and filter()
squares = map(lambda x: x**2, numbers)
even_squares = filter(lambda x: x % 2 == 0, squares)
# Using list comprehensions
squares = [x**2 for x in numbers]
even_squares = [x for x in squares if x % 2 == 0]
Tip 19
Never use mutable data structures as keys in dictionaries. Instead, use tuples or namedtuples:
# Bad practice
bad_key = [1, 2, 3]
bad_dict = {bad_key: "value"} # Raises TypeError: unhashable type: 'list'
# Good practice
good_key = (1, 2, 3)
good_dict = {good_key: "value"}
Tip 20
It's always better to use Python's built-in sum()
function to calculate the sum of a list of numbers:
numbers = [1, 2, 3, 4, 5]
# Inefficient
total = 0
for number in numbers:
total += number
# Efficient
total = sum(numbers)
Tip 21
Did you know? You can use the dir()
function to list all attributes and methods of an object, which is helpful for exploring libraries and understanding available functionality:
import datetime
attributes = dir(datetime)
print(attributes)
Tip 22
Always use list slicing to create shallow copies of lists instead of the assignment operator, which creates references to the same list object:
original_list = [1, 2, 3, 4, 5]
# Bad practice
bad_copy = original_list
# Good practice
good_copy = original_list[:]
Tip 23
Common misconception: Many people think that pass
and continue
are interchangeable. However, pass
is a no-operation statement, while continue
skips the rest of the loop and moves on to the next iteration:
for i in range(5):
if i == 2:
pass
print(i) # Output: 0, 1, 2, 3, 4
for i in range(5):
if i == 2:
continue
print(i) # Output: 0, 1, 3, 4
Tip 24
Never use the C-style for
loop in Python; instead, use the built-in range()
function
# Bad practice
i = 0
while i < 5:
print(i)
i += 1
# Good practice
for i in range(5):
print(i)
Tip 25
It's always better to use the os.path
module when working with file paths, as it ensures cross-platform compatibility:
import os
path = os.path.join("folder1", "folder2", "file.txt")
print(path)
Tip 26
Did you know? You can use the round()
function with an optional second argument to round a floating-point number to a specified number of decimal places:
pi = 3.1415926535
rounded_pi = round(pi, 3)
print(rounded_pi) # Output: 3.142
Tip 27
Always use the functools.lru_cache
decorator to cache the results of expensive function calls, especially when dealing with recursive functions:
import functools
@functools.lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n - 1) + fib(n - 2)
print(fib(100)) # Output: 354224848179261915075
Tip 28
Common misconception: Many people think that *args
and **kwargs
are special syntax in Python. However, they are just conventions for passing a variable number of positional and keyword arguments to a function:
def foo(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
foo(1, 2, 3, a=4, b=5)
Tip 29
Never use the assert
statement to validate user input, as it can be disabled globally with the -O
(optimize) command line switch. Instead, use explicit error checking and raise exceptions:
def divide(a, b):
# Bad practice
assert b != 0, "Division by zero!"
# Good practice
if b == 0:
raise ValueError("Division by zero!")
return a / b
Tip 30
It's always better to use the itertools
module for efficient iteration and combination of data:
import itertools
letters = ['a', 'b', 'c']
# Generate all possible permutations of length 2
perms = list(itertools.permutations(letters, 2))
print(perms) # Output: [('a', 'b'), ('a', 'c'), ('b', 'a'), ('b', 'c'), ('c', 'a'), ('c', 'b')]
Tip 31
Did you know? You can use the setattr()
and getattr()
functions to dynamically set and get attributes of an object:
class MyClass:
pass
obj = MyClass()
# Dynamically set attribute
setattr(obj, 'attribute', 42)
# Dynamically get attribute
value = getattr(obj, 'attribute')
print(value) # Output: 42
Tip 32
Always use the __name__
attribute to determine if a Python script is being run as the main program or being imported as a module:
def main():
print("This script is being run as the main program.")
if __name__ == '__main__':
main()
Tip 33
Common misconception: Many people believe that importing a module multiple times will cause the module to be executed multiple times. However, Python caches imported modules, so they are only executed once:
import my_module # my_module is executed
import my_module # my_module is NOT executed again
Tip 34
Never use floating-point numbers for exact decimal arithmetic, as they can lead to rounding errors. Instead, use the decimal
module:
from decimal import Decimal
# Using float (inaccurate)
result1 = 0.1 + 0.2
print(result1) # Output: 0.30000000000000004
# Using Decimal (accurate)
result2 = Decimal('0.1') + Decimal('0.2')
print(result2) # Output: 0.3
Tip 35
It's always better to use the re
module for working with regular expressions, as it provides powerful pattern matching and string manipulation tools:
import re
pattern = r'\\d+'
string = 'There are 42 apples and 15 oranges.'
numbers = re.findall(pattern, string)
print(numbers) # Output: ['42', '15']
Tip 36
Did you know? You can use the pprint
module to pretty-print complex data structures, making them easier to read and understand:
from pprint import pprint
data = {
'name': 'Alice',
'age': 30,
'skills': ['Python', 'Java', 'C++'],
'address': {
'street': '123 Main St',
'city': 'New York',
'state': 'NY',
'zip': '10001'
}
}
pprint(data)
Tip 37
Always use the __str__()
and __repr__()
magic methods to define human-readable and unambiguous string representations of your custom classes:
class MyClass:
def __str__(self):
return "This is a human-readable description of MyClass."
def __repr__(self):
return f"MyClass(id={id(self)})"
obj = MyClass()
print(str(obj)) # Output: This is a human-readable description of MyClass.
print(repr(obj)) # Output: MyClass(id=140123456789012)
Tip 38
Common misconception: Many people think that using try-except
blocks can make their code slower. However, when exceptions are rare, using try-except
can actually make your code faster and more readable:
def get_value(dictionary, key):
# Slower and less readable when exceptions are rare
if key in dictionary:
return dictionary[key]
else:
return None
# Faster and more readable when exceptions are rare
try:
return dictionary[key]
except KeyError:
return None
Tip 39
Never use os.system()
or os.popen()
to run external commands, as they can lead to security vulnerabilities. Instead, use the subprocess
module:
import subprocess
output = subprocess.run(['ls', '-l'], capture_output=True, text=True)
print(output.stdout)
Tip 40
It's always better to use the logging
module instead of print()
statements for diagnostic output, as it provides more control over log levels, output formats, and destination:
import logging
logging.basicConfig(level=logging.INFO)
logging.debug("This is a debug message.")
logging.info("This is an info message.")
logging.warning("This is a warning message.")
logging.error("This is an error message.")
logging.critical("This is a critical message.")
Tip 41
Did you know? You can use the collections.Counter
class to easily count occurrences of elements in a list:
from collections import Counter
colors = ['red', 'blue', 'red', 'green', 'blue', 'blue']
count = Counter(colors)
print(count) # Output: Counter({'blue': 3, 'red': 2, 'green': 1})
Tip 42
Always use the with
statement to open and close files, as it ensures that the file is properly closed after the block is executed:
# Good practice
with open('file.txt', 'r') as file:
data = file.read()
# Bad practice
file = open('file.txt', 'r')
data = file.read()
file.close()
Tip 43
Common misconception: Many people think that list comprehensions are always faster than equivalent for
loops. However, list comprehensions can be slower when performing complex operations or when side effects are involved:
import timeit
# List comprehension
def squares_list_comprehension(n):
return [x**2 for x in range(n)]
# For loop
def squares_for_loop(n):
squares = []
for x in range(n):
squares.append(x**2)
return squares
print(timeit.timeit(lambda: squares_list_comprehension(1000), number=1000))
print(timeit.timeit(lambda: squares_for_loop(1000), number=1000))
Tip 44
Never hardcode sensitive information, like API keys and passwords, in your source code. Instead, use environment variables or configuration files:
import os
api_key = os.environ.get('MY_API_KEY')
Tip 45
It's always better to use the unittest
module for writing and running tests, as it provides a comprehensive framework for test discovery, execution, and reporting:
import unittest
def add(a, b):
return a + b
class TestAddition(unittest.TestCase):
def test_addition(self):
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(-1, 1), 0)
self.assertEqual(add(0, 0), 0)
if __name__ == '__main__':
unittest.main()
Tip 46
Did you know? You can use the timeit
module to measure the execution time of code snippets, which is useful for comparing the performance of different solutions:
import timeit
def slow_function():
return sum(range(100000))
def fast_function():
return sum(x for x in range(100000))
slow_time = timeit.timeit(slow_function, number=1000)
fast_time = timeit.timeit(fast_function, number=1000)
print(f"Slow function: {slow_time:.6f}s")
print(f"Fast function: {fast_time:.6f}s")
Tip 47
Always use the zip()
function to iterate over multiple lists in parallel, as it can simplify your code and improve readability:
names = ['Alice', 'Bob', 'Charlie']
ages = [30, 25, 40]
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
Tip 48
Common misconception: Many people think that the global interpreter lock (GIL) in CPython prevents true parallelism. While this is true for threads, you can still achieve parallelism using the multiprocessing
module:
import multiprocessing
def work(n):
return n * n
if __name__ == "__main__":
with multiprocessing.Pool(processes=4) as pool:
results = pool.map(work, range(10))
print(results) # Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Tip 49
Never use mutable default arguments in function definitions, as they are shared across all function calls. Instead, use None
as a default value and create a new object inside the function:
# Bad practice
def bad_append(item, lst=[]):
lst.append(item)
return lst
# Good practice
def good_append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
Tip 50
It's always better to use the csv
module for reading and writing CSV files, as it provides built-in support for parsing and formatting CSV data:
import csv
data = [['Name', 'Age'], ['Alice', 30], ['Bob', 25], ['Charlie', 40]]
# Writing CSV data
with open('data.csv', 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
writer.writerows(data)
# Reading CSV data
with open('data.csv', 'r', newline='') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
print(row)
Tip 51
Did you know? You can use the any()
and all()
functions to check if any or all elements of an iterable satisfy a condition:
numbers = [2, 4, 6, 8, 10]
# Check if any number is odd
any_odd = any(x % 2 == 1 for x in numbers)
print(any_odd) # Output: False
# Check if all numbers are even
all_even = all(x % 2 == 0 for x in numbers)
print(all_even) # Output: True
Tip 52
Always use f-strings for string formatting, as they are more concise and easier to read than other formatting methods:
name = 'Alice'
age = 30
# Using f-strings (recommended)
formatted_string = f"{name} is {age} years old."
print(formatted_string) # Output: Alice is 30 years old.
# Using str.format() (not recommended)
formatted_string = "{} is {} years old.".format(name, age)
print(formatted_string) # Output: Alice is 30 years old.
Tip 53
Common misconception: Many people think that lambda
functions are more efficient than regular functions. However, lambda
functions are just a syntactic sugar for simple anonymous functions and do not provide any performance benefits:
# Using lambda function
square_lambda = lambda x: x * x
# Using regular function
def square_function(x):
return x * x
Tip 54
Never use the eval()
and exec()
functions without sanitizing input, as they can execute arbitrary code and introduce security vulnerabilities:
# Bad practice
eval("os.system('rm -rf /')") # This could delete your entire filesystem!
# Good practice
from ast import literal_eval
safe_result = literal_eval("{'a': 1, 'b': 2}")
print(safe_result) # Output: {'a': 1, 'b': 2}
Tip 55
It's always better to use the pickle
module for object serialization, as it provides a simple and efficient way to save and load Python objects:
import pickle
data = {'a': 1, 'b': 2, 'c': 3}
# Save data to a file
with open('data.pkl', 'wb') as file:
pickle.dump(data, file)
# Load data from a file
with open('data.pkl', 'rb') as file:
loaded_data = pickle.load(file)
print(loaded_data) # Output: {'a': 1, 'b': 2, 'c': 3}
Tip 56
Did you know? You can use the itertools
module to work with iterators more efficiently and create complex iterator pipelines:
import itertools
# Generate an iterator that yields the first 10 even numbers
evens = itertools.islice(itertools.count(0, 2), 10)
for even in evens:
print(even)
Tip 57
Always use the isinstance()
function to check if an object is an instance of a particular class or a tuple of classes:
class MyClass:
pass
obj = MyClass()
# Good practice
if isinstance(obj, MyClass):
print("obj is an instance of MyClass")
# Bad practice
if type(obj) == MyClass:
print("obj is an instance of MyClass")
Tip 58
Common misconception: Many people think that lists are faster than sets for membership testing. However, sets are faster for this operation because they use hashing, whereas lists require a linear search:
import timeit
my_list = list(range(1000))
my_set = set(range(1000))
# Testing list membership
list_time = timeit.timeit(lambda: 999 in my_list, number=1000)
print(f"List time: {list_time:.6f}s")
# Testing set membership
set_time = timeit.timeit(lambda: 999 in my_set, number=1000)
print(f"Set time: {set_time:.6f}s")
Tip 59
Never use mutable objects as keys in dictionaries, as they can lead to unpredictable behavior. Instead, use immutable objects like strings, numbers, or tuples:
# Bad practice
bad_dict = {['a', 'b']: 1} # Raises TypeError: unhashable type: 'list'
# Good practice
good_dict = {('a', 'b'): 1}
Tip 60
It's always better to use the functools.lru_cache
decorator to memoize function results, as it can significantly speed up recursive functions or functions with expensive computations:
import functools
@functools.lru_cache(maxsize=None)
def fibonacci(n):
if n <= 1:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # Output: 354224848179261915075
Tip 61
Did you know? You can use the pathlib
module to work with file paths in a platform-independent and more readable way:
from pathlib import Path
# Create a new directory
new_dir = Path('my_directory')
new_dir.mkdir(exist_ok=True)
# List files in the directory
for file in new_dir.iterdir():
print(file)
# Read the content of a file
file_path = new_dir / 'file.txt'
file_content = file_path.read_text()
Tip 62
Always use the enumerate()
function when you need both the index and the value of an iterable in a loop:
fruits = ['apple', 'banana', 'cherry']
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
Tip 63
Common misconception: Many people think that the range()
function returns a list. However, range()
returns a specialized range
object that is an immutable sequence type:
python
my_range = range(10)
print(type(my_range)) # Output: <class 'range'>
Tip 64
Never use the +
operator to concatenate strings inside a loop, as it creates a new string object for each iteration. Instead, use the join()
method:
# Bad practice
def bad_concat(strings):
result = ''
for s in strings:
result += s
return result
# Good practice
def good_concat(strings):
return ''.join(strings)
Tip 65
It's always better to use the json
module for encoding and decoding JSON data, as it provides built-in support for handling various data types and ensures proper encoding and decoding:
import json
data = {
'name': 'Alice',
'age': 30,
'skills': ['Python', 'Java', 'C++'],
}
# Serialize data to a JSON string
json_string = json.dumps(data)
print(json_string)
# Deserialize data from a JSON string
loaded_data = json.loads(json_string)
print(loaded_data)
Tip 66
Did you know? You can use the re
module to work with regular expressions, which can help you perform complex string manipulations and validations:
import re
text = "Python is a powerful language. I love Python!"
pattern = r"Python"
# Find all occurrences of the pattern
matches = re.findall(pattern, text)
print(matches) # Output: ['Python', 'Python']
# Replace all occurrences of the pattern
replaced_text = re.sub(pattern, "Java", text)
print(replaced_text) # Output: "Java is a powerful language. I love Java!"
Tip 67
Always use the sorted()
function or the list.sort()
method to sort sequences, as they provide a stable and efficient sorting algorithm:
numbers = [4, 2, 9, 7, 5, 1, 8, 3, 6]
# Using sorted() to create a new sorted list
sorted_numbers = sorted(numbers)
print(sorted_numbers) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Using list.sort() to sort the list in-place
numbers.sort()
print(numbers) # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Tip 68
Common misconception: Many people think that using a try
-except
block can slow down code execution. However, the performance impact is negligible if no exception is raised:
import timeit
def no_error():
try:
return 1 + 1
except ValueError:
pass
def no_try_except():
return 1 + 1
try_time = timeit.timeit(no_error, number=1000000)
normal_time = timeit.timeit(no_try_except, number=1000000)
print(f"Try-except time: {try_time:.6f}s")
print(f"Normal time: {normal_time:.6f}s")
Tip 69
Never use the os.system()
function for executing shell commands, as it is less secure and less flexible than the subprocess
module:
import subprocess
# Good practice
result = subprocess.run(['echo', 'Hello, World!'], capture_output=True, text=True)
print(result.stdout.strip()) # Output: Hello, World!
# Bad practice
import os
os.system('echo "Hello, World!"')
Tip 70
It's always better to use the argparse
module to handle command-line arguments, as it provides a more robust and user-friendly way to define and parse arguments:
import argparse
parser = argparse.ArgumentParser(description="A simple command-line program.")
parser.add_argument('-n', '--name', type=str, default='World', help="Your name")
args = parser.parse_args()
print(f"Hello, {args.name}!")
Tip 71
Did you know? You can use the concurrent.futures
module to simplify parallelism in your code, allowing you to execute tasks concurrently using threads or processes:
import concurrent.futures
def square(x):
return x * x
with concurrent.futures.ThreadPoolExecutor() as executor:
numbers = [1, 2, 3, 4, 5]
results = list(executor.map(square, numbers))
print(results) # Output: [1, 4, 9, 16, 25]
Tip 72
Always use context managers when working with resources, like files or sockets, to ensure that resources are properly acquired and released:
# Good practice
with open('file.txt', 'r') as file:
content = file.read()
# Bad practice
file = open('file.txt', 'r')
content = file.read()
file.close()
Tip 73
Common misconception: Many people believe that decorators always need to be defined using nested functions. However, you can also use classes as decorators by implementing the __call__()
method:
class MyDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print("Decorator called!")
return self.func(*args, **kwargs)
@MyDecorator
def my_function():
print("Hello, World!")
my_function()
Tip 74
Never use a wildcard import (from module import *
) because it can lead to name clashes and make it difficult to determine the origin of functions and classes:
# Bad practice
from os import *
# Good practice
import os
Tip 75
It's always better to use the collections.namedtuple()
factory function to create simple classes with named fields, as it can make your code more readable and self-documenting:
from collections import namedtuple
Person = namedtuple('Person', ['name', 'age'])
alice = Person('Alice', 30)
print(alice) # Output: Person(name='Alice', age=30)
print(alice.name) # Output: Alice
Tip 76
Did you know? You can use the heapq
module to work with heaps, which are efficient data structures for finding the smallest (or largest) elements in a collection:
import heapq
data = [3, 5, 1, 2, 6, 8, 7]
# Create a min-heap
min_heap = data.copy()
heapq.heapify(min_heap)
# Find the smallest element
smallest = heapq.heappop(min_heap)
print(smallest) # Output: 1
# Create a max-heap
max_heap = [-x for x in data]
heapq.heapify(max_heap)
# Find the largest element
largest = -heapq.heappop(max_heap)
print(largest) # Output: 8
Tip 77
Always use generator expressions or generator functions to create large sequences, as they can save memory by generating elements on-the-fly instead of storing them in memory:
# Using a generator expression
even_numbers = (x * 2 for x in range(1000))
# Using a generator function
def even_numbers_gen(n):
for i in range(n):
yield i * 2
even_numbers = even_numbers_gen(1000)
Tip 78
Common misconception: Many people believe that the time.sleep()
function guarantees an exact delay. However, it only guarantees a minimum delay, as the actual delay might be longer due to system factors:
import time
start_time = time.time()
time.sleep(2) # Sleep for at least 2 seconds
end_time = time.time()
print(f"Elapsed time: {end_time - start_time:.2f}s") # Output: Elapsed time: 2.00+s
Tip 79
Never use mutable default arguments in functions, as they persist across function calls and can lead to unexpected behavior:
# Bad practice
def bad_append(item, lst=[]):
lst.append(item)
return lst
print(bad_append(1)) # Output: [1]
print(bad_append(2)) # Output: [1, 2] (expected: [2])
# Good practice
def good_append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(good_append(1)) # Output: [1]
print(good_append(2)) # Output: [2]
Tip 80
It's always better to use the unittest
module to write and run tests, as it provides a consistent framework for organizing and running tests:
import unittest
def add(a, b):
return a + b
class TestAddition(unittest.TestCase):
def test_add(self):
self.assertEqual(add(1, 2), 3)
self.assertEqual(add(-1, 1), 0)
if __name__ == '__main__':
unittest.main()
Tip 81
Did you know? You can use the queue
module to implement thread-safe queues for communication between threads:
import threading
import queue
def producer(q):
for i in range(5):
q.put(i)
print(f"Produced: {i}")
def consumer(q):
while True:
item = q.get()
if item is None:
break
print(f"Consumed: {item}")
q = queue.Queue()
producer_thread = threading.Thread(target=producer, args=(q,))
consumer_thread = threading.Thread(target=consumer, args=(q,))
producer_thread.start()
consumer_thread.start()
producer_thread.join()
q.put(None) # Signal the consumer to exit
consumer_thread.join()
Tip 82
Always use the logging
module to handle logging in your applications, as it provides a flexible and customizable way to manage logging output:
import logging
logging.basicConfig(level=logging.INFO)
logging.debug("This is a debug message")
logging.info("This is an info message")
logging.warning("This is a warning message")
logging.error("This is an error message")
logging.critical("This is a critical message")
Tip 83
Common misconception: Many people believe that lambda functions are faster than regular functions. However, lambda functions have the same performance characteristics as regular functions:
import timeit
square_lambda = lambda x: x * x
def square_func(x):
return x * x
lambda_time = timeit.timeit(lambda: square_lambda(5), number=1000000)
func_time = timeit.timeit(lambda: square_func(5), number=1000000)
print(f"Lambda time: {lambda_time:.6f}s")
print(f"Function time: {func_time:.6f}s")
Tip 84
Never use the eval()
function to evaluate user-supplied strings, as it can lead to security vulnerabilities. Instead, use safer alternatives like ast.literal_eval()
for simple expressions:
from ast import literal_eval
# Good practice
result = literal_eval("1 + 2")
print(result) # Output: 3
# Bad practice
result = eval("1 + 2")
print(result) # Output: 3
Tip 85
It's always better to use the asyncio
module for asynchronous programming, as it can help you write more efficient and responsive applications:
import asyncio
async def print_every_second():
for i in range(5):
print(f"{i}s")
await asyncio.sleep(1)
async def main():
await print_every_second()
asyncio.run(main())
Tip 86
Did you know? You can use the itertools
module to work with iterators in a memory-efficient way, as it provides a set of high-performance tools for creating and manipulating iterators:
import itertools
# Infinite iterator that cycles through the given elements
cycled = itertools.cycle('ABC')
for _ in range(10):
print(next(cycled), end=' ') # Output: A B C A B C A B C A
print()
# Iterator that generates the Cartesian product of input iterables
product = itertools.product('AB', repeat=3)
for item in product:
print(item, end=' ')
print()
Tip 87
Always use the __name__
attribute to check if a Python script is being executed as the main module, as it can help you avoid unintended side effects when your script is imported:
def main():
print("Hello, World!")
if __name__ == '__main__':
main()
Tip 88
Common misconception: Many people believe that using list comprehensions is always faster than using loops. While list comprehensions are often faster due to their internal optimizations, the performance difference can be negligible for small data sets:
import timeit
def square_list_comprehension():
return [x * x for x in range(100)]
def square_loop():
result = []
for x in range(100):
result.append(x * x)
return result
lc_time = timeit.timeit(square_list_comprehension, number=100000)
loop_time = timeit.timeit(square_loop, number=100000)
print(f"List comprehension time: {lc_time:.6f}s")
print(f"Loop time: {loop_time:.6f}s")
Tip 89
Never use the global
keyword to share data between functions, as it can make your code hard to understand and maintain. Instead, use function arguments and return values to share data:
# Bad practice
counter = 0
def bad_increment():
global counter
counter += 1
# Good practice
def good_increment(counter):
return counter + 1
counter = good_increment(counter)
Tip 90
It's always better to use the timeit
module to measure the execution time of code snippets, as it provides a more accurate and reliable way to measure time:
import timeit
def code_to_time():
return sum(range(1000))
elapsed_time = timeit.timeit(code_to_time, number=1000)
print(f"Elapsed time: {elapsed_time:.6f}s")
Tip 91
Did you know? Python 3.8 introduced the "walrus operator" (:=
), which allows you to assign values to variables as part of an expression:
# Without walrus operator
n = 10
total = 0
while n > 0:
square = n * n
total += square
n -= 1
# With walrus operator
n = 10
total = 0
while (square := n * n) > 0:
total += square
n -= 1
Tip 92
Always take advantage of the improved syntax for positional-only parameters introduced in Python 3.8. This can help you make your API more robust by preventing users from passing arguments by name:
def greet(name, /, greeting="Hello"):
return f"{greeting}, {name}!"
# Valid call
print(greet("John")) # Output: Hello, John!
# Invalid call
print(greet(name="John")) # Raises TypeError
Tip 93
Common misconception: Many people believe that the math.isqrt()
function, introduced in Python 3.8, is equivalent to int(math.sqrt(x))
. While both return the integer part of the square root, math.isqrt()
is more efficient and accurate for large integers:
import math
x = 2 ** 100
# Using math.isqrt()
isqrt_x = math.isqrt(x)
print(isqrt_x)
# Using int(math.sqrt())
sqrt_x = int(math.sqrt(x))
print(sqrt_x)
Tip 94
Python 3.9 introduced the |
operator for merging dictionaries, allowing you to merge two dictionaries into a new one more easily:
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}
# Merging dictionaries in Python 3.9+
merged_dict = dict1 | dict2
print(merged_dict) # Output: {'a': 1, 'b': 3, 'c': 4}
# Merging dictionaries in older Python versions
merged_dict = {**dict1, **dict2}
print(merged_dict) # Output: {'a': 1, 'b': 3, 'c': 4}
Tip 95
In Python 3.9, you can use the str.removeprefix()
and str.removesuffix()
methods to remove specified prefixes and suffixes from strings:
filename = "image.jpg"
# Remove prefix
if filename.startswith("image"):
filename_without_prefix = filename.removeprefix("image")
print(filename_without_prefix) # Output: .jpg
# Remove suffix
if filename.endswith(".jpg"):
filename_without_suffix = filename.removesuffix(".jpg")
print(filename_without_suffix) # Output: image
Tip 96
Did you know? Python 3.10 introduced the match
statement, which simplifies complex conditional structures, making your code more readable:
def get_shape_description(shape):
match shape:
case 'circle':
return "A circle has no sides."
case 'triangle':
return "A triangle has 3 sides."
case 'square':
return "A square has 4 sides."
case _:
return "Unknown shape."
print(get_shape_description("circle")) # Output: A circle has no sides.
Tip 97
Always use the new zoneinfo
module introduced in Python 3.9 for working with time zones, as it provides an up-to-date and platform-independent implementation of the IANA time zone database:
from datetime import datetime
from zoneinfo import ZoneInfo
dt = datetime(2021, 9, 1, tzinfo=ZoneInfo("UTC"))
print(dt) # Output: 2021-09-01 00:00:00+00:00
dt_new_york = dt.astimezone(ZoneInfo("America/New_York"))
print(dt_new_york) # Output: 2021-08-31 20:00:00-04:00
Tip 98
In Python 3.10, you can use the case
statement with sequence patterns to match elements in a sequence, making it easier to work with complex data structures:
def process_data(data):
match data:
case []:
return "Empty list"
case [x]:
return f"List with one element: {x}"
case [x, y]:
return f"List with two elements: {x}, {y}"
case [x, y, *rest]:
return f"List with at least three elements: {x}, {y}, and {len(rest)} more"
case _:
return "Not a list"
print(process_data([1, 2, 3, 4])) # Output: List with at least three elements: 1, 2, and 2 more
Tip 99
Never use the __future__
module without understanding the implications, as it can change the behavior of your code to match future Python versions. However, if you are planning to migrate to a newer version of Python, it can help you identify and fix potential issues:
from __future__ import annotations
def greet(name: str) -> str:
return f"Hello, {name}!"
print(greet("Alice")) # Output: Hello, Alice!
Tip 100
It's always better to use f-strings, introduced in Python 3.6, for string formatting, as they provide a more concise and readable way to embed expressions inside string literals:
name = "Alice"
age = 30
# Using f-strings
formatted_string = f"{name} is {age} years old."
print(formatted_string) # Output: Alice is 30 years old.
# Using str.format()
formatted_string = "{} is {} years old.".format(name, age)
print(formatted_string) # Output: Alice is 30 years old.
Tip 101
Always use the pathlib
module, introduced in Python 3.4, for working with file system paths, as it provides an object-oriented and cross-platform approach to handling paths:
from pathlib import Path
# Creating a path
path = Path("data/subfolder/file.txt")
# Checking if a file or directory exists
print(path.exists()) # Output: True or False
# Get the parent directory
parent = path.parent
print(parent) # Output: data/subfolder
# Join paths
new_path = path.parent / "new_file.txt"
print(new_path) # Output: data/subfolder/new_file.txt
Tip 102
Did you know? Python 3.7 introduced the dataclasses
module, which simplifies the creation of classes used primarily for storing data:
from dataclasses import dataclass
@dataclass
class Point:
x: float
y: float
p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)
print(p1) # Output: Point(x=1.0, y=2.0)
print(p1 == p2) # Output: True
Tip 103
Common misconception: Many people believe that using try
-except
blocks for control flow is slow. However, the performance impact is minimal when exceptions are not raised frequently. Use try
-except
when it makes your code more readable:
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return None
result = safe_divide(10, 0)
print(result) # Output: None
Tip 104
Never use mutable default arguments, such as lists or dictionaries, in function definitions, as they can cause unexpected behavior:
# Bad practice
def bad_append(item, lst=[]):
lst.append(item)
return lst
# Good practice
def good_append(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(good_append(1)) # Output: [1]
print(good_append(2)) # Output: [2]
Tip 105
It's always better to use the functools.lru_cache
decorator, introduced in Python 3.2, to cache the results of expensive function calls, as it can significantly improve the performance of your code:
import functools
@functools.lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2) print(fib(100))
# Output: 354224848179261915075