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 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 = *cols_count too for c in xrange(cols_count): curr_row.append(0) matrix.append(curr_row)
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 # [, , ] mylist.append(1) print mylist # [, , ]
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
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).
== is determined by the
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,
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.
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
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 @html @bold def test(): return "hello world" print test() # <html><b>hello world</b></html>