Special Notes
Python variable scope
nonlocal and global keywords are used to declare variables in a different scope.
nonlocalis used to declare a variable in the nearest enclosing scope that is not global.globalis used to declare a variable in the global scope (which is module level).
Example of nonlocal keyword
def outer_function():
x = 10 # Enclosing scope variable
def inner_function():
nonlocal x # Declare x as nonlocal
x += 5
print("Inner function:", x)
inner_function()
print("Outer function:", x)
outer_function()
Output:
Inner function: 15
Outer function: 15
Example of global keyword
x = 10 # Global variable
def my_function():
global x # Declare x as global
x += 5
print("Inside function:", x)
my_function()
print("Outside function:", x)
Output:
Inside function: 15
Outside function: 15
Shallow copy vs Deep copy
- Shallow copy: Creates a new object, but inserts references into it to the objects found in the original. Changes made to mutable objects in the shallow copy will affect the original object.
- Deep copy: Creates a new object and recursively adds copies of nested objects found in the original. Changes made to mutable objects in the deep copy will not affect the original object.
- Use
copy.copy()for shallow copy andcopy.deepcopy()for deep copy.
import copy
original = [1, 2, [3, 4]]
shallow_copied = copy.copy(original) # this is the same as shallow_copied = original[:] or shallow_copied = original.copy()
deep_copied = copy.deepcopy(original)
shallow_copied[2][0] = 99
deep_copied[2][0] = 88
print(original) # [1, 2, [99, 4]]
print(shallow_copied) # [1, 2, [99, 4]]
print(deep_copied) # [1, 2, [88, 4]]
Default Arguments
- Default arguments are evaluated once when the function is defined, not each time the function is called.
- If you use mutable default arguments (like lists or dictionaries), they will retain changes across function calls.
- To avoid this, use
Noneas the default value and initialize the mutable object inside the function.
def append_to_list(value, my_list=None):
if my_list is None: # NOTE, you need to directly compare with None
my_list = []
my_list.append(value)
return my_list
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [2]
print(append_to_list(3, [4, 5])) # [4, 5, 3]
def append_to_list(value, my_list=[]):
my_list.append(value)
return my_list
print(append_to_list(1)) # [1]
print(append_to_list(2)) # [1, 2]
print(append_to_list(3, [4, 5])) # [4, 5, 3]
Sorting
- Python's built-in sorting (sorted() and list.sort()) is a stable sort, and sorts lexicographically by default (using unicode value via
ord()). - For tuple sorting, the first element is compared first, then the second, and so on.
# Sorting a list of tuples
data = [(1, 'apple'), (2, 'banana'), (1, 'orange')]
sorted_data = sorted(data, key=lambda x: (x[0], x[1]))
print(sorted_data) # [(1, 'apple'), (1, 'orange'), (2, 'banana')]
Tuple vs List in leetcode
Tuples:
- Immutable, cannot be changed after creation.
- Hashable, can be used as dictionary keys.
- Faster than lists for iteration and access.
- Typically represent fixed collections of heterogeneous data or pairs, coordinates, etc.
Lists:
- Mutable, can be changed after creation.
- Not hashable, cannot be used as dictionary keys.
- Slower than tuples for iteration and access.
- Suitable for homogeneous sequences, dynamic-length collections, or when frequent modifications are needed.
| Use Tuple | Use List |
|---|---|
| Coordinate, (row, col) pairs | When frequent insertion/deletion needed |
| Keys in dicts/sets | Temporary collections, stacks, queues |
| Fixed-size states (memoization keys) | Mutable states, paths, results |
*args and **kwargs
*argsallows you to pass a variable number of non-keyword arguments to a function.**kwargsallows you to pass a variable number of keyword arguments to a function.- They are often used in function definitions to allow for flexible argument passing.
def my_function(*args, **kwargs):
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
my_function(1, 2, 3, a=4, b=5)
# Output:
# Positional arguments: (1, 2, 3)
# Keyword arguments: {'a': 4, 'b': 5}
def my_function(a, b, *args, **kwargs):
print("a:", a)
print("b:", b)
print("Positional arguments:", args)
print("Keyword arguments:", kwargs)
my_function(1, 2, 3, 4, 5, x=6, y=7)
# Output:
# a: 1
# b: 2
# Positional arguments: (3, 4, 5)
# Keyword arguments: {'x': 6, 'y': 7}
The Zen of Python, by Tim Peters
>>> import this
The Zen of Python, by Tim Peters
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!