三周五次课(11月3日)
1.生成式和生成器
1.1列表生成式是python受欢迎的语法之一,通过一句简洁的语法就可以对一组元素进行过滤,还可以对得到的元素进行转换处理。
语法格式为:
[exp for val in collection if condition]
相当于
result = [] for val in collection: if (condition): result.append(exp)
例子:
a = [x * x for x in xrange(10) if x * x % 2 == 0] print(type(a)) print(a)
结果:
<type ‘list‘> [0, 4, 16, 36, 64]
解释:
1,以此取出xrange(10)从0到9的数字
2,判断x*x是偶数,就保留,存在新的字典中
3,把所有符合x*x是偶数的元素都放到新的列表中返回
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。
生成器是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。调用该函数将返回一个可用于生成连续 x 值的生成器【Generator】,简单的说就是在函数的执行过程中,yield语句会把你需要的值返回给调用生成器的地方,然后退出函数,下一次调用生成器函数的时候又从上次中断的地方开始执行,而生成器内的所有变量参数都会被保存下来供下一次使用。
要创建一个generator,有很多种方法。第一种方法是把一个列表生成式的[]改成(),就创建了一个generator:
例子:
a = (x * x for x in xrange(10) if x * x % 2 == 0) print(type(a)) print(a.next()) print(a.next()) print(‘aaaaaaa‘) for i in a: print(i)
结果:
<type ‘generator‘> 0 4 aaaaaaa 16 36 64
解释:
generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素为止
1.2 定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:
在了解这个方法之前,我们先来看一个小例子,更好的帮助你理解yield的使用
def fib(n): sum = 0 i = 0 while (i < n): sum = sum + i i += 1 print(sum) fib(10)
结果:
0 1 3 6 10 15 21 28 36 45
解释:
这个程序很简单,就是求0到9所有数字之和,接下来,我们只要稍微改动一下,你看看有什么差别
def fib(n): sum = 0 i = 0 while (i < n): sum = sum + i i += 1 yield sum for x in fib(10): print(x) print(type(fib(10)))
结果:
0 1 3 6 10 15 21 28 36 45 <type ‘generator‘>
结果和上面的结果是一样的,但是有什么不同呢,简而言之,包含yield语句的函数会被特地编译成生成器。当函数被调用时,他们返回一个生成器对象,这个对象支持迭代器接口。每当遇到yield关键字的时候,你可以理解成函数的return语句,yield后面的值,就是返回的值。但是不像一般的函数在return后退出,生成器函数在生成值后会自动挂起并暂停他们的执行和状态,他的本地变量将保存状态信息,这些信息在函数恢复时将再度有效,下次从yield下面的部分开始执行。
解释:
1,因为以上函数有关键字yield,所以生成的是一个生成器。
2,通过for循环调用生成器,当执行到yield的时候,返回sum的值,sum为0,此时暂停并记录sum的值
3,打印sum的值,然后继续往下执行。此时跳入下一个循环while(1<10)
4,直到遇到yield的时候,返回sum的值。
5,反复执行3,4步骤,直到循环结束,最终程序退出
二者的区别很明显:
一个直接返回了表达式的结果列表, 而另一个是一个对象,该对象包含了对表达式结果的计算引用, 通过循环可以直接输出
生成器不会一次性列出所有的数据,当你用到的时候,再列出来,更加节约内存的使用率。
2.迭代器
Iterable(可迭代对象)和Iterator(迭代器)主要区别是:
凡是可以用for 循环的都是Iterable(可迭代对象),凡是需要通过next()函数获得值的可迭代对象都是 Iterator(迭代器)。
所以生成器可以被next()函数调用并不断返回下一个值的对象称为迭代器)(可以简单理解为生成器就是迭代器的可迭代对象)
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
3.装饰器
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
简单装饰器
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args, **kwargs) retrun wrapper def bar(): print(‘i am bar‘) bar = use_logging(bar) bar()
函数use_logging就是装饰器,它把执行真正业务方法的func包裹在函数里面,看起来像bar被use_logging装饰了。在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)。
@符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作
def use_logging(func): def wrapper(*args, **kwargs): logging.warn("%s is running" % func.__name__) return func(*args) retrun wrapper @use_logging def foo(): print("i am foo") @use_logging def bar(): print("i am bar") bar()
如上所示,这样我们就可以省去bar = use_logging(bar)这一句了,直接调用bar()即可得到想要的结果。如果我们有其他的类似函数,我们可以继续调用装饰器来修饰函数,而不用重复修改函数或者增加新的封装。这样,我们就提高了程序的可重复利用性,并增加了程序的可读性。
装饰器在Python使用如此方便都要归因于Python的函数能像普通的对象一样能作为参数传递给其他函数,可以被赋值给其他变量,可以作为返回值,可以被定义在另外一个函数内。
练习题:用函数实现9*9乘法口诀
for i in range(1, 10): for j in range(1, i + 1): print j, ‘*‘, i, ‘=‘, i * j, ‘ ‘, print("")
结果:
1 * 1 = 1 1 * 2 = 2 2 * 2 = 4 1 * 3 = 3 2 * 3 = 6 3 * 3 = 9 1 * 4 = 4 2 * 4 = 8 3 * 4 = 12 4 * 4 = 16 1 * 5 = 5 2 * 5 = 10 3 * 5 = 15 4 * 5 = 20 5 * 5 = 25 1 * 6 = 6 2 * 6 = 12 3 * 6 = 18 4 * 6 = 24 5 * 6 = 30 6 * 6 = 36 1 * 7 = 7 2 * 7 = 14 3 * 7 = 21 4 * 7 = 28 5 * 7 = 35 6 * 7 = 42 7 * 7 = 49 1 * 8 = 8 2 * 8 = 16 3 * 8 = 24 4 * 8 = 32 5 * 8 = 40 6 * 8 = 48 7 * 8 = 56 8 * 8 = 64 1 * 9 = 9 2 * 9 = 18 3 * 9 = 27 4 * 9 = 36 5 * 9 = 45 6 * 9 = 54 7 * 9 = 63 8 * 9 = 72 9 * 9 = 81