Skip to content

Instantly share code, notes, and snippets.

@zianwar
Last active April 5, 2025 14:30
Show Gist options
  • Save zianwar/e0731e17f4078a7f9f4ccfe724e8b87e to your computer and use it in GitHub Desktop.
Save zianwar/e0731e17f4078a7f9f4ccfe724e8b87e to your computer and use it in GitHub Desktop.
Python Cheat Sheet

Python Cheat Sheet

Resources

Language

Python Quirks

  • Mod operator:
    • -1 % 10 results in 9 instead of -1. Use math.fmod(-1, 10) instead of -1 % 10
  • Rounding integers towards zero
    • In most cases rounding down -0.1 towards zero should give 0, this is not the case in Python unless you are using int(x/y). The table below shows that using int(x/y) always works.

      Operation Result Correct
      math.floor(-1 / 10) -1 No
      math.floor(1 / 10) 0 Yes
      1 // 10 0 Yes
      -1 // 10 -1 No
      int(-1 / 10) 0 Yes
      int(1 / 10) 0 Yes

Primitives

  • Integers in Python3 are unbounded, the maximum integer representable is a function of the available memory
  • Unlike integers, floats are not infinite precision, and it's convenient to refer to infinity as float('inf') and float('-inf'). These values are comparable to integers, and can be used to create psuedo max-int and pseudo min-int.

For loops

  • Index based loop

     for i in range(4):
     	print(i)
  • Loop through list

     x = [1,2,3]
     for n in x:
     	print(n)
  • Loop through list with index

     for i, n in enumerate(nums):
     	print(i, n)
  • Loop through dictionary

     x = {'x': 1, 'y': 2, 'z': 3}
     for k, v in x.items():
     	print(k, v)
  • Loop through characters of string

     x = "hello"
     for c in x:
     	print(c)

Sorting

  • Documentation: https://wiki.python.org/moin/HowTo/Sorting#Sortingbykeys

    • Starting with Python 2.2, sorts are guaranteed to be stable.
  • Sort list ASC by lambda

     x = [2,3,1]
     new_list = sorted(x, key=lambda x: x)
    • Operator Module Functions: Python provides convenience functions to make accessor functions easier and faster. The operator module has itemgetterattrgetter, and methodcaller

       >>> from operator import itemgetter, attrgetter, methodcaller
       
       >>> sorted(student_tuples, key=itemgetter(2))
       [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
       
       >>>  sorted(student_objects, key=attrgetter('age'))
       [('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
       
       # The operator module functions allow multiple levels of sorting. 
       # For example, to sort by grade then by age:
      
       >>> sorted(student_tuples, key=itemgetter(1,2))
       [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
       
       >>> sorted(student_objects, key=attrgetter('grade', 'age'))
       [('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
  • Sort list in-place

     x = [2,3,1]
     x.sort()
     print(x) # sorted list
  • Sort string

     x = "zxa"
     
     sorted(x) # ["a", "x", "z"]
     ''.join(sorted(x)) # "axz"

Data Structures

Namedtuple

  • Usage:

     from collections import namedtuple  
     
     # Define namedtuple type  
     Point = namedtuple('Point', 'x y')  
       
     # Create namedtuple instances  
     point1 = Point(1, 2)  
     point2 = Point(3, 4)    
     
     # To access elements, use attribute names instead of indexes:
     x_coord = point1.x  
     y_coord = point2.y  
  • Operations:

    • Namedtuples are fully iterable and unpackable like regular tuples.
    • They support comparisons (==, !=, etc.) based on their elements.
    • They are immutable, meaning their elements cannot be changed after creation.
  • Additional features:

    • Field names: Can be any valid Python identifier (avoid keywords).
    • Optional rename argument: Automatically replaces invalid/duplicate names.
    • _asdict() method: Converts namedtuple to a dictionary.
    • _replace() method: Creates a new namedtuple with modified elements.
    • Immutability: Ensures data consistency and simplifies multi-threaded programming.

Dictionary

List

  • Instantiation:
     [3, 5, 7, 11]
     [1] + [0] * 10
     list(range(100))
  • Existence check:
     1 in [1,2,3]
  • Copy list
     # Shallow copy
     B = list(A) # or copy.copy(B) or A[:]
    
     # Deep copy
     B = copy.deepcopy(A)
  • Binary search sorted list
     bisect.bisect(A,6)
     bisect.bisect_left(A,6)
     bisect.bisect_right(A,6)
  • Reverse list
     A.reverse() # in-place
     reversed(A) # returns an iterator, wrap with list()
  • Sort list
     A.sort() # in-place
     sorted(A) # returns copy
  • Delete items from list
     del A[i] # delete single item
     del A[i:j] # remove slice
  • Slicing a list
    • A[start:stop:step] with all of start, stop, and step being optional
       # index     0   1   2   3   4   5   6
       # reversed -5  -6  -5  -4  -3  -2  -1
       A =        [1,  6,  3,  4,  5,  2,  7]
      
       A[2:4]    # [3,4]
       A[2:]     # [3,4,5,2,7]
       A[:4]     # [1,6,3,4]
       A[:-1]    # [1,6,3,4,5,2]         (except last item)
       A[-3:]    # [5,2,7]               (index from end)
       A[-3:-1]  # [5,2],
       A[1:5:2]  # [6, 4]
       A[5:1:-2] # [2, 4]
       A[::-1]   # [7, 2, 5, 4, 3, 6, 1] (reverses list)
      
       A[k:] + A[:k] # rotates A by k to the left
  • List comprehension
    • A list comprehension consists of:
      1. An input sequence
      2. An iterator over the input sequence
      3. A logical condition over the iterator (optional)
      4. An expression that yields the elements of the derived list.
     [x ** 2 for x in range(6)]               # [0, 1, 4, 9, 16, 25]
     [x ** 2 for x in range(6) if x % 2 == 0] # [0, 4, 16]
    • Nested
      • As a general rule, it is best to avoid more than two nested comprehensions, and use conventional nested for loops
       A = [1, 3, 5]
       B = ['d', 'b']
       [(x, y) for x in A for y in B] 
       # [(1, 'a'), (1, 'b'), (3, 'a'), (3, 'b'), (5, 'a'), (5, 'b')]
      • Also supported in set and dict

Set

  • Lookup operation is O(1)
    • Set is implemented as hashmap

Counter

Heap and Priority Queue

  • Create heap (default = min-heap)

     import heapq
     h = []
     
     heapq.heappush(h, 5)
     heapq.heappush(h, 0)
     
     print([heapq.heappop(h) for i in range(len(h))]) # [0 5]
  • Create max heap or priority queue

     import heapq
     h = []
     
     heapq.heappush(h, (-2, 5)) # priority = 2 (highest) 
     heapq.heappush(h, (-1, 0)) # priority = 1
     
     print([heapq.heappop(h)[1] for i in range(len(h))]) # [5, 0]

String

  • Pad string from left
     "4".rjust(4, "0") # 0004
  • String formatting (Python 3.6+)
     >>> f'Hello, {name}' 
     'Hello, Bob'
     
     >>> '{:.2}'.format(0.1234) # Limit number of decimals to show
     '0.12'
    
     # Format to Hex
     >>> f'{errno:#x}'
     '0xbadc0ffee'
  • Trim / Strip characters
    • By default [l|r]strip functions remove whitespace when no argument is passed
     >>> s1 = '__abc__'
     
     >>> s1.lstrip('__') # Trim from left
     abc__
     
     >>> s1.rstrip('__') # Trim from right
     __abc
     
     >>> s1.strip('__') # Trim from left and right
     abc

Stack

  • Using list
     s = []
     
     # push O(1)
     s.append(1)
     s.append(2)
     
     print(s) # [1,2]
     
     # pop O(1)
     s.pop()  # 2 
     
     print(s) # [1]

Deque

  • collections.deque (pronounced "deck") - double-ended queue
  • Usage:
     from collections import deque
    
     d = deque(['a','b','c'])
     print(d) # deque(['a','b','c'])
    
     # Append from the right side of list
     d.append("f") # O(1)
     print(d) # deque(['a','b','c', 'f'])
    
     # Pop from the right side of list
     x = d.pop() # O(1)
     print(x) # 'f'
     print(d) # deque(['a','b','c'])
    
     # Append from the left side of list
     d.appendleft("z") # O(1)
     print(d) # deque(['z', 'a','b','c'])
    
     # Pop from the left side of list
     x = d.popleft() # O(1)
     print(x) # 'z'
     print(d) # deque(['a','b','c'])

Queue

  • Usage:
     from collections import deque
     queue = deque()
    
     # Append from right side O(1)
     queue.append(1)
     queue.append(2)
     print(queue) # deque([1,2])
    
     # Pop from left side O(1)
     queue.popleft() # 1
     print(queue) # deque([2])

Linked List

  • Using collections.deque

     from collections import deque
     l = deque()
     
     # Append from end
     l.append(1)
     l.append(2)
     
     # Remove end
     l.pop()
     
     # iterate over items 
     for n in d:
     	print(n)
     
     # or 
     while queue:
         print(queue.popleft())
  • Using custom data structure

     class Node:
         def __init__(self, data):
             self.data = data
             self.next = None
     
         def __repr__(self):
             return self.data
     
     class LinkedList:
         def __init__(self):
             self.head = None
     
         def __repr__(self):
             node = self.head
             nodes = []
             while node is not None:
                 nodes.append(node.data)
                 node = node.next
             nodes.append("None")
             return " -> ".join(nodes)

Unit testing

Pandas

Python Tricks

1. Trailing Comma

  • Smart formatting and comma placement can make your list, dict, or set constants easier to maintain.
  • Python’s string literal concatenation feature can work to your benefit, or introduce hard-to-catch bugs.
    • Gotcha:
      >>>'hello' 'world'
      'helloworld'
    • Always add trailing comma to container literal (list or dict):
       >>> names = [ 
       ... 'Alice',
       ...	 'Bob',
       ... 'Dilbert', # <- this one
       ... ]

2. Context managers - Supporting with in Your Own Objects

  • Context manager in Python is a an interface that your object needs to follow in order to support the with statement.

  • Basically, all you need to do is add __enter__ and __exit__ methods to an object if you want it to function as a context manager. Python will call these two methods at the appropriate times in the resource management cycle.

  • Example 1: using class based approach

     class ManagedFile:  
     	def __init__(self, name):
     	
     	self.name = name
     	
     	def __enter__(self):  
     	self.file = open(self.name, 'w') return self.file
     	
     	def __exit__(self, exc_type, exc_val, exc_tb): if self.file:
     	
     	            self.file.close()
  • Example 2: using contextlib.contextmanager

     from contextlib import contextmanager
    
     @contextmanager
     def managed_file(name): try: # generator function!
     	f = open(name, 'w')
     	yield f finally:
     	f.close()
  • Both examples can be used as:

    >>> with ManagedFile('hello.txt') as f:
    ...    f.write('hello, world!')
    ...    f.write('bye now')

3. Asserts

  • Python’s assert statement is a debugging aid that tests a condition as an internal self-check in your program. Example:

     assert counter == 10, "counter should be equal to 10"
  • Why asserts?

the proper use of assertions is to inform developers about unrecoverable errors in a program. Assertions are not intended to signal expected error conditions, like a File-Not-Found error, where a user can take corrective actions or just try again.

Assertions are meant to be internal self-checks for your program. They work by declaring some conditions as impossible in your code. If one of these conditions doesn’t hold, that means there’s a bug in the pr gram.

If your program is bug-free, these conditions will never occur. But if they do occur, the program will crash with an assertion error telling you exactly which “impossible” condition was triggered. This makes it much easier to track down and fix bugs in your programs. And I like anything that makes life easier—don’t you?

  • Caveats
    • Caveat #1 – Don’t Use Asserts for Data Validation
      • Asserts should only be used to help developers identify bugs. They’re not a mechanism for handling run-time errors.
      • Asserts can be globally disabled with an interpreter setting.
    • Caveat #2 – Asserts That Never Fail due to syntax mis-interpretation
      • This has to do with non-empty tuples always being truthy in Python
       assert(1 == 2, 'This should fail')

4. Underscores

  1. Single Leading Underscore _var
    • Single underscores are a Python naming convention that indicates a name is meant for internal use. It is generally not enforced by the Python interpreter and is only meant as a hint to the programmer.
    • if you use a wildcard import to import all the names from the module, Python will not import names with a leading underscore (unless the module defines an __all__ list that overrides this behavior)
  2. Single Trailing Underscore: var_
    • A single trailing underscore (postfix) is used by convention to avoid naming conflicts with Python keywords. Example:
       def make_object(name, class): # SyntaxError: "invalid syntax"
       	pass
       
       def make_object(name, class_): 
       	pass
  3. Double Leading Underscore: __var
    • Name mangling: the interpreter changes the name of the variable in a way that makes it harder to create collisions when the class is extended later.
       class Test:  
       	def __init__(self):
       	self.foo = 11
       	self._bar = 23 
       	self.__baz = 23
       	
       >>> t = Test()  
       >>> dir(t)
       ['_Test__baz', '_bar', 'foo', '__class__', '__dict__', ...]
      • __bar becomes _Test__baz
  4. Double Leading and Trailing Underscore: __var__
    • Reserved for special use in the language. This rule covers things like __init__ for object constructors, or __call__ to make objects callable.
  5. Single underscore _
    • Per convention, a single stand-alone underscore is sometimes used as a name to indicate that a variable is temporary or insignificant.
       for _ in range(32):  
       	print('Hello, World.')
    • You can also use single underscores in unpacking expressions as a “don’t care” variable to ignore particular values.
    • This meaning is per convention only and it doesn’t trigger any special behaviors in the Python parser. The single underscore is simply a valid variable name that’s sometimes used for this purpose.

5. String interpolation

  • Dan’s Python String Formatting Rule of Thumb:

If your format strings are user-supplied, use Template Strings to avoid security issues. Otherwise, use Literal String Interpolation if you’re on Python 3.6+, and “New Style” String Formatting if you’re not.

  • "New Style" String Formatting
     >>> errno = 50159747054 
     >>> name = 'Bob'
     >>> f"Hey {name}, there's a {errno:#x} error!"
     "Hey Bob, there's a 0xbadc0ffee error!"

6. Functions

  • Functions are objects

     def yell(text):
     	return text.upper() + '!'
     >>> yell('hello')
     'HELLO!'
     
     >>> bark = yell
     >>> bark('woof') 
     'WOOF!'
    • The name of a function is just a pointer to the object where the function is stored, you can have multiple names pointing to same function.
      • Python attaches a string identifier to every function at creation time for debugging purposes
         >>> bark.__name__ 
         'yell'
  • Functions Can Be Stored in Data Structures

     >>>funcs = [bark, str.lower, str.capitalize] 
     >>> for f in funcs:  
     	... print(f, f('hey there'))
     <function yell at 0x10ff96510> 'HEY THERE!' 
     <method 'lower' of 'str' objects> 'hey there' 
     <method 'capitalize' of 'str' objects> 'Hey there'
    
     >>> funcs[0]('heyho') # call a function object stored
     'HEYHO!'
  • Functions Can Be Passed to Other Functions

     >>> def greet(func):  
     	... greeting = func('Hi, I am a Python program') 
     	... print(greeting)
     
     >>> def whisper(text):  
     	... return text.lower() + '...'
     
     >>> greet(whisper)
     'hi, i am a python program...'
    • Higher-order function
      • The ability to pass function objects as arguments to other functions is powerful. It allows you to abstract away and pass around behavior in your programs. In this example, the greet function stays the same but you can influence its output by passing in different greeting behaviors. Functions that can accept other functions as arguments are also called higher-order functions.

      • The classical example for higher-order functions in Python is the built-in map function:

         >>> list(map(bark, ['hello', 'hey', 'hi'])) 
         ['HELLO!', 'HEY!', 'HI!']
  • Functions Can Be Nested

     def speak(text): 
     	def whisper(t):
     		return t.lower() + '...' 
     return whisper(text)
     
     >>> speak('Hello, World')
     'hello, world...'
  • Functions Can Capture Local State

     def get_speak_func(text, volume): 
     	def whisper():
     		return text.lower() + '...' 
     	def yell():
     		return text.upper() + '!' 
     	
     	if volume > 0.5:
     		return yell 
     	else:
     		return whisper
     
     >>> get_speak_func('Hello, World', 0.7)() 
     'HELLO, WORLD!'
    • Functions that do this are called closures. A closure remembers the values from its enclosing lexical scope even when the program flow is no longer in that scope.
  • Objects Can Behave Like Functions

    • While all functions are objects in Python, the reverse isn’t true

    • But objects can be made callable, which allows you to treat them like functions in many cases and invoke them with () syntax using the __call__ method.

       class Adder:  
       	def __init__(self, n):
       		self.n = n  
      
       	def __call__(self, x):
       		return self.n + x
      
      
       >>> plus_3 = Adder(3) 
       >>> plus_3(4)  
       7
    • "calling" an object as a function attempts to execute the object’s __call__ method.

    • Use callable(<object>) -> bool to check weather an object is callable

7. Lambdas

  • Lambdas Are Single-Expression Functions

     >>> add = lambda x, y: x + y 
     >>> add(5, 3)  
     8
  • Lambda functions are restricted to a single expression.

    • This means a lambda function can’t use statements or annotations — not even a return statement. How do you return values from lambdas then? Executing a lambda function evaluates its expression and then automatically returns the expression’s result, so there’s always an implicit return statement. That’s why some people refer to lambdas as single expression functions.
    • Define an “add” func- tion inline and then immediately called it with the arguments 5 and 3.
       >>> (lambda x, y: x + y)(5, 3)
       8
  • Lambdas You Can Use

    • Sort iterables by key
       >>> tuples = [(1, 'd'), (2, 'b'), (4, 'a'), (3, 'c')] 
       >>> sorted(tuples, key=lambda x: x[1])  
       [(4, 'a'), (2, 'b'), (3, 'c'), (1, 'd')]

8. Decorators

  • Decorators allow you to extend and modify the behavior of a callable (functions, methods, and classes) without permanently modifying the callable itself.

  • They “decorate” or “wrap” another function and let you execute code before and after the wrapped function runs.

    • Decorator is a callable that takes a callable as input and returns another callable as output
  • Decorators are used for:

    • logging
    • enforcing access control and authentication
    • instrumentation and timing functions
    • rate-limiting
    • caching, and more
  • Takeaways for understanding decorators are:

    • Functions are objects: they can be assigned to variables and passed to and returned from other functions
    • Functions can be defined inside other functions: and a child function can capture the parent function’s local state (closures)
  • Sample code:

     def null_decorator(func): 
     	return func
     
     @null_decorator # this syntax is same as greet = null_decorator(greet)
     def greet():  
     	return 'Hello!'
     
     >>>greet() 
     'Hello!'
  • Decorators with arguments

     def trace(func):
         def wrapper(*args, **kwargs):
             print(f"TRACE: calling {func.__name__}'
     	          f' with args={args}, kwargs={kwargs}")
             result = func(*args, **kwargs)
             print(f"TRACE: {func.__name__} returned {result!r}")
             return result
     
         return wrapper
    • It uses the * and ** operators in the wrapper closure definition to collect all positional and keyword arguments and stores them in variables (args and kwargs).
    • The wrapper closure then forwards the collected arguments to the original input function using the * and ** argument unpacking operators.
  • Applying functools.wraps to the wrapper closure returned by the decorator carries over the docstring and other metadata of the input function:

     import functools
     
     def uppercase(func): 
     	@functools.wraps(func) 
     	def wrapper():
     		return func().upper() 
    
     	return wrapper
     
     @uppercase
     def greet():  
     	"""Return a friendly greeting.""" 
     	return 'Hello!'
     
     >>> greet.__name__  
     'greet'
     >>> greet.__doc__  
     'Return a friendly greeting.'
    • As a best practice, I’d recommend that you use functools.wraps in all of the decorators you write yourself. It doesn’t take much time and it will save you (and others) debugging headaches down the road.

9. *args and **kwargs

  • *args and **kwargs let you write functions with a variable number of arguments in Python.
    • *args collects extra positional arguments as a tuple.
    • **kwargs collects the extra keyword arguments as a dictionary.
  • Calling them args and kwargs is just a convention (and one you should stick to).
  • Example usage with using a decorator
     def trace(f):  
     	@functools.wraps(f)  
     	def decorated_function(*args, **kwargs):
     		print(f, args, kwargs) 
     		result = f(*args, **kwargs) 
     		print(result)
     	return decorated_function
     
     @trace
     def greet(greeting, name):  
     	return '{}, {}!'.format(greeting, name)
     
     >>> greet('Hello', 'Bob')  
     <function greet at 0x1031c9158> ('Hello', 'Bob') {} 'Hello, Bob!'

10. Unpack operator

  • Putting a * before an iterable in a function call will unpack it and pass its elements as separate positional arguments to the called function.
     def add(a, b): 
     	return a + b 
     
     >> nums = [1, 2]
     >>> add(*nums)
     3
  • Using the * operator on a generator consumes all elements from the generator and passes them to the function:
     >>> genexpr = (x * x for x in range(3))
     >>> print(*genexpr)
     0 1 4
  • The ** operator is used for unpacking keyword arguments from dictionaries
    • The function argument names need to match the dictionary keys
     dict_vec = {'y': 0, 'z': 1, 'x': 1}
     
     def f(x, y, z):
     	print(x, y, z)
     
     def g(x, y):
     	print(x, y)
     	
     >>> f(**dict_vec)
     1 0 1
     
     >>> g(**dict_vec) # error
     Traceback (most recent call last):
       File "<stdin>", line 1, in <module>
     TypeError: g() got an unexpected keyword argument 'z'
    • If you were to use the single asterisk * operator to unpack the dictionary, keys would be passed to the function in random order instead:
       >>> f(*dict_vec) 
       y z x

11. Comparison operators

  • An is expression evaluates to True if two variables point to the same (identical) object. • An == expression evaluates to True if the objects referred to by the variables are equal (have the same contents).
  1. __repr__ and __str__
  • You can control to-string conversion in your own classes using the __str__ and __repr__ “dunder” methods.
  • The result of __str__ should be readable. The result of __repr__ should be unambiguous.
  • Always add a __repr__ to your classes. The default implementation for __str__ just calls __repr__.

12. zip

  • Basic pattern - iterate through pairs
  • Works same for odd/even lengths
  • Always produces (length - 1) pairs
arr = [1, 2, 3, 4, 5]
for a, b in zip(arr, arr[1:]):     # Pairs: (1,2), (2,3), (3,4), (4,5)

print(list(zip(arr, arr[1:])))     # [(1,2), (2,3), (3,4), (4,5)]

Common use cases:

diffs = [b - a for a, b in zip(arr, arr[1:])]                 # Differences
is_sorted = all(a <= b for a, b in zip(arr, arr[1:]))         # Check if sorted
transitions = [(a,b) for a,b in zip(arr, arr[1:]) if a != b]  # Find changes

Triple-wise using zip (looking at neighbors)

for a, b, c in zip(arr, arr[1:], arr[2:]):    # Triplets: (1,2,3), (2,3,4), (3,4,5)

Concurrency

  • Python provides three standard libraries for concurrency:
    • threading
    • asyncio
    • multiprocessing

Clean Pythonic Code

Create classes for data clumps

Explicitly create classes for data clumps, i.e., groups of values that do not have any methods on them. M*y programmers would use a generic Pair or Tuple class, but we have found that this leads to confusing and buggy programs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment