Python快速入门03
- 迭代器就是用来迭代取值的工具,单纯的重复并不是迭代
while True: msg = input('>> ').strip() print(msg)
-
#!/usr/bin/env python # author:lijipeng hobbies = ['sing','on foot','table tennis'] index = 0 while index < len(hobbies): print(hobbies[index]) index+=1 #①:满足重复 #②:并且以每次重新赋值后的Index值作为下一次循环的索引进行取值,反复迭代 #最终可以取尽列表中所有的值
- 上述案例用到的是索引取值,那么对于字典、集合等没有索引的类型怎么取值呢,那么就必须要找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。
- 可迭代对象:内置有__iter__方法的对象都是可迭代对象,比如字符串,列表、元组、字典、集合、打开的文件
- 迭代器对象:调用obj.iter()方法返回的就是一个迭代器对象。
- 迭代器对象内置有__iter__和__next__方法的对象。
- 执行迭代器对象的.iter()方法得到的仍然是迭代器本身,而执行迭代器.next()方法就会计算出迭代器的下一个值。
- 迭代器是Python提供的一种统一的不依赖于索引的迭代取值方式,只要存在多个值,无论是序列类型还是非序列类型都可以按照迭代器的方法取值。
-
#!/usr/bin/env python # author:lijipeng hobbies = ['sing','on foot','table tennis'] i = iter(hobbies) # 每次都需要重新获取一个迭代器对象 while True: try: print(next(i)) except StopIteration: # 捕捉异常,然后终止循环 break for item in hobbies: print(item) # for循环的工作原理: # ①、调用可迭代对象hobbies内置__iter__得到一个迭代器对象 # ②、调用迭代器对象内置的__next__得到一个值 # ③、将得到的值赋给item,执行循环体完成一次循环 # ④、一直到捕捉到StopIteration一场,结束迭代
- 基于索引的迭代取值,所有的迭代状态都保存到了索引中,基于迭代器的迭代取值,所有的迭代状态都保存在了迭代器中
- 迭代器优点:为序列和非序列的类型提供了一种统一的迭代取值方式;惰性计算:在需要的时候迭代器对象才会调用内置的__next__来计算出一个值,也就是说就迭代器本身来说,同一时刻在内存中只有一个值,因此可以存放无限大的数据流。对于列表、字典、元组等容器类型,需要把所有的元素都存放在内存中,受内存大小限制,可以存放的值是有限的。
- 迭代器的缺点:除非取尽,否则无法获取迭代器的长度;只能取下一个值,迭代器产生后就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次next。
#!/usr/bin/env python # author:lijipeng hobbies = ['sing','on foot','table tennis'] i = iter(hobbies) while True: try: print(next(i)) except StopIteration: break # i1 = iter(hobbies) # 两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值 while True: try: print(next(i)) except StopIteration: break
- 若函数体包含yield关键字,再调用函数,并不会执行函数体代码,得到的返回值即生成器对象
def myrange(start, stop, step=1): print('start') while start < stop: yield start start += step print('stop') g = myrange(0, 3) print(g)
- 生成器内置有__iter__,__next__方法,所以生成器本身也是迭代器
- yield关键字作用:可以自定义迭代器,并且可以利用yield可以保存函数状态的特性,返回多个值
- yield表达式应用:
def eater(): print('ready to eat') while True: food = yield print('get the food %s, and ready to eat' %food) g = eater() print(g) next(g) g.send('骨頭') g.send('包子')
- 针对表达式形式yield,生成器对象必须先初始化一次,让函数挂起在food=yield,然后g.send()方法为函数体传值,g.send(None)等同于next(g)
-
# 编写装饰器来完成为所有表达式形式yield对应生成器的初始化操作 def init(func): def wrapper(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return wrapper @init def eater(): print('ready to eat') while True: food = yield print('get the food:%s and ready to eat' %food) g = eater() g.send('薯条')
-
# yield表达式 def init(func): def wrapper(*args,**kwargs): res = func(*args,**kwargs) next(res) return res return wrapper @init def eater(name): print('%s 开始吃东西了' %name) food_list = [] while True: food = yield food_list food_list.append(food) # print('%s 吃了 %s' %(name, food)) print('%s 已经吃完了哈哈') g = eater('张三') # next(g) print(g.send('骨头')) print(g.send('馒头')) print(g.send('大力')) print(g.send('小李'))
- 生成器本质上也是迭代器所以得到生成器后可以用for进行迭代循环,有yield的函数的返回值是一个生成器对象,每next一次就会把函数挂起在yield的位置,yield后面的东西就是该函数的返回值,也就是执行next后的返回值。send一次返回一次。由于调用send方法需要初始化next方法一次,所以可以通过给函数加一层装饰器,简化操作。yield也可以返回多次值,形式为变量名=yield 值。
- 三元表达式
# 三元表达式 x = 1 y = 2 res = x if x > y else y print(res)
- 列表生成式
egg_list = [] for i in range(1,10): egg_list.append('%s个鸡蛋' %i) print(egg_list) egg_list = ['%s个鸡蛋' %i for i in range(1,10)] print(egg_list)
- 创建生成器对象有两种方式:一种是调用带yield关键字的函数,另一种就是生成器表达式。
-
l = [x*x for x in range(1,3)] print(l) g = (x*x for x in range(1,3)) print(g) print(next(g)) print(next(g)) # print(next(g)) 抛出异常 # 对比列表生成式,生成器表达式的优点是节省内存(一次只产生一个值在内存中) # 如果我们要读取一个大文件的字节数,应该基于生成器表达式的方式来完成
- python 可以设置递归深度,同事Python不是一门函数式编程语言,无法对递归进行尾递归优化
import os import sys # sys.setrecursionlimit(10000) 设置递归深度
- 什么时候用递归呢?在你不知道要调用多少层的情况下可以考虑用递归
items = [[1, 2], 3, [4, [5, [6, 7]]]] def foo(items1): for i in items: if isinstance(i, list): foo(i) else: print(i, end=' ') foo(items)
- 编程的世界里常见的流派有哪些呢?面向过程、函数式、面向对象
- 面向过程的优缺点:将复杂的问题流程化,进而简单化,可扩展性差
- 面向过程的应用场景:功能一旦实现就很少需要改变,或者做一次性任务,如果是复杂的,且需要不断迭代和维护,面向过程就不适用了
- 函数式编程注重的是执行结果而非执行的过程,python不是一门函数式编程的语言,但是仍然为我们提供了很多函数式编程好的特性,如lambda,map,reduce,filter。
- lambda匿名函数调用方式:第一种是直接匿名函数后加(实际参数),第二种是将匿名函数赋值给另外一个函数,然后再用新的函数名调用传参
def add(x, y, z): return x + y + z res = (lambda x, y, z:x+y+z)(1,2,3) print(res) func = lambda x, y, z:x+y+z # print(func) 匿名的本质就是要没有名字,所以此处为匿名函数指定名字是没有任何意义的,打印该函数名
at 0x019B4738> res = func(1,2,3) print(res) - map示例第一个参数是函数,第二个参数是可迭代对象
dic = { 'li': 1000, 'wang': 2000, 'seen': 300, 'niu': 100 } print(max(dic, key=lambda k: dic[k])) print(min(dic, key=lambda k: dic[k])) array = [1, 2, 3, 4, 5] res = map(lambda x: x * x, array) print(list(res)) res = map(lambda x: x * 2, array) print(list(res)) # map函数得到的结果仍然是迭代器
- reduce 第一个参数是函数,第二个参数是可迭代对象,第三个参数是初始值
from functools import reduce array=[1,2,3,4,5] res = reduce(lambda x,y:x+y,array,10) print(res)
- filter
from functools import reduce array=[1,2,3,4,5] res = filter(lambda x:x>3,array) # filter过滤后的数据也是一个迭代器对象 print(list(res))
-
模块的作用:保证了代码的重用性和程序的结构性和可维护性
- 首次导入模块会做哪三件事情:执行源代码文件;产生一个新的名称空间用来存放源文件执行过程中产生的名字;在当前执行文件所在的名称空间中得到一个名字foo,该名字指向新创建的模块名称空间,若要引用模块名称空间的名字,需要加上该前缀。
- 模块的导入顺序为:内置模块,第三方模块,自定义模块
- 函数内导入模块则属于局部作用域
- from 模块名 import * 只能在模块最顶层使用的方式导入,在函数内非法
-
# import foo # foo.get() # foo.change() # foo.get() # foo.Foo().func() from foo import x from foo import get # x = 2 # x = '你好呀' # a = x # print('a:%s' %a) y = 4 def get(): print('你妈呀') get()
-
__all__ = [x] # 在这里定义了*的范围 x=1 def get(): print(x) def change(): global x x=0 class Foo: def func(self): print('from the func')
-
from foo import * get() # 所以在这里调用是会报错的
- 起别名的好处:精简代码;避免与当前名字冲突,可以保持调用方式的一致性
if data_format == 'json': import json as serialize #如果数据格式是json,那么导入json模块并命名为serialize elif data_format == 'pickle': import pickle as serialize #如果数据格式是pickle,那么导入pickle模块并命名为serialize data=serialize.load(fn) #最终调用的方式是一致的
- 循环导入案例1:直接运行run.py
print('正在导入m1') from m2 import y x='m1'
print('正在导入m2') from m1 import x y='m2'
import m1 - 循环导入案例2:直接运行m1.py
- 循环导入案例3:将导入操作放在赋值的后边,完美解决
print('正在导入m1') x='m1' from m2 import y
print('正在导入m2') y='m2' from m1 import x
import m1 - 循环导入案例4:将导入的语句放在函数内,只要在调用函数时才会执行其内部代码
print('正在导入m1') def f1(): from m2 import y print(x,y) x = 'm1' # 文件:m2.py print('正在导入m2') def f2(): from m1 import x print(x,y) y = 'm2' # 文件:run.py内容如下,执行该文件,可以正常使用 import m1 m1.f1()
- sys.path被称为模块的搜索路径,它是一个列表类型
import sys print(sys.path)
-
import sys sys.path.append(r'C:/Users/Administrator/PycharmProjects/digui/haha/xixi') import foo print(sys.path)
#foo.py ... if __name__ == '__main__': foo.py被当做脚本执行时运行的代码 else: foo.py被当做模块导入时运行的代码
-
#!/usr/bin/env python #通常只在类unix环境有效,作用是可以使用脚本名来执行,而无需直接调用解释器。 "The module is used to..." #模块的文档描述 import sys #导入模块 x=1 #定义全局变量,如果非必须,则最好使用局部变量,这样可以提高代码的易维护性,并且可以节省内存提高性能 class Foo: #定义类,并写好类的注释 'Class Foo is used to...' pass def test(): #定义函数,并写好函数的注释 'Function test is used to…' pass if __name__ == '__main__': #主程序 test() #在被当做脚本执行时,执行此处的代码
- 包就是一个含有__init__.py文件的文件夹,文件夹内可以组织子模块或子包
- 创建包的目的是为了被导入使用,包只是模块的一种形式而已,包的本质就是一种模块
- 导入包会做三件事情:执行包下的__init__.py文件;产生一个新的名称空间用来存放__init__.py执行过程中产生的名字;在当前执行文件所在的名称空间中得到一个名字pool,该名字指向__init__.py的名称空间,导入包时并不会导入包下所有的子模块与子包。
- 1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如import 顶级包.子包.子模块,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。 2、包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间 3、import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
-
1、相对导入只能在包内部使用,用相对导入不同目录下的模块是非法的
2、无论是import还是from-import,但凡是在导入时带点的,点的左边必须是包,否则语法错误