
While being one of the most popular programming language world-wide(#1 according to TIOBE Index), Python has some absolutely bizarre behaviors that will make you question everything you thought you knew about programming. I’ve been coding in Python for years, and these weird behaviors still catch me off guard sometimes. Whether you’re a beginner or you’ve been writing Python for years, these quirks will either save you from debugging nightmares or completely blow your mind.
Let’s dive into these Python weird behaviors that every developer encounters eventually.
Boolean Truthiness Quirks
Why This Behavior is Weird
Python’s boolean system operates in ways that defy logical expectations. You’d think True
and False
are just boolean values, right? Wrong. Python treats them as integers where True
equals 1 and False
equals 0.
print(True + True) # Output: 2
print(True * 5) # Output: 5
print(False - 1) # Output: -1
PythonBut wait, it gets weirder. Empty containers are falsy, which means they evaluate to False
in boolean contexts:
empty_list = []<br>empty_dict = {}
empty_string = ""
if not empty_list:
print("Empty list is falsy!") # This prints!
if not empty_dict:
print("Empty dict is falsy!") # This prints too!
PythonHow to Avoid This Pitfall
Never rely on boolean arithmetic unless you explicitly need it. Instead, use explicit comparisons:
# Instead of this:
if some_list:
do_something()
# Consider this for clarity:
if len(some_list) > 0:
do_something()
# Or use 'is not None' for explicit None checks:
if my_variable is not None:
process_variable()
PythonLoop Variable Scope Leakage
Why This Behavior In Python is Weird
Python’s for loop variables leak into the surrounding scope, which is absolutely mind-boggling if you come from languages like Java or C++. The loop variable continues to exist even after the loop finishes:
for i in range(3):
print(i)
print(f"i still exists: {i}") # Output: i still exists: 2
PythonThis creates unexpected behavior in nested functions:
functions = []
for i in range(3):
functions.append(lambda: print(f"Function {i}"))
# All functions print "Function 2" instead of 0, 1, 2
for func in functions:
func() # Output: Function 2, Function 2, Function 2
PythonHow to Avoid This Gotcha
Use list comprehensions or capture the variable explicitly:
# Method 1: List comprehension (recommended)
functions = [lambda x=i: print(f"Function {x}") for i in range(3)]
# Method 2: Explicit capture
functions = []
for i in range(3):
functions.append(lambda x=i: print(f"Function {x}"))
PythonChained Comparison Peculiarity
Why This Behavior is Weird
Python allows chained comparisons that look mathematical but behave in unexpected ways. While 1 < x < 3
works beautifully, things get weird fast:
# This works as expected
x = 2
print(1 < x < 3) # True
# But this might surprise you
a, b, c = 1, 2, 1
print(a < b > c) # True (because 1 < 2 AND 2 > 1)
print(a < b < c) # False (because 1 < 2 but 2 is NOT < 1)
PythonThe confusion happens because Python evaluates each comparison separately and combines them with AND logic.
How to Avoid This
Break complex chained comparisons into explicit boolean operations:
# Instead of this confusing chain:
if a < b > c:
do_something()
# Use this explicit approach:
if a < b and b > c:
do_something()
PythonMutable vs Immutable Types with += Operator
Why This Behavior is Weird
The +=
operator behaves differently depending on whether you’re dealing with mutable or immutable types. This creates one of the most confusing Python weird behaviors:
# This works fine with lists
x = [1, 2]
x += [3]
print(x) # [1, 2, 3]
# But this creates chaos with tuples
y = ([1, 2],)
try:
y[0] += [3]
except TypeError as e:
print(f"Error: {e}")
print(f"But y is now: {y}") # ([1, 2, 3],) - it still changed!
PythonThe tuple example is particularly mind-bending because it raises an error but still modifies the nested list!
How to Avoid This Pitfall
Always be explicit about your intentions with mutable and immutable types:
# For tuples, create a new tuple:
y = ([1, 2],)
new_list = y[0] + [3]
y = (new_list,)
# For lists, += is fine:
x = [1, 2]
x += [3] # This is clear and works as expected
PythonFloating-Point Precision Oddity
Why This Behavior is Weird
Python’s floating-point arithmetic produces results that seem mathematically impossible:
print(0.1 + 0.2) # 0.30000000000000004
print(0.1 + 0.2 == 0.3) # False
print(0.1 + 0.1 + 0.1 == 0.3) # False
PythonThis happens because computers store floating-point numbers in binary, and many decimal numbers can’t be represented exactly in binary. It’s not unique to Python, but it’s one of the most frequently encountered Python quirks.
How to Avoid This Gotcha
Use the decimal
module for precise decimal arithmetic or implement tolerance-based comparisons:
from decimal import Decimal
import math
# Method 1: Using Decimal for precision
a = Decimal('0.1') + Decimal('0.2')
print(a == Decimal('0.3')) # True
# Method 2: Tolerance-based comparison
def almost_equal(a, b, tolerance=1e-9):
return abs(a - b) < tolerance
print(almost_equal(0.1 + 0.2, 0.3)) # True
# Method 3: Using math.isclose() (Python 3.5+)
print(math.isclose(0.1 + 0.2, 0.3)) # True
PythonThe “is” vs “==” Identity Crisis
Why This Behavior is Weird
Python’s is
operator compares object identity, not values. For small integers (-5 to 256), Python caches objects for performance, leading to this bizarre behavior:
a = 256
print(a is 256) # True
b = 257
print(b is 257) # False (in most cases)
PythonThis happens because Python pre-allocates integers from -5 to 256 for performance optimization. Beyond this range, new objects are created each time.
How to Avoid This
Never use is
for comparing values. Always use ==
for value comparison:
# Wrong approach:
if my_number is 100:
do_something()
# Correct approach:
if my_number == 100:
do_something()
# Use 'is' only for identity checks:
if my_variable is None:
handle_none_case()
PythonPython Quirks During Two-Dimensional Array Initialization
Why This Behavior is Weird
Python’s elegant array initialization syntax breaks down spectacularly with two-dimensional arrays:
# This works perfectly for 1D arrays
a = [0] * 4
a[0] = 2
print(a) # [2, 0, 0, 0]
# But this creates a nightmare for 2D arrays
matrix = [[0] * 4] * 4
matrix[0][0] = 1
print(matrix) # [[1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0], [1, 0, 0, 0]]
PythonThe problem is that [[0] * 4] * 4
creates four references to the same inner list object, not four separate lists.
How to Avoid This Gotcha
Use list comprehensions or explicit loops for multi-dimensional arrays:
# Method 1: List comprehension (recommended)
matrix = [[0 for _ in range(4)] for _ in range(4)]
matrix[0][0] = 1
print(matrix) # [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]
# Method 2: Using loops
matrix = []
for i in range(4):
row = [0] * 4
matrix.append(row)
PythonMutable Default Arguments Madness
Why This Behavior is Weird
Python evaluates default arguments once when the function is defined, not each time it’s called. This creates shared state between function calls:
def append_to_list(element, my_list=[]):
my_list.append(element)
return my_list
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [1, 2] - Wait, what?
print(append_to_list(3)) # [1, 2, 3] - The list keeps growing!
PythonThis behavior catches even experienced developers off guard because the same mutable object is reused across function calls.
This is how it works under-the-hood:

How to Avoid This Pitfall
Use None
as the default value and create new objects inside the function:
def append_to_list(element, my_list=None):
if my_list is None:
my_list = []
my_list.append(element)
return my_list
# Now it works as expected
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [2]
PythonDuck Typing Behavior
Why This Python Behavior is Weird
Python’s duck typing philosophy (“If it walks like a duck and quacks like a duck, it’s a duck”) allows objects to be used interchangeably if they have the same interface:
class Duck:
def quack(self):
print("Quack!")
class Person:
def quack(self):
print("I'm pretending to be a duck!")
def make_it_quack(obj):
obj.quack()
make_it_quack(Duck()) # Quack!
make_it_quack(Person()) # I'm pretending to be a duck!
PythonThis flexibility is powerful but can lead to runtime errors if objects don’t have expected methods.
How to Avoid This Gotcha
Use explicit type checking or try-except blocks for robust code:
# Method 1: Explicit type checking
def make_it_quack(obj):
if hasattr(obj, 'quack') and callable(obj.quack):
obj.quack()
else:
print("This object can't quack!")
# Method 2: Try-except approach
def make_it_quack(obj):
try:
obj.quack()
except AttributeError:
print("This object doesn't know how to quack!")
PythonGlobal Interpreter Lock (GIL) Mystery
Why This Behavior is Weird
Python’s Global Interpreter Lock prevents true multi-threading for CPU-bound tasks. Even with multiple threads, only one thread can execute Python bytecode at a time:
import threading
import time
def cpu_bound_task():
count = 0
for i in range(10000000):
count += 1
return count
start_time = time.time()
# Single-threaded execution
cpu_bound_task()
single_thread_time = time.time() - start_time
# Multi-threaded execution
start_time = time.time()
threads = []
for i in range(2):
t = threading.Thread(target=cpu_bound_task)
threads.append(t)
t.start()
for t in threads:
t.join()
multi_thread_time = time.time() - start_time
print(f"Single thread: {single_thread_time:.2f}s")
print(f"Multi thread: {multi_thread_time:.2f}s")
# Multi-threading is often slower due to GIL overhead!
PythonHow to Avoid This
Use multiprocessing for CPU-bound tasks and threading only for I/O-bound operations:
import multiprocessing
import time
def cpu_bound_task():
count = 0
for i in range(10000000):
count += 1
return count
# Use multiprocessing for CPU-bound tasks
if __name__ == '__main__':
start_time = time.time()
with multiprocessing.Pool(processes=2) as pool:
results = pool.map(cpu_bound_task, [None, None])
multiprocess_time = time.time() - start_time
print(f"Multiprocessing: {multiprocess_time:.2f}s")
PythonLearn about Python Global Interpreter Lock(GIL) more in depth
Key Takeaways
Python’s weird behaviors aren’t bugs – they’re features that emerge from the language’s design philosophy. Understanding these Python quirks makes you a better developer and helps you write more robust code.
Remember these essential points:
- Always use
==
for value comparison, neveris
- Be explicit with mutable default arguments
- Understand that floating-point arithmetic has inherent limitations
- Use multiprocessing for CPU-bound tasks, not threading
- Be careful with two-dimensional array initialization
These Python weird behaviors might seem frustrating at first, but they’re part of what makes Python flexible and powerful. Master these quirks, and you’ll write better Python code that’s both efficient and maintainable.
The more you understand these behaviors, the more you’ll appreciate Python’s elegant design choices – even the weird ones!
Python Quirks FAQs
Because default parameters are evaluated only once—when the function is defined. If a default is a mutable object (like a list), it persists across calls. The fix is to use None
as a default and create a new object inside the function.
GIL stands for Global Interpreter Lock. It ensures only one thread executes Python bytecode at a time. This is “weird” because it means threads can’t run truly in parallel for CPU-bound tasks, surprising developers expecting concurrency.
Learn about them! For example, avoid mutable defaults, be careful with is
vs ==
, and test small cases of any code that seems odd. Python’s documentation and community forums often discuss these gotchas – knowing them prevents bugs.
Not exactly – most “weird behaviors” are by design (for efficiency or historical reasons). They are documented features (or limitations) of Python. Understanding the rationale (e.g., how Python handles objects and scope) makes them less weird.
Yes. Every language has its own surprises. For instance, JavaScript has quirks with type coercion, C has undefined behaviors with pointers, etc. Python’s quirks are fewer and well-documented – they just require a bit of learning.
Discover more from CodeSamplez.com
Subscribe to get the latest posts sent to your email.
[…] you enjoyed reading this, you may also enjoy learning about some python weird behaviours as well. Happy Python programming! […]