6 Must-Know Python Concepts

Thu 28 July 2016 | -- (permalink)

This post talks about common concepts I encountered after using Python for some time. My aim is to present things simply without going deep into the technical details which I find a lot of blog posts tend to do. That is fine if you're already at an intermediate level, but pretty overwhelming for beginners.

List comprehension

List comprehension can be used to simplify your code if you find yourself doing a for loop and appending to a list. The example below shows two ways of building a 2D matrix.

Without list comprehension

matrix = []
for r in xrange(rows_count):
    curr_row = [] # we can do curr_row = [0]*cols_count too
    for c in xrange(cols_count):

With list comprehension

matrix = [[0 for x in xrange(cols_count)] for x in xrange(rows_count)]

Lists are mutable and thus pass-by-reference

If you do not want that behaviour then create a new list instead of copying one.

mylist = [[]]*3
print mylist # [[], [], []]
print mylist # [[1], [1], [1]]

Copying a list by value: b = a[:]

Note that if there are sub lists inside the list, doing it this way will still copy the inner lists by reference

Copying a list by reference: b = a

Object equality

To compare whether variables are referencing the same object, use A is B or id(A) == id(B).

is will return True if two variables point to the same object

== will return True if the values of two variables are equal (even if they are different objects).

Equality of == is determined by the __eq__() method

xrange vs. range

In python 2, xrange will return an xrange object (similar to an iterator). This means that if we do xrange(100), we do not get a list of 100 values. This is useful for iterating over large ranges where we do not need the whole list and on-demand generation is sufficient.

In python 3, range works like python 2's xrange. There is no xrange in python 3.


A generator is a function that produces a sequence of results instead of a single value or a single list. Generators can be easily created with the yield keyword. Again, it is useful when we only need the values in a sequence one at a time, and have no need for the whole list of values in memory.

Let see this in action by defining three methods, my_sequence, my_list and my_xrange

TOTAL = 100000000
def my_sequence():
    i = 0
    while i < TOTAL:
        yield i
        i += 1

def my_list():
    return range(TOTAL)

def my_xrange():
    return xrange(TOTAL)    

Memory usage (using psutil to get the process' RSS memory) are shown below:

sequence_of_values = my_sequence() # Memory usage: 6782976B
sequence_of_values2 = my_xrange() # Memory usage: 6774784B
list_of_values = my_list() # Memory usage: 3266207744B

Initially I thought that the object returned by my_xrange() was the same as the object returned by my_sequence(). Although they might behave similarly, xrange actually returns a sequence object whereas using yield returns a generator.

More info: http://stackoverflow.com/questions/24499624/generator-vs-sequence-object


In its simplest sense, decorators modify the behaviour of an existing method by adding extra functionality.

Two things are needed:

  • Defining the decorator function. It accepts the function to be decorated as parameter. We then return a new function which applies the extra functionality
  • Calling the decorator by doing @decorator_name

Note that order matters.

def html(fn):
    def wrapped():
        return "<html>" + fn() + "</html>"
    return wrapped

def bold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def test():
    return "hello world"

print test() # <html><b>hello world</b></html>
comments powered by Disqus