
A soufflé is a baked egg dish that originated in France in the 18th century. The process of making an elegant and delicious French soufflé is complex, and in the past, it was typically only prepared by professional French pastry chefs. However, with pre-made soufflé mixes now widely available in supermarkets, this classic French dish has found its way into the kitchens of countless households.
Python is like the pre-made soufflé mixes in programming. Many studies have consistently shown that Python is the most popular programming language among developers, and this advantage will continue to expand in 2025. Python stands out compared to languages like C, C++, Java, and Julia because it’s highly readable and expressive, flexible and dynamic, beginner-friendly yet powerful. These characteristics make Python the most suitable programming language for people even without programming basics. The following features distinguish Python from other programming languages:
- Dynamic Typing
- List Comprehensions
- Generators
- Argument Passing and Mutability
These features reveal Python’s intrinsic nature as a programming language. Without this knowledge, you’ll never truly understand Python. In today’s article, I will elaborate how Python excels over other programming languages through these features.
Dynamic Typing
For most programming languages like Java or C++, explicit data type declarations are required. But when it comes to Python, you don’t have to declare the type of a variable when you create one. This feature in Python is called dynamic typing, which makes Python flexible and easy to use.
List Comprehensions
List comprehensions are used to generate lists from other lists by applying functions to each element in the list. They provide a concise way to apply loops and optional conditions in a list.
For example, if you’d like to create a list of squares for even numbers between 0 and 9, you can use JavaScript, a regular loop in Python and Python’s list comprehension to achieve the same goal.
JavaScript
let squares = Array.from({ length: 10 }, (_, x) => x) // Create array [0, 1, 2, ..., 9]
.filter(x => x % 2 === 0) // Filter even numbers
.map(x => x ** 2); // Square each number
console.log(squares); // Output: [0, 4, 16, 36, 64]
Regular Loop in Python
squares = []
for x in range(10):
if x % 2 == 0:
squares.append(x**2)
print(squares)
Python’s List Comprehension
squares = [x**2 for x in range(10) if x % 2 == 0]print(squares)
All the three sections of code above generate the same list [0, 4, 16, 36, 64], but Python’s list comprehension is the most elegant because the syntax is concise and clearly express the intent while the Python function is more verbose and requires explicit initialization and appending. The syntax of JavaScript is the least elegant and readable because it requires chaining methods of using Array.from, filter, and map. Both Python function and JavaScript function are not intuitive and cannot be read as natural language as Python list comprehension does.
Generator
Generators in Python are a special kind of iterator that allow developers to iterate over a sequence of values without storing them all in memory at once. They are created with the yield keyword. Other programming languages like C++ and Java, though offering similar functionality, don’t have built-in yield keyword in the same simple, integrated way. Here are several key advantages that make Python Generators unique:
- Memory Efficiency: Generators yield one value at a time so that they only compute and hold one item in memory at any given moment. This is in contrast to, say, a list in Python, which stores all items in memory.
- Lazy Evaluation: Generators enable Python to compute values only as needed. This “lazy” computation results in significant performance improvements when dealing with large or potentially infinite sequences.
- Simple Syntax: This might be the biggest reason why developers choose to use generators because they can easily convert a regular function into a generator without having to manage state explicitly.
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(100):
print(next(fib))
The example above shows how to use the yield keyword when creating a sequence. For the memory usage and time difference between the code with and without Generators, generating 100 Fibonacci numbers can hardly see any differences. But when it comes to 100 million numbers in practice, you’d better use generators because a list of 100 million numbers could easily strain many system resources.
Argument Passing and Mutability
In Python, we don’t really assign values to variables; instead, we bind variables to objects. The result of such an action depends on whether the object is mutable or immutable. If an object is mutable, changes made to it inside the function will affect the original object.
def modify_list(lst):
lst.append(4)
my_list = [1, 2, 3]
modify_list(my_list)
print(my_list) # Output: [1, 2, 3, 4]
In the example above, we’d like to append ‘4’ to the list my_list which is [1,2,3]. Because lists are mutable, the behavior append operation changes the original list my_list without creating a copy.
However, immutable objects, such as integers, floats, strings, tuples and frozensets, cannot be changed after creation. Therefore, any modification results in a new object. In the example below, because integers are immutable, the function creates a new integer rather than modifying the original variable.
def modify_number(n):
n += 10
return n
a = 5
new_a = modify_number(a)
print(a) # Output: 5
print(new_a) # Output: 15
Python’s argument passing is sometimes described as “pass-by-object-reference” or “pass-by-assignment.” This makes Python unique because Python pass references uniformly (pass-by-object-reference) while other languages need to differentiate explicitly between pass-by-value and pass-by-reference. Python’s uniform approach is simple yet powerful. It avoids the need for explicit pointers or reference parameters but requires developers to be mindful of mutable objects.
With Python’s argument passing and mutability, we can enjoy the following benefits in coding:
- Memory Efficiency: It saves memory by passing references instead of making full copies of objects. This especially benefits code development with large data structures.
- Performance: It avoids unnecessary copies and thus improves the overall coding performance.
- Flexibility: This feature provides convenience for updating data structure because developers don’t need to explicitly choose between pass-by-value and pass-by-reference.
However, this characteristic of Python forces developers to carefully choose between mutable and immutable data types and it also brings more complex debugging.
So is Python Really Simple?
Python’s popularity results from its simplicity, memory efficiency, high performance, and beginner-friendiness. It’s also a programming language that looks most like a human’s natural language, so even people who haven’t received systematic and holistic programming training are still able to understand it. These characteristics make Python a top choice among enterprises, academic institutes, and government organisations.
For example, when we’d like to filter out the the “completed” orders with amounts greater than 200, and update a mutable summary report (a dictionary) with the total count and sum of amounts for an e-commerce company, we can use list comprehension to create a list of orders meeting our criteria, skip the declaration of variable types and make changes of the original dictionary with pass-by-assignment.
import random
import time
def order_stream(num_orders):
"""
A generator that yields a stream of orders.
Each order is a dictionary with dynamic types:
- 'order_id': str
- 'amount': float
- 'status': str (randomly chosen among 'completed', 'pending', 'cancelled')
"""
for i in range(num_orders):
order = {
"order_id": f"ORD{i+1}",
"amount": round(random.uniform(10.0, 500.0), 2),
"status": random.choice(["completed", "pending", "cancelled"])
}
yield order
time.sleep(0.001) # simulate delay
def update_summary(report, orders):
"""
Updates the mutable summary report dictionary in-place.
For each order in the list, it increments the count and adds the order's amount.
"""
for order in orders:
report["count"] += 1
report["total_amount"] += order["amount"]
# Create a mutable summary report dictionary.
summary_report = {"count": 0, "total_amount": 0.0}
# Use a generator to stream 10,000 orders.
orders_gen = order_stream(10000)
# Use a list comprehension to filter orders that are 'completed' and have amount > 200.
high_value_completed_orders = [order for order in orders_gen
if order["status"] == "completed" and order["amount"] > 200]
# Update the summary report using our mutable dictionary.
update_summary(summary_report, high_value_completed_orders)
print("Summary Report for High-Value Completed Orders:")
print(summary_report)
If we’d like to achieve the same goal with Java, since Java lacks built-in generators and list comprehensions, we have to generate a list of orders, then filter and update a summary using explicit loops, and thus make the code more complex, less readable and harder to maintain.
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
class Order {
public String orderId;
public double amount;
public String status;
public Order(String orderId, double amount, String status) {
this.orderId = orderId;
this.amount = amount;
this.status = status;
}
@Override
public String toString() {
return String.format("{orderId:%s, amount:%.2f, status:%s}", orderId, amount, status);
}
}
public class OrderProcessor {
// Generates a list of orders.
public static List generateOrders(int numOrders) {
List orders = new ArrayList();
String[] statuses = {"completed", "pending", "cancelled"};
Random rand = new Random();
for (int i = 0; i < numOrders; i++) {
String orderId = "ORD" + (i + 1);
double amount = Math.round(ThreadLocalRandom.current().nextDouble(10.0, 500.0) * 100.0) / 100.0;
String status = statuses[rand.nextInt(statuses.length)];
orders.add(new Order(orderId, amount, status));
}
return orders;
}
// Filters orders based on criteria.
public static List filterHighValueCompletedOrders(List orders) {
List filtered = new ArrayList();
for (Order order : orders) {
if ("completed".equals(order.status) && order.amount > 200) {
filtered.add(order);
}
}
return filtered;
}
// Updates a mutable summary Map with the count and total amount.
public static void updateSummary(Map summary, List orders) {
int count = 0;
double totalAmount = 0.0;
for (Order order : orders) {
count++;
totalAmount += order.amount;
}
summary.put("count", count);
summary.put("total_amount", totalAmount);
}
public static void main(String[] args) {
// Generate orders.
List orders = generateOrders(10000);
// Filter orders.
List highValueCompletedOrders = filterHighValueCompletedOrders(orders);
// Create a mutable summary map.
Map summaryReport = new HashMap();
summaryReport.put("count", 0);
summaryReport.put("total_amount", 0.0);
// Update the summary report.
updateSummary(summaryReport, highValueCompletedOrders);
System.out.println("Summary Report for High-Value Completed Orders:");
System.out.println(summaryReport);
}
}
Conclusion
Equipped with features of dynamic typing, list comprehensions, generators, and its approach to argument passing and mutability, Python is making itself a simplified coding while enhancing memory efficiency and performance. As a result, Python has become the ideal programming language for self-learners.
Thank you for reading!