Updates about python 3

Lambda expressions

The lamda expression

lambda [parameter_list]: expression

yields an anonymous function, which behaves in the same way as

def <lambda>(arguments):
    return expression

useage example:

  • uses a lambda expression to return a function
def is_odd(n):
    return n % 2 == 1

L = list(filter(is_odd, range(1, 20)))

is equal to

L = list(filter(lambda n: n % 2 == 1, range(1,20)))
  • pass a small function as an argument
    >>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
    >>> pairs.sort(key=lambda pair: pair[1])
    >>> pairs
    [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
    

    (see manual of list.sort())

higher-order function

In python, function can be be taken as arguments for other functions, and can also return as a result of other functions. That’s something I’ve met before, but the structure closure is something unfamiliar to me.

A closure is a way of keeping alive a variable even when the function has returned. In Python, this is done by nesting a function inside other function and then returning the underlying function.

def lazy_sum(*args):
    def sum():
        a = 0
        for n in args:
            a += n
        return a
    return sum

The good part about using closure here is that you need not to evaluate the sum immediately after providing your argument, see below

>>>f = lazy_sum(1, 3, 5, 7)
>>>f
<function closure_test.lazy_sum.<locals>.sum>
>>>f()
16

the f references the variables of lazy_sum() even after it returns. another useage:

def multiplier_of(n):
    def multiplier(number):
        return number*n
    return multiplier

multiplywith5 = multiplier_of(5)
print(multiplywith5(9))

According to learnpython.org

ADVANTAGE : Closures can avoid use of global variables and provides some form of data hiding.(Eg. When there are few methods in a class, use closures instead)

It’s very important to note that in python, the variables in the enclosing scopes are only readonly by nested functions. However, one can use the nonlocal keyword explicitly with these variables in order to modify them. Refer to examples of liaoxuefeng’s exercises if you please.

The nested functions also leads to the use of decorators.

decorators

decorator: A function returning another function, usually applied as a function transformation using the @wrapper syntax.

generally, a decorator use syntax like

@decorator
def functions(arg):
  ...

and it is equivalent to:

def function(arg):
  ...
function=decorator(function)

Usage example 1:

adding @classmethod before defining a method in class will transform that method into a classmethod

class C:
    @classmethod
    def f(cls, arg1, arg2, ...): ...

It can be called either on the class (such as C.f()) or on an instance (such as C().f()). The instance is ignored except for its class.

It is equal to

def f(self, arg1, arg2):...
f = classmethod(f)

Usage example 2:

a decorator is just another function which takes a functions and returns one, and you can use it to modify an old function and return a new one:

 def Check(old_function):
     def new_function(arg):
         if arg<0: raise ValueError("Negative Argument")
         old_function(arg)
     return new_function

 @Check
 def r(n):
     print(n**3)     

and it will raise ValueError then if you give r() a negative argument.

Usage example 3:

 def type_check(correct_type):
     def check(old_func):
         def new_func(arg):
             if not isinstance(arg, correct_type):
                 raise TypeError("Bad Type")
             return old_func(arg)
         return new_func
     return check


 @type_check(int)
 def times2(num):
     return num*2

 @type_check(str)
 def first_letter(word):
     return word[0]

then when you test it, you can get expected results

 print(times2(2)) # returns 4
 times2('Not A Number') # raises TypeError
 print(first_letter('Hello World')) # returns 'H'
 first_letter(['Not', 'A', 'String']) # raises TypeError

More help available here and here, also the learnpython.org is quite helpful.

the use of @property

It’s also part of the decorator techniques, but relatively more complex.

class Student(object):

    @property
    def score(self):
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value

According to Mr.Liao, when using @property, we transfer a method to an attribute, and when we wrapped it up with this decorator, this descript object has extra methods: getter, setter, deleter. Amazingly, these decorator act as decorators too. They return a new property object:

>>> property().getter(None)
<property object at 0x10ff079f0>

When we use @score.setter, what we are doing is call that property().setter method, so we are replacing the original setter function with the new one, and keep everything else. For in-depth explanation, see answers in stackoverflow.

If we don’t designate that setter() method, we’ll make the attribute readonly, which is, in some cases, what we desired.

class Screen(object):
    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        self._height = value    

    @property
    def resolution(self):
        return self._width * self._height

more about list

we can use lists as stacks as well as queue, which makes it possible both “last-in, first-out” and “first-in, first-out”, although the latter is less efficient.

>>> stack = [3, 4, 5]
>>> stack.append(6)
>>> stack
[3, 4, 5, 6]
>>> stack.pop()
6

To implement a queue, use collections.deque which was designed to have fast appends and pops from both ends.

>>> from collections import deque
>>> queue = deque(["Eric", "John", "Michael"])
>>> queue.append("Terry")           # Terry arrives
>>> queue.append("Graham")          # Graham arrives
>>> queue.popleft()                 # The first to arrive now leaves
'Eric'
>>> queue.popleft()                 # The second to arrive now leaves
'John'
>>> queue                           # Remaining queue in order of arrival
deque(['Michael', 'Terry', 'Graham'])

list comprehension is definitely flexible:

>>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
>>> # flatten a list using a listcomp with two 'for'
>>> vec = [[1,2,3], [4,5,6], [7,8,9]]
>>> [num for elem in vec for num in elem]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

The sequence of for in the second example should not be reversed, for the elem needs to be defined first by for elem in vec.

In case I might forget, to unpack argument list, we use the * operator, likewise, use **-operator for dictionaries. See here

Looping through a sequence is easy wiht for statements, and if we want the position index as well, we can use the enumerate() function

>>> for i, v in enumerate(['tic', 'tac', 'toe']):
...     print(i, v)
...
0 tic
1 tac
2 toe

to loop over two or more sequences, use zip to pair, zip yields an iterator

>>> questions = ['name', 'quest', 'favorite color']
>>> answers = ['lancelot', 'the holy grail', 'blue']
>>> for q, a in zip(questions, answers):
...     print('What is your {0}?  It is {1}.'.format(q, a))

For more about zip, see here

for the difference between list and tuple, the docs give us some hints:

Though tuples may seem similar to lists, they are often used in different situations and for different purposes. Tuples are immutable, and usually contain a heterogeneous sequence of elements that are accessed via unpacking or indexing (or even by attribute in the case of namedtuples). Lists are mutable, and their elements are usually homogeneous and are accessed by iterating over the list.