Skip to content

🐍 A collection of useful tips for beginners and experienced python programmers.

Notifications You must be signed in to change notification settings

raiyanyahya/100-Tips-on-Coding-in-Python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 
Β 
Β 

Repository files navigation

100+ Tips on Coding with Python

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

About

🐍 A collection of useful tips for beginners and experienced python programmers.

Resources

Stars

Watchers

Forks

Releases

No releases published

Sponsor this project

 

Packages

No packages published