COMP9021 Principles of Programming WEEK5 Optional

1. Quiz_3 hints

最大的难点是如何避免完全重复的stairs,比如一个3 step的size 2 stairs包含2个2 step的size 2 stairs和3个1 step的size 2 stairs。解决方法是做标记,以3 step的size 2 stairs为例:

#1 2 3
#2   1 2
#3     3 2
#4       1 2

第一行的stairs有3个step,size2;第二行进入的时候会重复遇见从1开始的另一个2个stp,size2的,它是前面staris的一部分,不能计算。所以要想一个解决办法,既能做标记,同时不影响其他size查找的工作。做标记要求不能出现矩阵中有的数字,所以倾向于使用negative integer;不能影响其他size就是说最好只和当下size有关,所以倾向于用size大小做标记。两条叠加,最终确定使用-size做标记。这样上例变成:

#1 2 3
#2   1 -2
#3      3 -2
#4         1 -2

这样从第二行进入的时候一旦改变方向的数字是-size则放弃,同时当size2全部检索完成之后,标记-2和其他的非0数字全部一样,不对size3造成影响,size3的标记是-3.

from random import randrange

def print_grid():
    for i in range(10):
        print(' '.join(str(int(e != 0)) for e in grid[i]))

density = 2
grid = [[randrange(density) for _ in range(10)] for _ in range(10)]

print_grid()
#每次搜索全部的同一个size,比如size2,如果完成一个step,在最后的部分把数字改成-size,以避免sub-stairs

2. 函数嵌套

def f(a):
    def g (b):
        return a * b
    return g

print(f(2))
print(f(2)(3))
>>>
.g at 0x10e608730>
6

对于函数f来说,输入值是a,返回值是函数g;
对于函数g来说,输入值是b,返回值是函数f的输入值和b的乘积。
所以f(2)输出的是一个函数的内存地址;而对于f(2)(3)而言,a = 2,g = f(2),所以f(2)(3) = g(3),b = 3,所以a * b = 6,接着g函数返回6,所以a函数返回g函数的返回值6,最终输出6。

3. 变化的input variable

很多时候一个函数不能确定有多少输入值,比如定义一个未知用户数量计算平均分的函数,此时多少用户使用是未知的,当然可以直接定义一个变量表征用户数量,但也可以使用可变化的input variable。
python中常见的flexible input viriable有两个:*args和**kwargs。
(1)*args
*args is used to send a non-keyworded variable length argument list to the function.例如:

def test_var_args(f_arg, *argv):
    print ("first normal arg:", f_arg)
    for arg in argv:
        print ("another arg through *argv:", arg)

test_var_args('yasoob','python','eggs','test')
>>>
first normal arg: yasoob
another arg through *argv: python
another arg through *argv: eggs
another arg through *argv: test

(2)**kwargs
**kwargs allows you to pass keyworded variable length of arguments to a function. You should use **kwargs if you want to handle named arguments in a function. 例如:

def greet_me(**kwargs):
    if kwargs is not None:
        for key, value in kwargs.items():
            print ("%s == %s" % (key,value))

greet_me(name = "yasoob")
>>>
name == yasoob

(3)实例
通常我们要评估一个程序的运算时间,应用函数嵌套+变化的input variable,实现方式如下

def running_time(f):
    def g(*args):    
    #因为这个函数是对其他函数的运行时间进行计算,而其他函数的input个数是随意的,可以是1个,也可能是多个,所以使用*args  
        from time import time
        before = time()
        f(*args)
        after = time()
        print(f'It took {after - before} seconds to execute the function.')
    return g

def h(n):
    for _ in range(n):
        pass

running_time(h)(10_000_000)
# running_time(h) = function g returned by running_time with f initialised to h
#上面的嵌套函数running_time(h)(10_000_000)和下面的代码等价
h = running_time(h)
h(10_000_000)
>>>
It took 0.316251277923584 seconds to execute the function.
It took 0.3344690799713135 seconds to execute the function.

4. Decorator

Decorator是一种简化写法,方便函数的嵌套,例如上文实例可以用decorator实现如下:

def running_time(f):
    def g(*args):      
        from time import time
        before = time()
        f(*args)
        after = time()
        print(f'It took {after - before} seconds to execute the function.')
    return g

@running_time
def h(n):
    for _ in range(n):
        pass

h(10_000_000)

decorator的好处是不需要复杂的操作,方便理解。上面程序可以理解为写了两个函数running_time(f)和h(n),如果调用h(n)函数,则自动嵌套running_time(f)。
@running_time可以理解为

h = running_time(h)

5. Decorator实例

应用上文的decorator方法实现函数调用的计数,这里新引入了另一个函数的attribute,代码中用符号'.'来表示。

def count_function_calls(f):
    def super_f(*args):
        f(*args)
        super_f.count += 1
        #在super_f()函数的attribute中加入一个count的统计
    super_f.count = 0
    return super_f

@count_function_calls
def hi():
    print('Hello!')
#每次调用hi()函数的时候都会嵌套函数count_function_calls(f),这里的decorator(@count_function_calls)等价于
#hi = count_function_calls(hi)

hi()
hi()
hi()
hi.count
>>>
Hello!
Hello!
Hello!
3

如果不使用函数的attribute来记录count,使用下面的代码不成立,要小心local和global variable

def count_function_calls(f):
    count = 0
    def super_f(*args):
        f(*args)
        count += 1
    return super_f
#不成立,因为count是local variable

@count_function_calls
def hi():
    print('Hello!')
    
hi()
hi()
hi()
hi.count

如果把count改成global variable则可以运行,count = 0并不会执行多次,从而影响count的数值。编译过程是:
1.先编译count_function_calls(),这是count被赋值0;
2.再编译hi();
3.再编译decorater @count_function_calls(),此后执行的所有hi()都是嵌套函数,并不会重新给count赋值0;
所以count = 0只初始化一次,在最开始编译count_function_calls()的时候。

def count_function_calls(f):
    count = 0
    def super_f(*args):
        nonlocal count
        f(*args)
        count += 1
        print(f'Function called for the {count}th time.')
    return super_f
#成立,因为count是nonlocal variable

@count_function_calls
def hi():
    print('Hello!')
    
hi()
hi()
hi()

你可能感兴趣的:(COMP9021 Principles of Programming WEEK5 Optional)