In Python, lists are mutable, ordered sequences of elements, and their creation is a fundamental operation with several nuanced approaches. The method chosen impacts not only readability but also performance and clarity of intent.

Literal Syntax: The Square Bracket []

The most common and Pythonic method for creating a list is using the literal syntax with square brackets. Elements are placed inside the brackets, separated by commas. This syntax is direct, highly readable, and efficient because it is a built-in operation the Python interpreter handles without an explicit function call.

# Creating an empty list
empty_list = []

# Creating a list with heterogeneous elements
my_list = [1, "hello", 3.14, True, [1, 2, 3]]

# Creating a list with duplicate elements
fruits = ["apple", "banana", "apple", "cherry"]
print(fruits)  # Output: ['apple', 'banana', 'apple', 'cherry']

This approach is preferred for its simplicity. It is important to remember that the elements within a list literal are evaluated at the time of creation. If a variable is used, its current value is captured, not its name.

x = 10
list_a = [x, 20]  # list_a becomes [10, 20]
x = 99            # Changing x does NOT affect the list
print(list_a)     # Output: [10, 20]

The list() Constructor

The list() built-in function is a versatile constructor that creates a list from an iterable object. Its primary use is to convert other iterable types (like tuples, strings, sets, or dictionaries) into a list. When called with no arguments, it creates an empty list, functionally equivalent to [].

# Creating a list from a string (iterates over characters)
list_from_string = list("hello")
print(list_from_string)  # Output: ['h', 'e', 'l', 'l', 'o']

# Creating a list from a tuple
list_from_tuple = list((1, 2, 3))
print(list_from_tuple)  # Output: [1, 2, 3]

# Creating a list from a range object
list_from_range = list(range(5))
print(list_from_range)  # Output: [0, 1, 2, 3, 4]

# Creating a list from a dictionary (iterates over keys)
list_from_dict = list({'a': 1, 'b': 2})
print(list_from_dict)   # Output: ['a', 'b']

A common pitfall arises when trying to create a list of a predefined size with a default value. Using the * operator on a literal list with a mutable element (like another list) creates multiple references to the same object, not distinct objects.

# INCORRECT: Creates three references to the same inner list.
list_of_lists = [[]] * 3
list_of_lists[0].append(99)
print(list_of_lists)  # Output: [[99], [99], [99]] (Not what was intended!)

# CORRECT: Use a list comprehension to create distinct objects.
list_of_lists = [[] for _ in range(3)]
list_of_lists[0].append(99)
print(list_of_lists)  # Output: [[99], [], []]

List Comprehensions: Concise and Expressive

List comprehensions provide a compact, readable, and often more efficient way to create lists based on existing iterables or sequences. They combine a for loop, an optional conditional filter, and an expression into a single, declarative line inside square brackets. They are not just syntactic sugar; they are optimized for performance, as the iteration and appending happen in underlying C code, making them faster than an equivalent for loop with .append().

The basic syntax is: [expression for item in iterable if condition]

# Create a list of squares for numbers 0 to 9
squares = [x**2 for x in range(10)]
print(squares)  # Output: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# Create a list of even squares only
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print(even_squares)  # Output: [0, 4, 16, 36, 64]

# Nested loops in a comprehension: flattening a 2D list
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
print(flattened)  # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

While powerful, comprehensions can become less readable if overly complex. A best practice is to avoid excessive nesting or complicated expressions. If the logic for generating the list element is multi-step or requires significant control flow, a traditional for loop with .append() is often more maintainable, even if slightly less performant. The goal is to prioritize clarity unless performance is a proven bottleneck.