5
1       2
    4               17032
                    your AU ID
 Students (lecturers, TAs, ...) can poste multiple-choice questions
  together with answer options and explanations
 Students (and others) can
   • try to answer questions
   • add comments to questions
   • grade the quality and difficulty of questions
 Questions can be sorted by e.g. rating, difficulty, #answers, ...
 Activity on PeerWise releases badges
                                                                               Basic programming
  Course overview                                                       Advanced / specific python
                                                                           Libraries & applications
1. Introduction to Python    10. Functions as objects              19. Linear programming
2. Python basics / if        11. Object oriented programming       20. Generators, iterators, with
3. Basic operations          12. Class hierarchies                 21. Modules and packages
4. Lists / while / for       13. Exceptions and files              22. Working with text
5. Tuples / comprehensions 14. Doc, testing, debugging             23. Relational data
6. Dictionaries and sets     15. Decorators                        24. Clustering
7. Functions                 16. Dynamic programming               25. Graphical user interfaces (GUI)
8. Recursion                 17. Visualization and optimization 26. Java vs Python
9. Recursion and Iteration   18. Multi-dimensional data            27. Final lecture
                                            10 handins
                                  1 final project (last 1 month)
Decorators
 @
www.python.org/dev/peps/pep-0318/
Python decorators are just syntatic sugar
  Python                                     Python
  @dec2                                      def func(arg1, arg2, ...):
  @dec1
  def func(arg1, arg2, ...):
                                      ≡          pass
      pass                                   func = dec2(dec1(func))
 'pie-decorator' syntax
 dec1, dec2, ... are functions (decorators) taking a function as an argument
 and returning a new function
 Note: decorators are listed bottom up in order of execution
     Recap functions
                            x
                   5
                                          +
                                                     x+y
                            y                                    8
                   3
                           list
['defg', 'ij', 'abc']                              sorted list
                  len
                        key function    sorted                   ['ij', 'abc', 'defg']
                         original                  decorated
                         function      decorator    function
Contrived example : Plus one (I-II)
plus_one1.py                                    plus_one2.py
def plus_one(x):                                def plus_one(x):
    return x + 1                                    return x + 1
def square(x):                                  def square(x):
    return x ** 2                                   return plus_one(x ** 2)
def cube(x):                                    def cube(x):
    return x ** 3                                   return plus_one(x ** 3)
print(plus_one(square(5)))                      print(square(5))
print(plus_one(cube(5)))                        print(cube(5))
Python shell                                    Python shell
| 26                                            | 26
| 126                                           | 126
Assume we always need to call plus_one on the     We could call plus_one inside functions
  result of square and cube (don’t ask why!)
  Contrived example : Plus one (III-IV)
plus_one3.py                                             plus_one4.py
def plus_one(x):                                         def plus_one(x):
    return x + 1                                             return x + 1
def square(x):                                           def plus_one_decorator(f):
    return x ** 2                                            return lambda x: plus_one(f(x))
def cube(x):                                             def square(x):
    return x ** 3                                            return x ** 2
square_original = square                                 def cube(x):
cube_original = cube                                         return x ** 3
square = lambda x: plus_one(square_original(x))          square = plus_one_decorator(square)
cube = lambda x: plus_one(cube_original(x))              cube = plus_one_decorator(cube)
print(square(5))                                         print(square(5))
print(cube(5))                                           print(cube(5))
Python shell                                             Python shell
| 26                                                     | 26
| 126                                                    | 126
   Overwrite square and cube with decorated versions   Create a decorator function plus_one_decorator
Contrived example : Plus one (V-VI)
plus_one5.py                          plus_one6.py
def plus_one(x):                      def plus_one_decorator(f):
    return x + 1                          def plus_one(x):
def plus_one_decorator(f):                    return f(x) + 1
    return lambda x: plus_one(f(x))       return plus_one
@plus_one_decorator                   @plus_one_decorator
def square(x):                        def square(x):
    return x ** 2                         return x ** 2
@plus_one_decorator                   @plus_one_decorator
def cube(x):                          def cube(x):
    return x ** 3                         return x ** 3
print(square(5))                      print(square(5))
print(cube(5))                        print(cube(5))
Python shell                          Python shell
| 26                                  | 26
| 126                                 | 126
        Use Python decorator syntax   Create local function instead of using lambda
Contrived example : Plus one (VII)
plus_one7.py
def plus_one_decorator(f):
    def plus_one(x):
        return f(x) + 1
    return plus_one
@plus_one_decorator
@plus_one_decorator
def square(x):                A function can have an
    return x ** 2              arbitrary number of decorators
@plus_one_decorator
@plus_one_decorator
                               (also the same repeated)
@plus_one_decorator
def cube(x):
    return x ** 3
print(square(5))
print(cube(5))
Python shell
| 27
| 128
Handling arguments                        run_twice2.py
                                          def run_twice(f):
                                              def wrapper(*args):
run_twice1.py                                     f(*args)
                                                  f(*args)
def run_twice(f):
    def wrapper():                             return wrapper
        f()                               @run_twice
        f()                               def hello_world():
   return wrapper                             print("Hello world")
@run_twice                                @run_twice
def hello_world():                        def hello(txt):
    print("Hello world")                      print("Hello", txt)
hello_world()                             hello_world()
                                          hello(“Mars")
Python shell
                                          Python shell
| Hello world
| Hello world                             |   Hello   world
                                          |   Hello   world
                                          |   Hello   Mars
   ”wrapper” is a common name for the     |   Hello   Mars
     function returned by a decorator
                                        args holds the arguments in a tuple
                                        given to the function to be decorated
Question – What does the decorated program print ?
         decorator_quizz.py
         def double(f):
             def wrapper(*args):          7
                 return 2 * f(*args)
             return wrapper               10
         def add_three(f):                14
             def wrapper(*args):
                 return 3 + f(*args)
                                          17
             return wrapper               20
         @double                          Don’t know
         @add_three
         def seven():
             return 7
         print(seven())
                              integer_sum1.py
                              def integer_sum(*args):
                                  assert all([isinstance(x, int) for x in args]),\
Example:                                 "all arguments most be int"
                                  return sum(args)
                              Python shell
Enforcing                     > integer_sum(1, 2, 3, 4)
                              | 10
argument                      > integer_sum(1, 2, 3.2, 4)
                              | AssertionError: all arguments most be int
types                         integer_sum2.py
                              def enforce_integer(f):   # decorator function
                                  def wrapper(*args):
                                      assert all([isinstance(x, int) for x in args]),\
                                             "all arguments most be int"
 Defining decorators can             return f(*args)
                                  return wrapper
  be (slightly) complicated   @enforce_integer
                              def integer_sum(*args):
 Using decorators is easy        return sum(args)
                              Python shell
                              > integer_sum(1, 2, 3, 4)
                              | 10
                              > integer_sum(1, 2, 3.2, 4)
                              | AssertionError: all arguments most be int
Decorators can take arguments
Python                                    Python
@dec(argA, argB, ...)                     def func(arg1, arg2, ...):
def func(arg1, arg2, ...):
    pass
                                    ≡         pass
                                          func = dec(argA, argB, ...)(func)
dec is a function (decorator) that takes a list of arguments and returns a function
(to decorate func) that takes a function as an argument and returns a new function
Example: Generic type enforcing
print_repeated.py
def enforce_types(*decorator_args):
    def decorator(f):
        def wrapper(*args):
            assert len(args) == len(decorator_args),\
                   ("got %s arguments, expected %s" % (len(args), len(decorator_args)))
            assert all([isinstance(x, t) for x, t in zip(args,decorator_args)]),\
                   "unexpected types"
               return f(*args)
        return wrapper
   return decorator
@enforce_types(str, int) # decorator with arguments
def print_repeated(txt, n):
    print(txt * n)
print_repeated("Hello ", 3)
print_repeated("Hello ", "world")
Python shell
| Hello Hello Hello
| AssertionError: unexpected types
Example: A timer decorator
time_it.py
import time
def time_it(f):
    def wrapper(*args, **kwargs):
        t_start = time.time()
        result = f(*args, **kwargs)
        t_end = time.time()
                                                      Python shell
        t = t_end - t_start
        print("%s took %.2f sec" % (f.__name__, t))   |   The sum is: 499999500000
        return result                                 |   slow_function took 0.27 sec
   return wrapper                                     |   The sum is: 1999999000000
                                                      |   slow_function took 0.23 sec
@time_it                                              |   The sum is: 7999998000000
def slow_function(n):                                 |   slow_function took 0.41 sec
    sum_ = 0                                          |   The sum is: 31999996000000
    for x in range(n):                                |   slow_function took 0.81 sec
         sum_ += x                                    |   The sum is: 127999992000000
    print("The sum is:", sum_)                        |   slow_function took 1.52 sec
for i in range(6):                                    |   The sum is: 511999984000000
    slow_function(1_000_000 * 2**i)                   |   slow_function took 3.12 sec
Built-in @property
 decorator specific for class methods
 allows accessing x.attribute() as x.attribute,
  convenient if attribute does not take any arguments (also readonly)
rectangle1.py                             rectangle2.py
class Rectangle:                          class Rectangle:
    def __init__(self, width, height):        def __init__(self, width, height):
        self.width = width                        self.width = width
        self.height = height                      self.height = height
#   @property                                @property
    def area(self):                          def area(self):
        return self.width * self.height          return self.width * self.height
Python shell                              Python shell
> r = Rectangle(3, 4)                     > r = Rectangle(3, 4)
> print(r.area())                         > print(r.area)
| 12                                      | 12
Class decorators
  Python               Python
  @dec2                class A:
  @dec1
  class A:
                   ≡     pass
    pass               A = dec2(dec1(A))
     @functools.total_ordering (class decorator)
time_it.py
import functools
                                                                    Automatically creates
                                                                    <, <=, >, >= if at least
@functools.total_ordering
class Student():                                                    one of the functions
    def __init__(self,name, student_id):                            is implemented and
        self.name = name
        self.id = student_id                                        == is implemented
   def __eq__(self, other):
       return (self.name == other.name                              Python shell
               and self.id == other.id)
                                                                    > donald < grandma
   def __lt__(self, other):
       my_name = ', '.join(reversed(self.name.split()))
                                                                    | True
       other_name = ', '.join(reversed(other.name.split()))         > grandma >= gladstone
       return (my_name < other_name                                 | False
               or (my_name == other_name and self.id < other.id))   > grandma <= gladstone
donald = Student('Donald Duck', 7)                                  | True
gladstone = Student('Gladstone Gander', 42)                         > donald > gladstone
grandma = Student('Grandma Duck', 1)                                | False
Summary
 @decorator_name
 Pyton decorators are just syntatic sugar
 Adds functionality to a function without having to augment each
  call to the function or each return statement in the function
 There are decorators for functions, class methods, and classes
 There are many decorators in the Python Standard Library
 Decorators are easy to use
 ...and slightly harder to write