Python Tips and Tricks

Written in


Summary of Python tips, tricks, and to-dos

These pointers are what I picked up from the book Python Tricks: The Book

The book itself is a summary, and here i’ll be doing a summary of a summary.

Python is great because of its flexibility, but that itself could potentially be a double edged sword. It can be so easy to abuse and write really messy code, yet the program still runs fine.

Lets talk about the points made in the book. I only picked out points that I feel that are useful and that I have very little exposure to. Don’t get me wrong, all the points in the book are great, just some greater than others.


If the asserted condition returns true, nothing happens.
If the asserted condition returns false, AssertionError exception is raised.

def price_after_discount(0ld_price, discount):
new_price = 0ld_price * discount
assert 0 <= new_price <= old_price

This block of code applies a discount the an item. We assert that the new price is greater than zero, and not more than the old price.

Assert is different from a regular exception in that it’s meant for unrecoverable errors. Recoverable errors are things like File not found, where you can fix it (by putting the file where it should be) and try to run the program again. Asserts are meant for internal sanity checking.

Don’t use Assert for data validation, because it can be optimized away.

def delete_product(prod_id, user):
assert user.is_admin()
assert store.has_product(prod_id) 'Unknown product'

When you optimize away asserts, we remove checking if the user is admin, or if the store has the product.

Context Managers

When you do OO in python and you create classes to use, you can set context managers that dictate what happens when you enter and exit the code.

This is done by defining __enter__ and __exit__ functions

class ManagedFile:
def __init__(self, name): = name

def __enter__(self):
self.file = open(, 'w')
return self.file

def __exit__(self, exc_type, exc_val, exc_tb):
if self.file:

__enter__ is called when the execution enters the context of the statement, and __exit__ is called when it leaves the context.

Underscores and Dunders

On naming the variables in python, each name has a different meaning:

  1. SingleLeadingUnderscore: _varPurely conventional, this tells the reader that the variable is only meant for use internal to the function.
  2. SingleTrailingUnderscore: var_

    Purely conventional, putting an underscore at the back prevents naming conflicts with Python’s keywords

  3. DoubleLeadingUnderscore: __var

    When double underscores are infront, Python name-mangles the variable, and puts the class name in front of it.

    class Test: def __init__(self): = 11
    self._bar = 23
    self.__baz = 42

    When you look at the attributes of object Test, we see that __baz has become _Test__baz

    >>> t = Test()
    >>> dir(t)
    ['_Test__baz', '__class__', '__delattr__' ... ]

    This is done to protect the variable from being overridden in subclasses that extends from the parent class

  4. DoubleLeadingandTrailingUnderscore: __var__

    Leading and trailing underscores are left untouched by Python. They are reserved for special usage in Python, such as __init__ and __call__

  5. SingleUnderscore: _

    Meant to represent a variable that is temporary and insignificant

    for _ in range(5):
    print("Hello World)

    _ also represents the last value of the Python interpreter session

String Formatting

Old method: "Hello, %s" % name

New method: "Hello, {}".format(name)

The new method is more powerful, because the order in format doesn’t matter

'Hey {name}, there is a 0x{errno:x} error!'.format(errno=errno, name=name)

Python Functions

Python’s functions are first class objects.

What this means is that they can be assigned to variables, stored in data structures, and passed as arguements

>>> funcs = [bark, str.lower, str.capitalize]
>>> funcs
[<function yell at 0x10ff96510>, <method 'lower' of 'str' objects>, <method 'capitalize' of 'str' objects>]


Lambdas declare small anonymous functions. It’s a declarative way of programming

>>> add = lambda x, y: x + y
>>> add(5, 3)

The syntax: lambda x, y are the inputs. x + y is the action to carry out and return.

A more complete example:

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

The tuple is passed into the lambda function, and it returns the second element, which is assigned to key. The output is then sorted according to the second value.


Decorators let you modify the behavior of the callee, without modifying the callee’s code itself.

Some common use case for decorators are:

  1. Logging
  2. User authentication
def uppercase(func):
def wrapper():
original_result = func()
modified_result = original_result.upper()
return modified_result
return wrapper

def greet():
return 'Hello!'

>>> greet()

When we put the decorator on greet(), we are passing the function to our decorator function.

The output is then gotten from the decorator

Decorators are done bottom to top

def greet():
return 'Hello!

emphasis is executed first, before strong

Decorators can also accept arguments by using args and kwargs. The arguments are gotten from the original function, and passed to the decorators.

def proxy(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

*args and **kwargs

*args and **kwargs are optional arguments to a function.

def foo(required, *args, **kwargs):
if args:
if kwargs:

>>> foo() TypeError:
"foo() missing 1 required positional arg: 'required'"

>>> foo('hello')

>>> foo('hello', 1, 2, 3)
hello (1, 2, 3)

>>> foo('hello', 1, 2, 3, key1='value', key2=999)
hello (1, 2, 3) {'key1': 'value', 'key2': 999}

*args collects extra positional arguments
**kwargs collects extra keywords as a dictionary

Writing your own exception class

class NameTooShortError(ValueError):

def validate(name):
if len(name) < 10::
raise NameTooShortError(name)

References, Shallow Copying and Deep Copying


new_list = original_list
new_dict = original_dict
new_set = original_set

This just creates references, and any modifications done to original_ will also modify new_

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs.append("Hello")

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9], "Hello"]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9], "Hello"]

Shallow Copying

new_list = list(original_list)
new_dict = dict(original_dict)
new_set = set(original_set)

This makes a new list, but the children objects in the list are not copied.

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs.append("Hello")

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9], "Hello"]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs[1][0] = "X"

>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], "Hello"]
>>> ys
[[1, 2, 3], ['X', 5, 6], [7, 8, 9]]

Deep Copying

new_list = copy.deepcopy(original_list)
new_dict = copy.deepcopy(original_dict)
new_set = copy.deepcopy(original_set)

This creates an entirely new instance, and copies all the children too.

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs.append("Hello")

>>> xs
[[1, 2, 3], [4, 5, 6], [7, 8, 9], "Hello"]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

>>> xs[1][0] = "X"

>>> xs
[[1, 2, 3], ['X', 5, 6], [7, 8, 9], "Hello"]
>>> ys
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]


Generators generate values JIT (Just In Time). This is opposed to making a list, and iterating through it.

genexpr = ('Hello' for i in range(3))

>>> next(genexpr)

>>> next(genexpr)

>>> next(genexpr)

>>> next(genexpr)


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: