Python3 基础知识笔记
############################################################################################# # Subject: Python3 # Author : Wang Chunwang # Update : 2020.03 # Words : 8万余字 # 在cmd中执行py文件(路径可加双引号),如:python E:PythonSrcmypy_packagedemo.py ############################################################################################# 1、输入函数:input('输入提示信息'),获取用户输入,保存成一个字符串! 输出(打印)函数:print(*args, sep=' ', end=' ', file=None),"sep"分隔符号,"end"打印后的结束方式,活用print的参数,可以实现灵活的打印控制。 2、Python数据类型都是"类(class)"类型。 Python中的变量不需要声明类型(根据Python的动态语言特性而来)。 每个变量在使用前都必须赋值!变量赋值以后才会被创建。 在Python中,变量本身没有数据类型的概念,通常所说的“变量类型”是变量所引用的对象的类型,或者说是变量的值的类型。 一些内置函数: dir(objname):输出对象的属性和方法。最棒的辅助函数之一! dir(__builtins__):输出所有Python内置系统的变量和函数,首字母大写的是异常类名。 dir(__name__):输出Python对象的属性和方法。 vars():输出某对象所有属性,vars()跟"__dict__"是等同的。 chr():将十进制数转化为对应的ASCII字符。 ord():将ASCII字符转化为对应的十进制数。 divmod():返回商和余数的元组。 reversed():反转序列。 repr():调用对象所属类的__repr__方法,与print功能类似。 3、数据类型: Python七种标准数据类型: -----------------+------------------------------------------------ | Number (数字,包含True、False) 不 +------------------------------------------------ 可 | String (字符串) 变 +------------------------------------------------ 类 | Tuple (元组) 型 +------------------------------------------------ | None (真空) -----------------+------------------------------------------------ 可 | List (列表) 变 +------------------------------------------------ 类 | Set (集合) 型 +------------------------------------------------ | Dictionary (字典) -----------------+------------------------------------------------ 注意:在Python2中是没有布尔型的,它用数字 1 表示True, 用 0 表示False。 到Python3中,把True和False定义成关键字了,但它们的值还是 1 和 0,它们可以和数字相加。 附加说明: (1)整型:int,长度32位(即long) 赋值方式:① x=99 ② x=int(99),如果是浮点数,则取整。 转换函数:int(),bin(),oct(),hex()等。 进制表示法:二进制0b,八进制0o,十六进制0x。进行二进制计算时加前缀"0b",其他类似。 (2)布尔型:bool (3)浮点型:float(双精度)、complex(复数)、Decimal(定点数) Decimal入参是整数或字符串,因为float精度不够。 (4)字符串型:str (Unicode字符构成) 使用三引号(''' '''),可以格式化缩进和换行。 "*"操作符能复制字符串、列表、元组等,如s='#' * 9,即9个#。 使用""连接,可以换行书写代码。如: s='abcd' 'efgh' 字符串对象方法: s.strip(c):去除空格或指定的字符c,默认空格;(lstrip/rstrip); s.zfill(w):宽度小于w用"0"前补; s.ljust(w,c):左对齐,宽度w,填充符c;rjust()右对齐。 s.join(q):用"s"将序列"q"中的字符串元素拼接起来; s.partition(sep):以sep为分隔符,把字符串分割,返回一个元组(包含分割符); s.split(sep=None, maxsplit=-1):把字符串以sep为分隔符分割,maxsplit为分割次数,返回一个列表(不包含分割符); s.find(t):返回t的最左位置,未找到,返回-1; s.index(t):返回t的最左位置,未找到,返回ValueError异常。推荐用find(); s.upper():全部转为大写; s.lower():全部转为小写; s.capitalize():首字母大写; s.title():每个单词首字母大写; s.center(width, fillchar=None):字符串内容居中,在给定的字符串长度width中内容居中,两边用提供的字符fillchar填充,fillchar默认为空; s.count(sub, start=None, end=None):在指定位置start与end之间,计算sub字符的数量; s.startswith(suffix, start=None, end=None):判断字符串在start与end位置之间是不是以某个子序列suffix开头,类似方法还有endswith(); s.replace(old, new, count=None):把字符串中某个子序列old替换成一个新序列new,count是替换数,返回一个新字符串; s.isdigit():判断是否全是数字; s.isalpha():判断是否全是字母; s.isalnum():判断是否包含字母或数字; s.isspace():判断是否是空格字符串; s[start:stop:step]:切片操作,截取子字符串。 新式格式化方法(传统格式化方法见本文第11标号): s.format():占位格式化,用{}作为占位符。如'...{0}...{1}...{2}'.format('A','B','C') 或 '...{name}...{age}...'.format(name='She', age=20); format参数说明:位置参数必须在关键字参数之前,可以传字典参数。数字参数可以不写。 用冒号":"引导格式化语法,这里的name是指占位为{name}的字符: '{name:20}'.format(name=str) # 宽度20,默认左对齐 '{name:<20}'.format(name=str) # 宽度20,左对齐 '{name:>20}'.format(name=str) # 宽度20,右对齐 '{name:^20}'.format(name=str) # 宽度20,居中 '{name:*>20}'.format(name=str) # 宽度20,右对齐,用*填充 '{name:.4}'.format(name=str) # 剪切字符,最大宽度4 '{name:+}'.format(name=num) # 对于数值num,带符号输出 '{name:,}'.format(name=num) # 千位逗号分组,如银行计数 '{name:.2f}'.format(name=num) # 带2位小数的浮点数 '{name:#x}'.format(name=num) # 返回十六进制形式,#b二进制,#o八进制 '{name:e}'.format(name=num) # 科学计数 (4)字节型(二进制): bytes(固定的)、bytearray(可变的,可插入可删除某个字符)。 如,b=b'python' 或 'python'.encode() barr=bytearray(b), barr.pop(1), barr.insert(1,ord('a')) (5)其他类型:列表、元组、字典、集合。 4、运算符"/"结果是浮点数,"//"结果是取整,"**"表幂运算,"!="表不等于。 is:基于身份比较。如,x=('python'),y=('python'),x is y 返回True。"is"和"=="结果类似。 ==:基于对象值比较。如,x=('python'),y=('python'),x == y 返回True。Python支持链式比较,1 <= a <= 3。 id():获取内存地址。 in:理解为属于,判断成员关系,val in lists。 and 、or:与、或。短运算逻辑,返回布尔值,或决定结果的操作数。Python中没有"&&"、"||"、"!"符号。 not:非。总是返回布尔值。 del obj:只是删除引用对象(当无任何引用对象时,数据才被回收),没有删除内存地址。 []:切片操作符或项目操作符。 6、ASCII编码:只有127个字符,字母占1个字节;(无法编码汉字、日文等) Unicode编码:万国码,字母占2个字节,汉字占3个字节;(字母占用了多余的空间) utf-8编码:字母占1个字节,汉字占3个字节。(推荐) 7、方法encode('编码格式'):如,'中文'.encode('utf-8')。 8、方法decode('解码格式'):如,b'xe6xb1xaa'.decode('utf-8')。Python对bytes类型的数据用带"b"前缀的单引号或双引号表示。 9、r'字符':原生字符串,使转义无效。 10、#!/usr/bin/env python3 告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释; # -*- coding: utf-8 -*- 告诉Python解释器,按照utf-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码。 Python文件名不能和关键字、模块名相同,否则很可能会报找不到属性的错误! 11、"%"传统格式化(变量前加格式): %d 格式化整数 %f 格式化浮点数 %s 格式化字符串 %o 格式化八进制 %x 格式化十六进制 格式化辅助:"."精度,"-"左对齐,"+"整数带加号,"0"数字前面填充0,等。 如,print('成绩增长率:%.2f' %(num)) #保留两位小数 12、列表(list),用list()或"[]"初始化。有序、重复,列表是可变对象。如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素,以此类推,可以获取倒数第2个、倒数第3个。 若L=[1, 2, 3, 4], print(L) #整个列表 print(*L) #在print中用"*"拆出列表所有元素(只适用于print) 组合类型函数,list(),tuple(),dict(),set(),frozenset()只能传入一个参数。 list适合当做[堆栈],queue.Queue作为[单向队列],collections.deque作为[双向队列]。 13、列表对象方法: 1)append(self, p_object):在原列表最后位置上追加一个新元素(注意是一个元素),不生成新的列表。 2)clear(self):清空列表里面的元素,不生成新列表。 3)copy(self):复制一份列表,注意这是浅拷贝。 5)extend(self, iterable):把iterable中的每个元素扩展成列表的元素,iterable可以是字符串、列表、集合、元组、字典。等价于列表的加法运算,如[1,2] + [3,4] --> [1,2,3,4] 6)index(self, value, start=None, stop=None):查找列表中value元素索引位置,start与stop参数是查找起始与结束位置,默认为None。 7)insert(self, index, p_object):在列表index索引位置插入一个元素p_object,当index大于列表包含的元素个数时,在最后位置插入元素。 8)pop(self, index=None):从列表中取出index位置的值,index默认为None,此时弹除并返回列表中最后一个值。 9)remove(self, value):移除列表中第一个出现的value元素,value元素不存在列表中时,则抛出ValueError。 10)reverse(self):反转列表元素的位置。 11)sort(self, key=None, reverse=False):给列表中的元素排序,改变原列表!reverse默认False(升序)。【而sorted()函数是生成副本】。 12)[start:stop:step]:切片操作,从列表中取出一部分元素生成一个新列表,start与stop默认为None,step表示步长值,默认是一个接着一个切取, 如果为2,则表示进行隔一取一操作。步长值为正时表示从左向右取,如果为负,则表示从右向左取。步长值不能为0。 13)索引[index]:获取索引位置index的值。 讲讲深浅拷贝(拷贝的目标就是让数据复制并独立出来,拷贝越深,数据越独立): 1.浅拷贝:数据不完全独立,比如列表拷贝,只有第一维数据被独立,而更高维度的数据会共享引用(即同步修改)。 2.深拷贝:数据完全独立,比如列表拷贝,所有维度数据被独立。import copy,copy.deepcopy() 14、元组(tuple),用tuple()或"()"初始化。有序、重复,tuple和list非常类似,但是tuple是不可变对象,一旦初始化就不能修改,只读,更安全。 元组只保证它的一级子元素不可变,对于嵌套的元素内部,不保证不可变! 如果可能,能用tuple代替list就尽量用tuple。 最好以","结尾,当tuple只有一个元素时,必须以","结尾。 元组对象方法: 1)count(self, value):统计元组中包含value元素的数量,返回一个int值。 2)index(self, value, start=None, stop=None):索引,查找元组中value元素第一个出现的位置,start与stop参数是查找起始与结束位置,默认为None。 3)[start:stop:step]:切片操作。 4)索引[index]:获取索引位置index的值。 15、for i in <范围>: 循环就是把每个元素代入变量i,然后执行缩进块的语句。也就是遍历。 for ... in range(0, 10): 用range()实现循环次数,这里是10次。 for _ in range(n): 使用下划线"_",循环n次执行循环体。此时"_"作为临时性的名称使用,忽略循环计数中的实际值。 16、range(x1, x2, 步长)函数,可以生成一个整数序列,长度为(x2-x1),左闭右开规则。range(n)范围是"0 ~ n-1"。 17、不要滥用break和continue语句。break和continue会造成代码执行逻辑分叉过多,容易出错。 大多数循环并不需要用到break和continue语句。 (1)break只能用于循环体内。其效果是直接结束并退出当前循环,剩下的未循环的工作全部被忽略和取消。 (2)与break不同,continue语句用于跳过当前循环的剩余部分代码,直接开始下一轮循环。 18、字典(dict),用dict()或"{}"初始化。无序、不重复,字典是可变对象,在其他语言中也称为map,使用可哈希算法的键-值("key":"value")存储,具有极快的查找速度。字典无序。 通过in判断key是否存在,如果key不存在,返回false,如:'cat' in d; 字典对象方法: 1)clear(self):清除字典中的所有元素。 2)copy(self):复制一份元组,注意这是浅拷贝。 3)get(self, k, d=None):获取字典中键为k的值,如果字典中不包含k,则给出d值,d默认为None。 4)update(self, E=None, **F):给字典新增元素(可以是多个元素),没有返回值。用法:dict1.update(dict2)。 5)items(self):遍历字典的一个方法,把字典中每对key和value组成一个元组,并把这些元组放在一个类似列表的dict_items中返回。 6)keys(self):遍历字典键keys的一个方法,返回一个类似列表的dict_keys,与items方法用法相同。 7) values(self):遍历字典值value的一个方法,返回一个类似列表的dict_values,与items方法用法相同。 8)pop(self, k, d=None):弹除并返回字典中键为k的值。 9)popitem(self):从字典中随机取出一组键值,返回一个新的元组。 10)setdefault(self, k, default=None):从字典中获取键为k的值,当存在k时,功能和get基本一致,当字典中不存在k时,在原字典上添加键为k、值为default的项,并返回default值。 11)fromkeys(self, iter, value=None):分别以iter中的元素为键,以value为值,创建一个字典。 19、zip(iter1, iter2, ...):打包分组为一一对应的tuple。可转成list或tuple或dict输出,如,dict(zip(('No', 'Name'), ('001', 'wong'))) --> {'No': '001', 'Name': 'wong'}。 20、dict内部存放的顺序和key放入的顺序是没有关系的。 和list比较,dict有以下几个特点: 1)查找和插入的速度极快,不会随着key的增加而变慢; 2)占用大量的内存,内存浪费多。 而list相反: 1)查找和插入的时间随着元素的增加而增加; 2)占用空间小,内存浪费很少。 21、集合(set),用set()或"{}"初始化。但是初始化空集合必须是set(),因为"{}"表示空字典。无序、不重复,集合是可变对象。 交集:set1 & set2,并集:set1 | set2,交集之外:set1 ^ set2 固定集合(frozenset),不可变的集合。 集合没有读取操作!因为集合既不支持下标索引,也不支持字典那样的键取值。 集合对象方法: 1)add(self, *args, **kwargs):在原集合里添加一个元素。 2)clear(self, *args, **kwargs):清空原集合。 3)copy(self, *args, **kwargs):复制一份集合,注意这是浅拷贝。 4)difference(self, *args, **kwargs):差集。set1.difference(set2)等价于set1-set2,即把set1中在set2中相同的元素去掉后剩下的元素。 5)isdisjoint(self, *args, **kwargs):是否空交集,若空交集则返回True,否则返回False。 6)issubset(self, *args, **kwargs):是否是参数集合的子集。 7)pop(self, *args, **kwargs):从集合中随机(因集合无序)弹除并返回一个元素,如果集合为空,则报TypeError错误。 8)remove(self, *args, **kwargs):移除集合中的一个元素,这个元素必须在集合中,如果不在,则报TypeError错误。 9)discard(self, *args, **kwargs):删除集合中的某个元素,如果这个元素没有在集合中,不做操作。 10)union(self, *args, **kwargs):并集。 11)update(self, *args, **kwargs):给集合新增元素(可以是多个元素)。 22、"不可变对象":number、str、tuple; "可变对象":list、set、dict; 对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容; 比如,字符串虽然像列表一样,通过方括号加下标的方式获取它的子串,当然也包括切片操作,但这一切都不会修改字符串本身。 23、如果想定义一个什么事也不做的空函数,需用pass语句,如: def nop(): pass 实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来以致不报错。 24、判断数据类型用内置函数:isinstance(x, (int, float))。 查看数据类型用内置函数:type(obj)。 isinstance 和 type 的区别在于: isinstance() 会认为子类是一种父类类型,考虑深度。 type() 不会认为子类是一种父类类型,不考虑深度。 25、raise TypeError('bad operand type'):抛出异常信息。 26、包:必须包含一个"__init__.py"文件。 import importname[.modulename as newname] ,使用"as"能避免同名同API的包冲突。 from importname import * ,导入非私有的一切对象,使用"*"会调用该包的"__init__.py"模块中"__all__"语句(模块名、类名、方法名列表)。 相对导入:from .. import modulename。 将包或模块放入"C:PythonLibsite-packages"中,对所有程序都可用。 27、return(或return None),跳出函数。 函数可以同时返回多个值,但其实就是一个tuple。 作用域:局部、全局(global)、外层(nonlocal)。如: class Test(): total = 0 # 全局变量(静态变量) def outerfun(self): x = 0 # 局部变量(动态变量) global total # 声明为外部全局变量 def innerfun(): nonlocal x # 声明为内部函数使用外层的变量 return return 28、(1)位置参数:不带默认值的参数,传参时,可以直接写值(这种方式必须按照顺序传参),也可以是"参数名=值"(这种方式可以不按顺序传参)。如: def func(para): pass 用法:func(123) (2)默认参数:带有默认值的参数,可看作是单一的字典参数,必须放位置参数之后!如: def func(para1, para2=None): pass 用法:func(123, para2='newval') 定义默认参数要牢记一点:默认参数尽量指向不变的对象! 备注:1.任何参数入参时都可以用"参数名=值"的方式指定入参。 2.使用" * "可整体传入所有位置和默认参数,如def func(arg1,arg2,arg3) data=(1,2,3) func(*data)。 29、可变参数(类似于重载):"*paras" 表示把paras这个"list"或"tuple"的多个元素作为可变参数传进去,定义函数时往往需配用for..in..,如: 不使用可变参数时可用元组或列表传入: def calc(arg) 用法:calc((arg1, arg2, arg3, )) 使用可变参数时则可直接输入多个参数: def calc(*args) 用法:calc(arg1, arg2, arg3) 注意:可变参数之后的参数进行入参时,必须写"参数名=值"的形式表达。可变参数,必须放在所有的位置参数和默认参数后面! 30、关键字参数(指定关键字的参数,类似于字典作参数):"**kwargs"表示把kw这个dict的所有key-value用关键字形式传入到函数的参数中。 例:def func(**kwargs): print(kwargs.keys(), kwargs.values()) 用法(调用时必须带上**): func(**{'city': 'Beijing', 'job': 'Engineer'}) 或者一个一个地传入键值: func(city='Beijing', job='Engineer') 注意:同可变参数类似,关键字参数必须放在所有的位置参数和默认参数后面! “万能参数”:当"*args"和"**kwargs"组合起来使用(有先后顺序),理论上能接受任何形式和任意数量的参数。 31、命名的关键字参数:限制关键字参数的名字,调用时带上参数名。需要一个特殊分隔符"*","*"后面的参数被视为命名关键字参数。 例如,只接收city和job作为关键字的参数,如: def person(name, age, *, city, job) ==> 调用时,必须带上"city"和"job"参数名!如:person('nina', 21, city='shenzhen', job='管理')。 函数调用时不要带上*。 32、1)大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以任何递归函数都存在栈溢出的问题! 递归核心思想:每一次递归,整体问题都要比原来减小,并且递归到一定层次时,能直接给出结果! 2)流程控制(条件不用"()"括起来) 1、if condition: ... elif condition: ... else: # 否则。else可选 ... if语句的简单形式:content if condition else val 2、while condition: ... else: # 循环结束后执行else语句。else可选 ... 3、for i in iter: # 遍历 ... else: # 遍历结束后执行else语句。else可选 ... 4、备注:在Python中没有"switch – case"语句。 33、切片"[]":mylist['开始索引':'结束索引':'±步长'],"+"正向切片,"-"逆向切片。 注意:不包含结束索引的位置,如果要包含"结束索引"的值,请将"结束索引"加1。 tuple也可以用切片操作,只是操作的结果仍是tuple; 字符串也可以看成是一种list,也可以用切片操作,只是操作结果仍是字符串。 字符串翻转:str(mystr)[::-1] 34、默认情况下,"dict"迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()。 35、判断一个对象是否为可迭代类型: from collections import Iterable print(isinstance('12345', Iterable)) 36、函数enumerate(iter):给可迭代对象的每个元素加上序号,变成"序号-元素"对(tuple)。 37、推导式。 1)列表推导式 ['生成表达式' for i in range(1, 11) if i % 2 == 0],使用[]转成列表,生成的结果元素放前面(表达式),后面跟for循环。可以for嵌套、if条件。 将循环结果进行了表达式运算并保存结果到列表中,直接打印出print(mylist)。 2)集合推导式。 {'生成表达式' for i in range(1, 11) if i % 2 == 0},使用{}转成集合。 3)字典推导式。 {'键生成表达式':'值生成表达式' for k,v in iterable if condition},使用{}转成字典,必须含有键值。 38、生成器(generator):只产生数据,不接收数据。创建一个生成器后,可以用next()输出,也可以通过"for"循环来迭代输出,用for不需关心StopIteration的错误。 第一种(类似于迭代),只要把一个列表生成式的[]改成()。不必一次性创建整个list,而是迭代生成,从而节省大量的空间。如: gen = (i * i for i in range(1, 11) if i % 2 == 0) 第二种(类似于中断(循环)),利用关键字——"yield"就变成了生成器。yield只用于函数中,遇到yield就中断返回,再次执行时从上次返回的yield语句处继续执行。 next(),返回下一个yield参数值; send(msg),将msg作为当前的yield表达式的返回值,并返回下一个yield参数值。 #斐波拉契数列# def fib(max): n = 0 a, b = 0, 1 # 初始值 while n < max: # print(b) yield b # yield,生成器 a, b = b, a+b # 规律表达式 n += 1 for v in fib(6): # 一般使用for循环输出 print(v) #杨辉三角# def yanghui(lines): L = [1] # 初始化 n = 0 while n<= lines: yield L # yield,生成器 L = [1] + [L[x-1] + L[x] for x in range(1, len(L))] + [1] # 规律表达式 n += 1 for v in yanghui(10): print(*v) # "*"只输出列表元素 39、可迭代(iterable):实现了__iter__()方法,凡是可作用于"for"循环的对象都是可迭代类型,可迭代的类型有:list、tuple、dict、set、str等。 迭代器(Iterator):实现__iter__()方法和next()方法,凡是可作用于"next()"函数的对象都是迭代器,它们表示一个惰性计算的序列。 可通过 iter() 函数获得一个迭代器(Iterator)对象。 __iter__():该方法返回一个迭代对象,return self ; next():该方法拿到循环的下一个值。 迭代器/生成器区别:迭代器是一个对象(类),而生成器是一个函数。 40、函数式编程。 可简单理解成高阶函数。函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量! Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言。 函数式编程的一个特点就是:允许把函数本身作为参数传入另一个函数,也允许返回一个函数! 41、在Python中常见的函数式编程: 高阶函数、闭包、装饰器、偏函数、匿名函数。 42、高阶函数:把函数作为参数传入(函数的函数),属于"函数式编程"范畴。 函数式编程的主要三个关联概念:映射、过滤、统计处理,它们的共同特点是以函数和Iterable为入参。其他函数式有:find, all, any。 any():只要对象非空,或对象中某一个元素是"真元素"(非空、非0、True值),即返回True; all():对象中所有元素是"真元素"时,返回True,但是对象为空时(all([])、all({})、all('')),也返回True。 映射函数map(函数f, Iter):即映射,函数f依次作用到Iter的每个元素,并把结果作为新的Iterator返回。结果是个迭代器(Iterator)。 过滤函数filter(函数f, Iter):过滤,函数f依次作用到Iter的每个元素上,若函数f返回True则留下该Iter的元素,若返回False则滤除掉该Iter的元素。 累计函数reduce(函数f, Iter):函数f(arg1, arg2),该函数f返回的结果又作为arg1,这样的函数f作用在Iter元素上进行累计计算。如, from functools import reduce reduce(lambda x,y: x*y, [1,2,3,4,5]) # return 120,或利用操作符模块:reduce(operator.add, [1,2,3,4,5]),首先import operator。 函数sorted(Iter, <key=函数>, <reverse=False>):排序。可以通过函数完成自定义排序,该函数作用在Iter元素上。如: 对字典按键名进行排序: d = {'d':9,'b':2,'a':10,'c':3} sorted(d.items(), key=lambda x:x[0], reverse=False) # x[0]自动迭代,按键名排序 43、几个经典排序方法:(经测试以下3种排序,插入排序效率最快,冒泡排序效率最慢。实际上,列表方法sort()速度是最最快的,其次是内置函数sorted()) "冒泡排序":两两比较相邻的元素,如果第一个比第二个大,就交换它们,以此类推。 def bubble_sort(arr): length = len(arr) for i in range(0, length-1): # 第1次循环次数 for j in range(0, length-1-i): # 第2次循环次数 if arr[j+1] < arr[j]: # 第二个和第一个元素开始两两对比 arr[j+1], arr[j] = arr[j], arr[j+1] # 两者交换 return arr "选择排序":首先在未排序序列中选择出最小(大)元素,放到序列的起始位置,然后再从剩余未排序元素中继续选择出最小(大)元素,放到已排序序列的末尾,以此类推。 def select_sort(arr): length = len(arr) for i in range(0, length): # 第1次循环次数 min_index = i # 首先,初始化最小元素下标 for j in range(i, length): # 第2次循环次数 if arr[j] < arr[min_index]: # 选择出最小元素的下标 min_index = j arr[min_index], arr[i] = arr[i], arr[min_index] # 两者交换 return arr "插入排序":把一个未排序的元素,跟已排序的序列"由后向前"逐一扫描比较,找到相应位置并插入,以此类推。 def insert_sort(arr): length = len(arr) for i in range(1, length): # 第1次循环次数。第一个元素看成已排序,所以从第二个元素开始循环出未排序 j = i # 未排序元素的下标j while j > 0: # 第二次循环用while比较合适 if arr[j] < arr[j - 1]: # 跟已排序的元素"由后向前"逐一比较 arr[j - 1], arr[j] = arr[j], arr[j - 1] # 两者交换 j -= 1 return arr 44、"二分法":判断元素是否存在。 def bin_search(arr, find): mid = len(arr) // 2 # //取整。arr和mid都是动态缩小的。 if len(arr) >= 1: if find < arr[mid]: bin_search(arr[:mid], find) # 使用了递归思想。递归核心思想:每一次递归,整体问题都要比原来减小,并且递归到一定层次时,能直接给出结果! elif find > arr[mid]: bin_search(arr[mid + 1:], find) else: print(1) # 找到。 注意:不能用return else: print(-1) # 未找到。 注意:不能用return 45、一个函数可以返回一个计算结果,也可以返回一个函数。 返回一个函数时,牢记该函数并未执行,返回函数中不要引用任何可能会变化的变量。 46、匿名函数(λ):当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入没有名称的函数更方便。 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。 形如,lambda '参数': '返回值' 若定义一个变量接收lam = lambda x: x**2,则可显式调用lam(3)=9。 sorted(L, key=lambda x: abs(x)),隐式调用,会自动将列表L每个元素传入x。 47、偏函数:当函数的参数个数太多,需要简化时,使用"functools.partial"可以改变并固定住原函数的部分参数,从而在调用时更简单。如: int2 = functools.partial(int, base=2) # 修改默认参数值并固定住。int函数原先是十进制内容的字符,现在为二进制内容的字符 48、模块:一个.py文件就称之为一个模块。最大的好处是大大提高了代码的可维护性。 导入模块:" from 包名 import 模块名 "。 49、if __name__=='__main__': test() 这段代码表示:模拟程序入口,当且仅当直接执行本模块时,才会执行main代码块,在其它模块中导入此模块时,则不会执行这个mian代码块。(即仅在本模块下执行) 50、面向对象编程:Object Oriented Programming,简称OOP,是一种程序设计方法。OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的方法。 具有封装、继承、多态三大特点。Python就是一种面向对象的语言,支持面向对象编程,在其内部,一切都被视作对象。 类是抽象的模板,实例是根据类创建出来的一个个具体的"对象",每个对象都拥有相同的方法,但各自的数据可能不同。 class Student(object): attrcls = value # 方法之外的属性,称作类属性(全局变量/静态变量)。类属性是所有实例公有的属性,每一个实例都可以访问、修改。 def __init__(self, attrobj): # 重写object中__init__()方法初始化,可用super().__init__()调用父类的__init__()初始化。 self.attrobj = attrobj # 实例属性写在这里!实例本身拥有的属性(局部变量/动态变量)。 def demo(self, arg): # 实例方法 var = 0 # 局部变量 return var class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是继承类(默认为object,在builtins.py模块中第720行~820行),表示该类是从哪个类继承下来的。 "self"一般用在实例方法中,表示只有实例对象能访问,而类对象不能访问。作用:调用自身(实例)的属性或方法。 实例对象:可以访问实例属性/方法和类属性/方法; 类对象:只能访问类属性/方法。 实例对象:stu = Student([args]),自动进行了两个步骤,首先调用__new__()方法创建实例对象,然后调用__init__()初始化。 Python要么是"public"要么就是"private",一般要避免下划线的使用: 1.以单下划线开头(_foo):建议性的私有成员,建议不要在外部访问; 2.以双下划线开头的(__foo):强制的私有成员,只能在类自己内部中使用;(但是还是可以强制通过"obj._类名__属性名"成功访问,所以Python的私有成员是"假"的)。 3.以双下划线开头和结尾的(__foo__):魔法方法,即特殊方法专用的标识,通常情况下是自动触发调用的,如__init__()构造函数在初始化时自动调用。 特殊方法特殊使用,这类特殊方法可以重定义,但不是同名调用,而是使用操作符或调用去掉双"__"的方法。 一些魔法方法(或属性): __doc__:说明性文档和信息。 __class__:表示当前操作的对象属于哪个类。 __dict__:列出类或对象中的所有成员。 __slots__:限制实例的变量(对继承它的子类不起作用)。 __new__():创建实例时自动调用,实例化对象调用的第一个方法,它只取下 cls 参数,并把其他参数传给 __init__。 __init__():初始化时自动调用。 __del__():析构方法,当对象在内存中被释放时,自动触发此方法。 __getitem__()、__setitem__()、__delitem__():按索引方式(方括号)取值、赋值、删除数据。 __iter__():迭代器方法。 __str__()与__repr__():返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的,通常两者代码一样即可。 __new__实现单例模式(无论多少次实例化,结果都是同一个实例,共享同一个资源!): 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。 当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场。 比如,某个服务器程序的配置信息存放在一个文件中,客户端通过一个 AppConfig 的类来读取配置文件的信息。 如果在程序运行期间, 有很多地方都需要使用配置文件的内容,也就是说,很多地方都需要创建 AppConfig 对象的实例,这就导致系统中存在多个 AppConfig 的实例对象, 而这样会严重浪费内存资源,尤其是在配置文件内容很多的情况下。事实上,类似 AppConfig 这样的类,我们希望在程序运行期间只存在一个实例对象。 应用场景: 1)Python的logger就是一个单例模式,用以日志记录 2)线程池、数据库连接池等资源池一般也用单例模式 3)Windows的资源管理器是一个单例模式 4)网站计数器 单例模式举例: class Person(object): def __init__(self, name, age): self.name = name self.age = age def __new__(cls, *args, **kwargs): if not hasattr(cls,'instance'): # 判断是否存在类属性instance cls.instance = super().__new__(cls) return cls.instance # 返回类实例 a = Person('p1',21) b = Person('p2',22) print(a == b, a.name == b.name) # 这里的打印结果都是True,可见a和b都是同一个实例(实例b覆盖了实例a)。 51、(1)封装:将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现。 (2)继承:子类在调用某个方法或属性时,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。 根据父类定义中的顺序,以深度优先的方式逐一查找父类!(当一条路走到黑也没找到的时候,才换另一条路)。 但是,多条路的公共基类会最后去查找。 (3)多态:一个类实例可以是该类的类型,也可以是父类的类型,但不可以是其子类的类型。 52、对于静态语言(类型不可变,例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用其中的方法。 对于Python这样的动态语言("鸭子语言":类型可变,如果某个对象调用某个方法,就不必管该对象属于哪个类,只要存在需要调用的方法即可)来说, 则不一定需要传入Animal类型,我们只需要保证传入的对象有其方法就可以了。 53、方法type(数据参数):返回数据类型。 isinstance 和 type 的区别在于: isinstance() 会认为子类是一种父类类型,考虑深度。 type() 不会认为子类是一种父类类型,不考虑深度。 54、函数与方法区别: 方法(类的)调用方式: 对象.方法名() 函数调用方式: 函数名(对象) 方法的第一个参数是自调参数(self),函数没有自调参数。 55、函数getattr(对象/类,属性):获取对象/类的属性; 函数setattr(对象/类,属性):设置对象/类的属性; 函数hasattr(对象/类,属性):判断对象/类的属性是否存在。 56、动态添加(绑定)属性: "对象名.新属性名" = "值" , 此方式添加的属性只对该实例对象有效; "类名.新属性名" = "值" , 此方式添加的属性对所有实例对象有效; 动态缺点:无法做属性检查,而自定义的"getter()"和"setter()"可以做参数的检查,如判断等。 57、动态添加(绑定)方法: "对象名.新方法名" = "已定义函数名" , 此方式添加的方法只对该实例对象有效; "类名.新方法名" = "已定义函数名" , 此方式添加的方法对所有实例对象有效; 58、变量__slots__=('属性1','属性2',...):限制类属性,对象只能访问和修改属性,不能添加和移除属性。但对继承的子类是不起作用的。 如果没有__slots__,类属性存在于__dict__私有字典中。 hasattr(object, name): 判断一个对象里面是否有name属性或者name方法,返回BOOL值,有name特性返回True, 否则返回False。 getattr(object, name[,default]): 获取对象object的属性或者方法,如果存在则输出,如果不存在则输出默认值,默认值可选。 setattr(object, name, values): 给对象的属性赋值,若属性不存在,先创建再赋值。 59、@property属性装饰器:把类的方法伪装成属性调用的方式,也就是本来是Foo.func()的调用方法,变成Foo.func的方式。 一般用于私有属性,把取值、赋值(getter()、setter())方法变成属性,则可以通过"obj.__attr"取值,通过"obj.__attr=val"赋值(最好使用私有属性)。 @property 属性装饰器广泛应用在类的定义中,可以控制属性访问,保证对参数进行必要的检查,如判断等,这样,程序运行时就减少了出错的可能性。 例: class Money: def __init__(self, dollars, cents): self.__dollars = dollars self.__cents = cents self.__total_cents = dollars * 100 + cents #获取属性,直接使用@property,取值-->obj.cents,相当于obj.getter() @property def cents(self): return self.__total_cents % 100; #设置属性,使用@方法名.setter,赋值-->obj.cents=val,相当于obj.setter() @cents.setter def cents(self, new_cents): self.__total_cents = 100 * self.dollars + new_cents #删除属性 @cents.deleter def cents(self): return #实例 m=Money(1,1) m.cents #取值 m.cents=2 #赋值 @classmethod 类方法装饰器,这样,类对象(即类名)就可以直接调用该方法(classname.classmethod)。 1)@staticmethod:静态方法装饰器,其跟实例方法的区别是没有"self"参数,并且可以在类不进行实例化的情况下调用; 2)@classmethod :类方法装饰器,与实例方法的区别在于所接收的第一个参数不是"self"(类实例的指针),而是"cls"(当前类的具体类型); 3)@property :属性装饰器,表示可以通过通过类实例直接访问信息。 60、Mixin:在设计类的继承关系时,通常,主线都是单一继承下来的,如果需要"组合"额外的功能,通过多重继承就可以实现,这种设计通常称之为Mixin(混合)。 Mixin类一般以Mixin为后缀,使用Mixin类实现多重继承需要注意: 1)首先,它必须表示某一种辅助功能,而不是某个事物; 2)其次,它必须功能单一,如果有多个功能,那就写多个Mixin类; 3)再者,它不依赖于子类的实现; 4)最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能(比如飞机照样可以载客,就是不能飞了^_^)。 61、1)动态创建类:通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。 myclass=type('MyClass',(baseclass,),{'func':exfun}) 说明:'MyClass':类名; (baseclass,):继承类,用tuple; {'func':exfun}:类方法,dict型,其中exfun是外部定义好的方法。 2)元类(metaclass):元类创建类(用来指定类是由谁创建的),而类创建实例。(很少情况使用。抽象基类、ORM中有应用) 定义:class Name_metaclass(type): # 必须传入type def __new__(cls, name, bases, attrs): # cls当前准备创建的类;name类名,即"Name_metaclass"; pass # bases父类集合;attrs类的方法集合。 return type.__new__(cls, name, bases, attrs) 62、捕获异常 try: pass except Exception as e: # 捕获异常,尽量指定精确的异常类(类名首字母大写) pass # 处理异常 raise # 主动抛出一个异常,让上层调用者处理。程序不会往下执行。如果没有抛出,程序会继续往下执行。 else: # 可选,没有异常时执行 pass finally: # 可选,总是被执行 pass 当认为某些代码可能会出错时,就可以用try来运行这段代码,如果执行出错,则后续代码不会继续执行,而是直接跳转至错误处理代码, 即except语句块,执行完except后,如果有finally语句块(非必需),则再执行finally语句块,至此,执行完毕。 所有的错误类型都继承自BaseException,所以在使用except时需要注意的是,它不但捕获该类型的错误,还把其子类也"一网打尽"。 错误和异常: 错误,机器出现的错误,程序无法处理或不需要处理。 异常,程序运行而能自动识别的异常,一般要捕获处理(立即处理或上抛给调用者)。有系统异常与逻辑异常。 63、如果要打印错误追踪,导入logging,如: import logging ... except Exception as e: logging.exception(e) # 不需要print(),直接输出错误日志追踪。 64、抛出异常 a、raise Exception('str') b、raise # raise语句如果不带参数,就会把当前错误原样抛出给调用者。 65、调试。 (1)使用断言"assert"语句。面向开发者设计,用于程序的调试。 凡是用print()来辅助查看的地方,都可以用断言(assert)来替代。 语法:assert '条件(Bool)', '提示(为假时输出的内容)' 其中,若"条件"为真,程序继续执行;若"条件"为假,程序终止,并给出"提示"。 (2)使用"logging"日志模块,服务端写入日志,还可以通过http get/post,socket,email写入日志。 例: logging.basicConfig( level=logging.DEBUG, format='%(asctime)s %(filename)s [line:%(lineno)d] %(levelname)s %(message)s', filename='/test.log', filemode='a+', # a+ 追加写入 datefmt='%a, %d %b %Y %H:%M:%S', # 一般默认就好(形如2018-07-08 16:49:45,896) ) logging.debug('debug message') # 传入要记录的日志消息内容 logging.info('info message') logging.warning('warning message') logging.error('error message') logging.critical('critical message') 参数说明: level:设置日志最低级别,默认是WARNING,打印出此级别及以上的信息; format:指定日志显示格式; filename:日志存储指定的文件,默认是控制台输出; filemode:文件打开方式; datefmt:指定日期时间格式; 其中format参数值说明: %(message)s:日志消息 %(levelname)s:日志级别 %(asctime)s:当前时间,默认格式是 "2018-07-08 16:49:45,896",逗号后面的是毫秒 %(pathname)s:完整路径名,可能没有 %(filename)s:文件名 %(module)s:模块名 %(funcName)s:函数名 %(lineno)d:程序所在的代码行 %(created)f:当前时间,用UNIX标准的表示时间的浮点数表示 %(relativeCreated)d:输出日志信息时的,自Logger创建以 来的毫秒数 %(thread)d:线程ID。可能没有 %(threadName)s:线程名。可能没有 %(process)d:进程ID。可能没有 %(name)±10s:Logger的名字(+10s宽度为10的字符串,右对齐; -左对齐) %(levelno)s:数字形式的日志级别 (3)单元测试(标准库 unittest) 测试驱动开发(TDD) 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。 编写一个测试类,继承"TestCase",其中测试方法必须以"test"开头,否则不被认为是测试方法,测试的时候不会被执行。 对每一类测试都需要编写一个"test_xxx()"方法,由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。 可以在单元测试中编写两个特殊的"setUp()"(初始化、连接库等)和"tearDown()"(清除对象、关闭连接等)方法,这两个方法会分别在每调用一个测试方法的前后分别被执行。 最常用的断言:self.assertEqual(first, second, msg),与期望值对比是否相等。另一种重要的断言:self.assertRaises(exception),期待抛出指定类型的异常。 最后运行单元测试:if __name__ == '__main__': unittest.main() 第三方库 pytest: 安装:pip install pytest (管理员cmd,"pip"与"easy_install"是Python的安装工具) 测试样例可以写在类中,也可以直接写模块中。 与unittest类似,提供setup和teardown函数。但是断言只用原始的assert即可。 运行单元测试:在CMD中执行单元测试文件,命令 "pytest -q testname.py" (-q会清除版本信息,testname为文件名) 生成log结果文件:"pytest testname.py --resultlog=filename.txt" 例:TestAdd.py def setup(): print('start...') def teardown(): print('end...') def test_add(): assert MyAdd.add((1, 2, 0)) == 6, '计算有误!' cmd执行:pytest -q TestAdd.py (4)性能测试 cProfile,用C语言实现。 # 测试内容为test函数,测试结果输出到filename,按cumtime列排序。如果没指定filename,则输出到控制台。 # filename保存的是二进制文件 cProfile.run("test()", filename="filename", sort="cumtime") 测试结果是二进制文件,Python提供了一个"pstats"模块,用来分析cProfile输出的文件内容: p = pstats.Stats("filename") p.strip_dirs().sort_stats('').print_stats() # strip_dirs(): 去掉无关的路径信息 # sort_stats(): 排序,支持的方式和上述的一致 # print_stats(): 打印分析结果,可以指定打印前几行;参数为小数,表示前百分之几的函数信息 66、闭包:如果外部函数里有内部函数,内部函数对外部函数作用域的变量进行引用,那么内部函数就被认为是闭包(closure)。 "装饰器就是一种闭包!" 调用外部函数传递的参数就是自由变量。 简单说,闭包就是根据不同的配置信息得到不同的结果。 用途1:当闭包执行完后,仍然能够保持住当前的运行环境。(类似静态变量) 用途2:闭包可以根据外部作用域的局部变量来得到不同的结果。(这有点类似配置功能(注册),我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。) 如: def exfunc(x): def clofunc(y): return x + y return clofunc 调用:f=exfunc(6) # x=6,返回了内部函数clofunc赋给f,即此时调用内部函数,"由外而内"。 print(f(8)) # y=8,结果14 67、装饰器(decorator):使用"@"调用装饰器。在不更改原函数的情况下,该函数被调用前,扩展该函数功能。 故装饰器以函数入参,并返回内部函数。例: 1)函数装饰器 def deco(func): # 函数装饰器,故以函数为参数 # @functools.wraps(func) # 可以加上此语句,确保wrapper()函数的名称、docstring与原函数相同 def __wrap(*args2, **kwargs2): # 内部函数,"*args2,**kw2",表示可接收任何参数 # 执行函数前 print('调用了%s()' % func.__name__) return func(*args2, **kwargs2) # 返回入参函数 return __wrap # 返回内部函数,闭包。 使用: @deco #等价语句:now = deco(now) 。通常装饰器没参数,如果装饰器带参,则在装饰器定义时再外套一层函数名并返回内嵌函数。 def now(): print('2017-5-4') 2)类方法装饰器 使用: class A(): @method_decorator(decorator_name) # 参数decorator_name指装饰器的调用 def func(self): pass 68、IO:涉及到数据交换的地方,通常是磁盘、网络等,就需要IO接口。 从磁盘【读】文件到内存,就只有【Input】操作,反过来,把数据【写】到磁盘文件里,就只是一个【Output】操作。 现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符)。 69、Python中,所有具有read和write方法的对象,都可以归类为file类型。Python内置了一个open()方法,用于对文件进行读写操作。 open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None): 参数errors='ignore',如果遇到编码错误后忽略处理。 ----------------------------------------------------------------------------------------------------- r | 只读 | 默认模式,如果文件不存在就报错,存在就正常读取 ----------------------------------------------------------------------------------------------------- w | 只写 | 如果文件不存在,新建文件然后写入;如果存在,先清空文件内容,再写入 ----------------------------------------------------------------------------------------------------- a | 追加 | 如果文件不存在,新建文件然后写入;如果存在,在文件的最后追加写入 ----------------------------------------------------------------------------------------------------- x | 新建 | 如果文件存在则报错,如果不存在就新建文件,然后写入内容,比w模式更安全 ----------------------------------------------------------------------------------------------------- b | 二进制模式 | 比如rb、wb、ab,以bytes类型操作数据 ----------------------------------------------------------------------------------------------------- + | 读写模式 | 比如r+、w+、a+ ----------------------------------------------------------------------------------------------------- 对于"w+"模式,在读写之前都会清空文件的内容,建议不要使用! 对于"a+"模式,永远只能在文件的末尾写入,有局限性,建议不要使用! 对于"r+"模式,也就是读写模式,配合seek()和tell()方法,可以实现更多操作。 读文件: f.read(字符数):读取一定大小的数据, 然后作为字符串或字节对象返回。 f.readline(字符数):读取一行内容。 f.readlines():读取所有行,且一行一行放入列表中。 f.tell():获取文件指针(当前所在的位置,字节数)。 f.seek(offset, whence=1):移动文件指针(字节数)。offset 偏移量,whence=0 从文件开头计算,whence=1 从当前位置计算,whence=2 从文件末尾计算。 f.close():关闭文件对象。 无论读写,都先打开(open)文件的路径,即入口。 例:try: f=open('D:PersonalDesktopPyNote.py', 'r') # 先打开文件 print(f.read()) # 一次性全部读取 except IOError as e: print('读入文件出错', e, sep='||') finally: f.close() # 无论如何需要关闭流 *推荐使用:另一种简洁读文件方法(不必调用close()方法),使用" with...as... "上下文管理器,操作文件IO是个好习惯: with open('D:PersonalDesktopPyNote.py', 'r') as file: print(file.read()) 70、写文件: 当我们写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲的时候再慢慢写入。 只有调用close()方法时,操作系统才保证把没有写入的数据全部写入磁盘。 忘记调用close()的后果是数据可能只写了一部分到磁盘,剩下的丢失了。所以,还是用with语句来得保险。 写文件: f.write(str):传入一个字符串写入。 f.writelines(iter):传入一个可迭代序列写入。 例: *推荐使用: with open('D:PersonalDesktopPyNote1.txt', 'w', encoding='utf8') as file: file.write('写文件!') # 返回字符长度 71、字符流(StringIO)。StringIO和BytesIO是在内存中操作str和bytes的方法,使得和读写文件具有一致的接口。流必须先读出来再写入文件中。 from io import StringIO # StringIO和BytesIO都属于io库 例1:使用write()写入流中。 f=StringIO() # 创建一个StringIO实例 f.write('字符流IO') print(f.getvalue()) # 用getvalue()方法获取流。 # write()结束后指针在末尾,使用方法tell()能知道指针位置,所以read()方法读取不到。 # 除非用方法seek(i)将指针定位开始位置,则可以用read()方法读取。 例2:初始化流,用read()或getvalue()方法读取流。 f = StringIO('Hello! Hi! Goodbye!') # 初始化流,指针在开头 while True: s = f.readline() # 可用read()或getvalue()方法读取流 if s == '': break print(s.strip()) # strip()去除首尾空格 72、字节流(BytesIO)。类似于字符流。 from io import BytesIO 例1:使用write()写入流中。 b=BytesIO() # 创建一个BytesIO实例 b.write('字节流'.encode()) print(b.getvalue()) # 用getvalue()方法获取流。 # write()结束后指针在末尾,使用方法tell()能知道指针位置,所以read()方法读取不到。 # 除非用方法seek(i)将指针定位开始位置,则可以用read()方法读取。 例2:初始化流,用read()或getvalue()方法读取流。 b=BytesIO('字节流'.encode()) # 初始化流,指针在开头 print(b.read()) # 可用read()或getvalue()方法读取流 73、目录与路径。 变量"__file__":返回到当前文件的完整路径。 备注: os :负责程序与操作系统的交互,提供了访问操作系统底层的接口; sys:负责程序与python解释器的交互,提供了一系列的函数和变量,用于操控python的运行时环境; platform:提供操作平台信息的模块,如是Windows平台还是Linux平台。 #********************** os内置模块 **********************# os.name 查看当前操作系统的名称。windows平台下返回"nt",Linux则返回"posix" os.getcwd() 取当前工作目录名 os.chdir() 切换工作目录,相当于shell下的cd os.chmod(path, mode) 改变目录权限。mode为stat模块里的常量 os.rename('oldname', 'newname') 重命名文件 os.walk() 生成目录树下的所有文件名 os.mkdir/makedirs('dirname') 创建目录/多层目录 os.rmdir/removedirs('dirname') 删除目录/多层目录 os.remove('path/filename') 删除文件 os.listdir('dirname') 列出指定目录的文件 os.path.abspath('path/filename') 返回完整的绝对路径。__file__得到到当前文件的完整路径 os.path.basename('path/filename') 去掉目录,返回文件名 os.path.dirname('path/filename') 去掉文件名,返回目录 os.path.join(path1[,path2[,...]]) 将分离的各部分组拼接成一个路径名 os.path.split('path/filename') 返回(dirname, basenam)元组 os.path.splitext('path/filename') 返回(filename, '.扩展名')元组 os.path.getatime/ctime/mtime() 分别返回最近访问、创建、修改时间(时间戳) os.path.getsize('path/filename') 返回文件大小(字符数) os.path.exists('path/filename') 是否存在 os.path.isabs('path/filename') 是否为绝对路径 os.path.isdir('path/filename') 是否为目录 os.path.isfile('path/filename') 是否为文件 os.getpid() 获取操作系统的进程ID #********************** sys内置模块 **********************# sys.argv 命令行参数List,第一个元素是程序本身路径 sys.hexversion 获取Python解释程序的版本值,16进制格式如:0x020403F0 sys.version 获取Python解释程序的版本信息 sys.maxsize 最大的Int值 sys.maxunicode 最大的Unicode值 sys.modules 返回系统导入的模块字段,key是模块名,value是模块 sys.path 返回模块的搜索路径,初始化时使用'PYTHONPATH'环境变量的值 sys.platform 返回操作系统平台名称 sys.exec_prefix 返回平台独立的python文件安装的位置 sys.byteorder 本地字节规则的指示器,big-endian平台的值是'big',little-endian平台的值是'little' sys.copyright 记录python版权相关的东西 sys.api_version 解释器的C的API版本 sys.stdin 标准输入 sys.stdout 标准输出 sys.stderr 错误输出 sys.modules.keys() 返回所有已经导入的模块列表 sys.exc_info() 获取当前正在处理的异常类,exc_type、exc_value、exc_traceback当前处理的异常详细信息 sys.exit(n) 退出程序,正常退出时exit(0) sys.exc_clear() 用来清除当前线程所出现的当前的或最近的错误信息 sys.stdin input()调用了sys.stdin.read() sys.stdout write()调用了sys.stdout.write() sys.stderr 解压与压缩(import gzip): 解压(读): f = gzip.open('file.txt.gz', 'rb') # 打开解压路径 file_content = f.read() # 直接解压,read() f.close() 压缩(写): air=open('Air.xml') fh=StringIO(air.read()) # 字符流缓存 airzip=gzip.open('AirZip.xml.gz','wb') # 打开压缩路径 airzip.write(fh.getvalue().encode()) # 直接压缩,write() air.close() airzip.close() 74、序列化:序列化的结果是字符串,序列化的目的是方便数据传输和存储。 (1)(a)pickle序列化:把对象转成字节(持久化二进制字符串)。经常遇到在Python程序运行中得到了一些字符串、列表、字典等数据, 想要长久的保存下来,方便以后使用,而不是简单的放入内存中关机断电就丢失数据。它可以将对象转换为一种可以传输或存储的格式。如: import pickle 1. pickle.dumps(obj):将Python数据序列化为bytes字符串,带"s"即str。 2. pickle.dump(obj, file):将Python数据序列化到文件内。 file = open('D:PersonalDesktopmypy.txt', 'wb') # 'wb',以字节写入 pickle.dump(obj, file, protocol=0) # 不带字母"s"的方法与文件相关。protocol=0 为ASCII,1是旧式二进制,2是新式二进制协议 file.close() 3. pickle.loads(bytes_object):反序列化,将pickle格式的bytes字符串转换为python类型。 4. pickle.load(file):反序列化,从pickle格式的文件中读取数据并转换为python类型。 file = open('D:PersonalDesktopmyp1y.txt', 'rb') # 'rb',以字节读取 pickle.load(file) # 反序列,load,将字节转成Python对象 file.close() pickle序列化的缺点:1.只能用于Python,并且可能不同版本的Python彼此都不兼容。2.不安全。 (b)shelve:字典式持久化。它依赖于pickle模块,但比pickle用起来简单。它的用法和字典类似,支持所有字典类型的操作。 shelve.open(filename, flag='c', protocol=None, writeback=False):创建或打开一个shelve对象。 参数:writeback=True 优点是,可以动态修改数据,并减少出错的概率。缺点是,在open()的时候会增加额外的内存消耗。 (2)struct结构体:类似C语言的结构体,包含不同类型的数据。将特定的结构体类型打包成字节型数据(二进制流)然后再网络传输, 而接收端也应该可以通过某种机制进行解包还原出原始的结构体数据。网络通信当中,大多传递的数据是以二进制流存在的。 import struct # 打包方法:pack(fmt,values) bin_data = struct.pack('>i3sf', 1, b'abc', 2.7) # 按格式打包成字节,">"字节顺序,"i"32位整型,"3s"长度为3的字符串,"f"浮点型。 # 字符串需要转成字节,且指定长度。 struct.unpack('>i3sf', bin_data) # 解析,解包还原成Python对象,返回元组 struct.calcsize(fmt):用于计算格式字符串所对应的结果的长度,如:struct.calcsize(‘ii’),返回8。因为两个int类型所占用的长度是8个字节。 或: strc = struct.Struct(fmt) # Struct类,初始化打包/解包格式fmt strc.pack(data) strc.unpack(bin_data) 75、json序列化(序列化的结果是字符串)。 JSON不仅是标准格式,并且比XML更快,而且可以直接在Web页面中读取,非常方便。JSON是一种轻量级的数据交换格式,跨语言,跨平台,便于接口数据交互。 1)json序列化: import json json.dumps(data, ensure_ascii=False, cls=None) # data可以是:除了集合和日期类型以外。序列化的结果是字符串! # ensure_ascii=False 保留中文,cls 自定义格式类 json模块不支持datetime序列化,需要自定义格式类: class ComplexEncoder(json.JSONEncoder): # 继承JSONEncoder def default(self, obj): # 重写default()方法 if isinstance(obj, datetime): return obj.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(obj, date): return obj.strftime('%Y-%m-%d') else: return super().default(self, obj) # 返回父类方法 print(json.dumps({'now': datetime.today()}, cls=ComplexEncoder)) 注意:字典json序列化的结果一定是双引号JSON字符串,并且不能有多余的逗号。 2)json反序列化(解析): json_str = '{"name":"wcw","addr":"bj"}' # 注意是字符串 inv_json = json.loads(json_str) # loads加载json数据转成Python对象 print(inv_json) # 结果是字典 3)类对象的json序列化: stu = Student('wcw', 20, 100) # 实例 json.dumps(stu.__dict__, indent=4) # 用内置属性__dict__ pickle也可以对类对象序列化。 76、多线程(threading)。 Python的标准库提供了两个模块:threading和_thread。_thread是低级模块,threading是高级模块,绝大多数情况下,只使用threading这个高级模块。 multiprocessing模块的完全模仿了threading模块的接口,二者在使用层面,有很大的相似性。 一些属性与方法: os.getpid():获取操作系统的进程ID。 threading.current_thread():返回当前线程。 getName()、setName():获取、设置线程/进程名称。 start():开启线程/进程。 terminate():强制终止线程/进程。 is_alive():线程/进程是否是激活状态。 setDaemon():把子线程/进程都设为主线程的守护线程/进程,当主线程结束后,守护子线程/进程也会随之结束。必须在start()之前。 join([timeout]):进入子线程,timeout是执行超时时间。 有两种方式来创建线程:一种是自定义线程类,即继承Thread类,并重写它的run()方法; 另一种是在实例化threading.Thread对象的时候,将线程要执行的任务函数作为参数传入线程。 1)自定义线程类: class Threads(threading.Thread): def __init__(self,name): super(Threads, self).__init__() self.name=name def run(self): # 重写run()方法 time.sleep(1) print('{0}启用线程:{1}'.format(self.name,threading.current_thread())) if __name__ == '__main__': t= Threads('ThrName') t.start() t.join() print('end...') 2)实例化Thread: # 写个任务函数 def dowaiting(): t1 = time.time() print('进入线程:', datetime.fromtimestamp(t1).time()) time.sleep(2) t2 = time.time() print('退出线程:', datetime.fromtimestamp(t2).time()) if __name__ == '__main__': thr = threading.Thread(target=dowaiting) # 实例化Thread,传入任务函数 thr.start() # time.sleep(1) # print('join开始') # thr.join() # print('join结束') 77、线程锁:当多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,这被称为“线程不安全”。 为了保证数据安全,Python设计了线程锁,即同一时刻只允许一个线程操作该数据。 (1)互斥/同步锁(Lock):互斥/同步锁是一种独占锁,同一时刻只有一个线程可以访问共享的数据。 使用很简单,初始化锁对象,然后将锁当做参数传递给任务函数,在任务中加锁,使用后释放锁。 例: num = 0 def plus(lock): # 给任务"加锁" global num # global声明此处的num是外面的全局变量num with lock: # 所有的线程锁都有加锁和释放锁的动作,类似文件的打开和关闭。所以可用"with"上下文管理器,替代acquire()和release()方法 for _ in range(10000): num += 1 print('子线程%s结束,num = %d' % (threading.current_thread().getName(), num)) if __name__ == '__main__': lock = threading.Lock() # 实例化同步锁 for i in range(2): # 开启2个线程 t = threading.Thread(target=plus, args=(lock,), name='-互斥锁线程-') # args参数列表传入的就是target任务函数所需的参数 t.start() (2)信号量锁(Semaphore):这种锁允许一定数量的线程同时执行,即多线程分批次执行,它不是互斥锁。 比如地铁安检,排队人很多,工作人员只允许一定数量的人进入安检区,其它的人继续排队。 例: def testsema(n, sema): # 给任务带上信号量锁 with sema: print('运行第%d个线程。' % n) time.sleep(1) if __name__ == '__main__': semas = threading.Semaphore(5) for i in range(1, 21): thr = threading.Thread(target=testsema, args=(i, semas)) thr.start() (3)事件锁(Event):全局定义了一个"Flag",如果Flag值为True,线程不再阻塞。如果Flag的值为False,那么当程序执行wait()方法时就会阻塞。 事件主要提供了四个方法set()、wait()、clear()和is_set(): set():将事件的Flag设置为True,不阻塞。 clear():将事件的Flag设置为False,阻塞。 wait():等待Flag值。 is_set():判断当前Flag值。 例: event = threading.Event() def lighter(): green_time = 3 # 绿灯时间 red_time = 3 # 红灯时间 event.set() # 初始设为绿灯 while True: print("33[32;0m 绿灯亮...33[0m") time.sleep(green_time) event.clear() print("33[31;0m 红灯亮...33[0m") time.sleep(red_time) event.set() def run(name): while True: if event.is_set(): # 判断当前是否"放行"状态 print("一辆[%s] 开过去了..." % name) time.sleep(1) else: print("一辆[%s] 看到红灯,立即停下了..." % name) event.wait() print("[%s] 看到绿灯亮了,立即走起..." % name) if __name__ == '__main__': light = threading.Thread(target=lighter,) light.start() for name in ['东风', '吉利', '奇瑞']: car = threading.Thread(target=run, args=(name,)) car.start() (4)线程通讯,同进程通信queue。(见80节) 78、全局解释器锁(GIL)。 在大多数环境中,单核CPU情况下,本质上某一时刻只能有一个线程被执行,多核CPU则可以支持多个线程同时执行。 但是在Python中,无论CPU有多少核,同时只能执行一个线程。这是由于GIL的存在导致的。 Python中想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行。 在Python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。建议在IO密集型任务(无须多核)中使用多线程,在计算密集型任务(可用多核)中使用多进程。 另外,深入研究Python的协程机制,会有惊喜的。 79、多进程(multiprocessing)。Python提供了非常好用的多进程包multiprocessing。 python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu_count()查看核数),则使用多进程。 需要再次强调的一点是:与线程不同,进程是独立的,没有任何共享状态,进程修改的数据,改动仅限于该进程内。 任何进程默认就会启动一个线程,我们把该线程称为主线程。 对于操作系统来说,一个任务就是一个进程(Process)。有些进程还不止同时干一件事,把进程内的这些"子任务"称为线程(Thread)。 线程是进程内的最小执行单元,而进程由至少一个线程组成。 !注意:在Windows中进程类Process必须放到 if __name__ == '__main__' 下,显然这只能用于调试和学习,不能用于实际环境。 (1)实例进程类Process if __name__=='__main__': # 必须在main中执行 p = Process(target='子进程代码块', name='自定义子进程名', arg=('子进程的参数',), kwargs={'子进程字典'}) # 实例一个子进程,target传入任务函数,arg是参数元组。 p.start() # 运行进程,自动调用run()方法 p.join([timeout]) # 进入子进程 p.terminate() # 强制终止进程,不会进行任何清理操作,不会释放锁,进而导致死锁,小心使用 p.is_alive() # 如果子进程仍然运行,返回Bool (2)自定义进程类 class ClsProcess(multiprocessing.Process): def __init__(self, intv): super(ClsProcess, self).__init__() self.intv = intv def run(self): # 重写run()方法 n = 3 while n > 0: print('现在时间是:{0}'.format(time.ctime())) time.sleep(self.intv) n -= 1 if __name__ == '__main__': p = ClsProcess(1) p.start() # 自动调用类中的run()方法。 p.join() 进程锁: 与threading类似,在multiprocessing里也有同名的锁类Lock,Semaphore和Event等,连用法都是一样一样的,这一点非常友好! 进程之间数据不共享,加锁的目的是为了保证多个进程修改同一块数据时,同一时间只能有一个修改,即串行的修改,没错,速度是慢了,牺牲了速度而保证了数据安全。 例:进程互斥锁Lock,与线程互斥锁用法相似: def work(filename, lock): # 模拟买票,给任务加锁 with lock: # 添加进程锁参数,由于进程锁也是上下文(__enter__为acquire(),__exit__为release()),所以可使用with处理 with open(filename, encoding='utf-8') as f: dic = json.loads(f.read()) print('剩余票数: %s' % dic['count']) if dic['count'] > 0: dic['count'] -= 1 time.sleep(random.randint(1, 3)) # 模拟网络延迟 with open(filename, 'w', encoding='utf-8') as f: f.write(json.dumps(dic)) print('%s 购票成功' % os.getpid()) else: print('%s 购票失败' % os.getpid()) if __name__ == '__main__': lock = multiprocessing.Lock() # 使用进程锁,传入"任务"的参数中 for i in range(5): p = Process(target=work, args=('db.json', lock)) # args参数列表传入的就是target任务函数所需的参数 p.start() p.join() 80、线程/进程通信:线程/进程彼此之间互相隔离,要实现线程/进程间通信,Python提供了队列:Queue(线程通信)和Pipe(进程通信)等多种方式来交换数据。 (1)Queue([maxsize]):单向队列。队列先进先出,底层以[管道]加锁的方式实现。 生产者-消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。 生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产数据之后不用等待消费者处理,直接扔给阻塞队列, 消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。 例: import queue ## 生产者,队列q入参 def produ(q, data): for item in data: time.sleep(random.randint(1, 2)) print('生产者生产了:%s' % item) q.put(item) # 数据放入队列中 ## 消费者,队列q入参 def consu(q): while True: # 循环取数据 time.sleep(random.randint(1, 2)) res = q.get() # 从队列中取数据。get_nowait():不需要一直等待,一旦队列空了,则抛出异常,此时可以捕获它 if res is None: break print('消费者拿到了:%s' % res) if __name__ == '__main__': que = queue.Queue() # 使用队列通信 # 生产者和消费者分别开启线程,然后通信 pro = threading.Thread(target=produ, args=(que, [1, 2, 3])) con = threading.Thread(target=consu, args=(que,)) pro.start() con.start() pro.join() print('主线程') que.empty():que为空则返回True,该结果不可靠,比如在返回True的过程中,如果队列中又加入了项目。 que.full() :que已满则返回True,该结果不可靠,比如在返回True的过程中,如果队列中的项目被取走。 (2)Pipe([duplex]):管道,双向队列。 在进程之间创建一条管道,并返回元组(conn1,conn2),其中conn1,conn2表示管道两端的连接对象,强调一点:必须在产生Process对象之前产生管道。 参数dumplex:默认管道是全双工的;若将duplex设成False,管道是单工,conn1只能用于发送,conn2只能用于接收。 例: from multiprocessing import Pipe ## 生产者 def produ(p, data): psend, precv = p # 既是生产者也是消费者 # precv.close() for d in data: psend.send(d) time.sleep(0.5) else: # 循环结束时 psend.close() ## 消费者 def consu(p, name): psend, precv = p # 既是生产者也是消费者 # psend.close() while True: try: chanpin = precv.recv() print('{0}获取产品:{1}'.format(name, chanpin)) except EOFError: precv.close() break if __name__ == '__main__': psend, precv = Pipe() # 实例化双向管道 # 生产者和消费者分别开启进程,然后通信 pro = Process(target=produ, args=((psend, precv), [1, 2, 3, 4, 5])) con = Process(target=consu, args=((psend, precv), '消费者')) pro.start() con.start() print(psend.fileno()) pro.join() con.join() psend.close() precv.close() print('=>主进程') 81、线程/进程池(Pool):因为在切换线程/进程的时候,需要切换上下文环境,线程很多的时候,依然会造成CPU的大量开销。为解决这个问题,线程池的概念被提出来了。 预先创建好一个数量较为优化的线程/进程组,在需要的时候立刻能够使用,就形成了线程/进程池。 如果池还没有满,那么就会创建一个新的线程/进程用来执行该请求;但如果池中的线程/进程数已经达到规定最大值,那么该请求就会等待。 Python没有内置的线程池模块,需要自己实现或使用第三方模块。但Python提供了进程池模块!(from multiprocessing.pool import Pool) apply():同步执行进程池(串行) apply_async():异步执行进程池(并行) terminate():立刻关闭进程池 close():先关闭进程池,再进入子进程。 join():进入子进程。注意:必须在close()或terminate()之后。 例: if __name__=='__main__': p = Pool(processes=8) # 允许最多同时放入8个进程,自由调配子程序。默认是本机的CPU核数。 for i in range(20): # 开启20个进程 res = p.apply_async(func=task, args=(x,), callback=None) # 异步运行进程池。func传入任务函数名,args是任务函数的参数,任务函数的返回结果作为callback参数 p.close() # 先关闭进程池,再进入子进程 p.join() # 进入子进程,如果没有则进入主进程 res.get() # 得到子程序返回结果 res.ready() # 子程序是否调用完成 res.successful() # 子程序是否调用成功 进程池方法apply_async()中的回调函数callback说明: a、不需要回调函数的场景:如果在主进程中等待进程池中所有任务都执行完毕后,再统一处理结果,则无需回调函数,如上例。 b、需要回调函数的场景:进程池中任何一个任务一旦处理完了,就立即告知主进程,主进程则调用一个函数去处理该结果,该函数即回调函数。 例: # 任务函数(子进程) def get_page(url): print('<进程 %s> 正在下载页面: %s' % (os.getpid(), url)) time.sleep(random.randint(1, 3)) return url+'/---parsing-page---' # 用url充当下载后的结果 # 回调函数 def parse_page(page_content): # 任务函数的返回值作为该参数 print('<进程 %s> 正在解析页面: %s' % (os.getpid(), page_content)) time.sleep(1) print('{%s 回调函数处理结果: 成功!}' % os.getpid()) if __name__ == '__main__': urls = [ 'http://www.taobao.com/v1.0/1', 'http://www.taobao.com/v1.0/2', 'http://www.taobao.com/v1.0/3', 'http://www.taobao.com/v1.0/4' ] p = Pool(processes=8) # 实例化进程池 # 异步的方式提交任务,然后把任务的结果交给callback处理 # 注意:会专门开启一个进程来处理callback指定的任务(单独的一个进程,而且只有一个) for url in urls: p.apply_async(get_page, args=(url,), callback=parse_page) # 异步执行进程池。任务函数的返回值作为callback的参数 # 异步提交完任务后,主进程先关闭p(必须先关闭),然后再用p.join()等待所有任务结束(包括callback) p.close() # 关闭进程池,即进入子进程 p.join() # 进入子进程,必须在close()或terminate()之后 print('{主进程 %s}' % os.getpid()) 82、ThreadLocal:一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。 作用:实现线程的不同函数之间局部变量的传递。 ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等, 这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。例: import threading local = threading.local() # 实例一个ThreadLocal对象,用于封装属性,并能保证多线程使用该属性而不互相干扰 # 线程调用的方法 def func1(name, age, score): local.name = name # 封装属性 local.age = age local.score = score func2() # 调用某个函数,将参数传给func2 def func2(): name = local.name # 读取属性 age = local.age score = local.score print('name:%s,age:%d,score:%0.1f' % (name, age, score), threading.current_thread().ident) thr = threading.Thread(target=func1, args=('wangchunwang', 25, 99.5)) thr.start() thr.join() 83、进程 VS 线程。 进程是最小的资源单位,线程是进程最小的执行单位(一个进程至少有一个主线程)。 要实现多任务,通常我们会设计Master-Worker模式,Master(主进程/线程)负责分配任务,Worker(子进程/线程)负责执行任务。 如果把进程比喻成项目组办公室,那么线程就是办公室里的员工,一个办公室可以有多个员工,每个员工的任务不同,但他们共享办公司资源。 进程优点:稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。 进程缺点:耗CPU和内存。 线程优点:效率高。 线程缺点:任何一个线程挂掉都可能直接造成整个进程崩溃,因为所有线程共享进程的内存。 任务分为:计算密集型和IO密集型。 计算密集型(C等)任务主要消耗CPU资源,多核提升的是计算性能; -->>使用多进程 IO密集型(Python等)任务主要涉及到网络IO、磁盘IO,多核对IO操作没什么用处; -->>使用多线程 在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。 分布式进程:Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。 84、协程Coroutine(异步IO,也叫"微线程",轻量级,高效):是一种用户态的轻量级【单线程】,协程的任务调度完全由用户控制(用await或yield from)。 对于IO密集型任务我们还有一种选择就是协程。协程就是利用【单线程】实现 “并发” 效果。 对一个线程进行切片,使得线程在程序代码块之间来回切换执行任务,一旦遇到IO操作,就会从应用程序级别(而非操作系统)控制切换,此类似于CPU中断。 协程相比多线程的一大优势就是省去了多线程之间的切换开销,获得了更高的运行效率。所以可以用协程取代多线程! 并发:同一时刻只能处理一个任务,但可以 “来回切换” 处理多个任务。(一个处理器处理多个任务) 并行:同一时刻可以处理多个任务。(多个处理器或者多核处理器同时处理多个任务) 要想创建协程,只需要包含至少一个yield表达式(通常在一个无线循环中)的函数,到达该yield表达式时,协程的执行被挂起并等待数据, 收到数据后(不会影响其他协程),协程从该yield处恢复处理,如此循环。 Python3.4中加入了asyncio模块,引入"@asyncio.coroutine"装饰器和yield from语法。 协程能取代多线程和多进程,如读写磁盘文件、网络数据的异步问题。 多线程和多进程的模型虽然解决了异步问题,但是系统不能无上限地增加线程,系统切换线程的开销也很大。 协程优点: 1)能实现异步; 2)协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级; 3)单线程内就可以实现并发的效果,最大限度地利用cpu单核,效率高; 4)修改共享数据不需加锁(由于单线程); 那怎么利用多核CPU呢?最简单的方法是 “多进程+协程” ,既充分利用多核,又充分发挥协程的高效率,从而获得极高的性能。 协程与线程的联系和区别: 1)一个线程可以有多个协程,一个进程也可以有多个协程,这样Python则能使用多核CPU; 2)线程、进程都是同步机制,而协程是异步; 3)协程能保留上一次调用时的状态。 生成器和协程的区别:生成器是数据的产生者,协程是数据的消费者。 85、同步和异步、阻塞和非阻塞的区别 同步:执行一个操作之后,需要主动等待调用的返回结果; 异步:执行一个操作之后,不需要主动等待调用的返回结果,若接收到结果通知,再回来执行刚才没执行完的操作。 同步和异步关心的问题是:要不要主动等待返回结果。 阻塞:在执行一个操作时,不能做其他操作; 非阻塞:在执行一个操作时,能做其他操作。 阻塞和非阻塞关心的问题是:能不能做其他操作。 86、协程标库(asyncio):基于事件循环,从asyncio模块中直接获取一个"EventLoop"的引用,然后把需要执行的协程扔到"EventLoop"中执行。 1)@asyncio.coroutine声明一个协程; 2)关键字yield from来等待协程的返回结果,yield from的对象必须是可循环(可迭代); 3)多个协程可以封装成一组Task然后并发执行,Task是Future的子类。 例: @asyncio.coroutine # 声明一个协程 def hellopf(): print('Hello!', threading.current_thread()) yield from asyncio.sleep(2) # asyncio.sleep()返回一个可循环(可迭代),yield from后面必须跟可循环(可迭代)对象 # 示例中sleep为异步IO的代表,在实际项目中,可以使用协程异步的读写网络、读写文件、渲染界面等 print('World!', threading.current_thread()) tasks = [hellopf(), hellopf()] loop = asyncio.get_event_loop() # 1.要运行协程,就要用事件循环! loop.run_until_complete(asyncio.wait(tasks)) # 2.运行循环。多个任务用wait(),如单个任务,不需要asyncio.wait() loop.close() # 3.关闭事件循环 # ------------------------------------------------------------------ # Python3.5变化: a.把@asyncio.coroutine替换为"async"(置于def之前); # sync:同步;async:异步。 b.把yield from替换为await。 例: async def wget(host): print('wget %s...' %host) connect = asyncio.open_connection(host, 80) reader, writer = await connect header = 'GET/HTTP/1.0 Host: %s ' % (host) writer.write(header.encode('utf-8')) await writer.drain() while True: line = await reader.readline() if line == b' ': break print('%s header > %s' % (host, line.decode('utf-8').rstrip())) # Ignore the body, close the socket writer.close() loop = asyncio.get_event_loop() tasks = [wget(host) for host in ['www.sina.com.cn', 'www.sohu.com', 'www.163.com']] loop.run_until_complete(asyncio.wait(tasks)) loop.close() **第三方协程库:gevent(不需要用户控制任务调度,而是自动控制任务调度) g1 = gevent.spawn(任务函数, args) g2 = gevent.spawn(任务函数, args) gevent.joinall((g1, g2)) 87、正则表达式("re"模块)。强烈建议使用Python的原生字符"r"前缀,就不用考虑转义的问题。 1)元字符:表示一些特殊的含义,一般不是指具体字符。 ------------------------------------------------------------------------------------------- . | 匹配任意一个字符(除了换行符 ) ------------------------------------------------------------------------------------------- [] | 匹配字符集合中的一个字符 ------------------------------------------------------------------------------------------- [^] | 对字符集求反,也就是上面的反操作。尖号必须在方括号里的最前面 ------------------------------------------------------------------------------------------- - | 定义[]里的一个字符区间,例如[a-z] ------------------------------------------------------------------------------------------- () | 对表达式进行分组,将圆括号内的内容当做一个整体,并获得匹配的值 ------------------------------------------------------------------------------------------- | 对紧跟其后的一个字符进行转义 ------------------------------------------------------------------------------------------- | | 逻辑或操作符 ------------------------------------------------------------------------------------------- 2)转义字符:具有特殊功能的字符。 ------------------------------------------------------------------------------------------- , | 匹配回车和换行符 ------------------------------------------------------------------------------------------- | 匹配制表符 ------------------------------------------------------------------------------------------- \ | 匹配斜杠 ------------------------------------------------------------------------------------------- ^ | 匹配^符号 ------------------------------------------------------------------------------------------- $ | 匹配$符号 ------------------------------------------------------------------------------------------- . | 匹配小数点. ------------------------------------------------------------------------------------------- 3)预定义字符:匹配预定义字符集中的任意一个字符。 ------------------------------------------------------------------------------------------- d | 任意一个数字,0~9 中的任意一个 ------------------------------------------------------------------------------------------- w | 任意一个字母或数字或下划线,也就是 A~Z,a~z,0~9,_ 中的任意一个 ------------------------------------------------------------------------------------------- s | 空格、制表符、换页符等空白字符的其中任意一个 ------------------------------------------------------------------------------------------- D | d的反集,也就是非数字的任意一个字符,等同于[^d] ------------------------------------------------------------------------------------------- W | w的反集,也就是[^w] ------------------------------------------------------------------------------------------- S | s的反集,也就是[^s] ------------------------------------------------------------------------------------------- 4)重复 ------------------------------------------------------------------------------------------- {n} | 表达式重复n次,比如d{2}相当于dd,a{3}相当于aaa ------------------------------------------------------------------------------------------- {m,n} | 表达式至少重复m次,最多重复n次。比如ab{1,3}可以匹配ab或abb或abbb ------------------------------------------------------------------------------------------- {m,} | 表达式至少重复m次,比如wd{2,}可以匹配a12,_1111,M123等等 ------------------------------------------------------------------------------------------- ? | 匹配表达式0次或者1次,相当于{0,1},比如a[cd]?可以匹配a,ac,ad ------------------------------------------------------------------------------------------- + | 表达式至少出现1次,相当于{1,},比如a+b可以匹配ab,aab,aaab等等 ------------------------------------------------------------------------------------------- * | 表达式出现任意次,相当于{0,},比如^*b可以匹配b,^^^b等等 ------------------------------------------------------------------------------------------- 5)位置 ------------------------------------------------------------------------------------------- ^ | 在字符串开始的地方匹配,符号本身不匹配任何字符 ------------------------------------------------------------------------------------------- $ | 在字符串结束的地方匹配,符号本身不匹配任何字符 ------------------------------------------------------------------------------------------- | 匹配一个单词边界,也就是单词和空格之间的位置,符号本身不匹配任何字符 ------------------------------------------------------------------------------------------- B | 匹配非单词边界,即左右两边都是w范围或者左右两边都不是w范围时的字符缝隙 ------------------------------------------------------------------------------------------- 常用方法与比较: 1)re.match(pattern, string, flags):尝试从字符串的【开头】匹配【一个】模式。 text = "JGood 666666 is a handsome boy, he is cool, clever, and so on..." m = re.match(r"(w+)s", text) # 结果是"JGood " m = re.match(r"d+", text) # 结果是None,因为开头不是数字 2)re.search(pattern, string, flags):在【整个】字符串内查找模式匹配,只到找到第一个匹配就返回。 text = "JGood 666666 is a handsome boy, he is cool, clever, and so on..." m = re.search(r"d+", text) # 结果是"666666" 3)re.findall(pattern, string, flags):获取字符串中所有匹配的字符串。 text = "JGood 666666 is a handsome boy, he is cool, clever, and so on..." m = re.findall(r'e',text) # 结果是['e', 'e', 'e', 'e'] 4)re.finditer(pattern, string, flags):获取字符串中所有匹配的字符串,并把它们作为一个迭代器返回。 5)用正则表达式分割字符串比用固定的字符更灵活,如: re.split(r's+', 'a b c') # 必须是re包的split()方法。以一个或多个空格分割。 6)reg_obj = re.compile(pattern, flags):把模板pattern编译成"正则对象",提高效率。如reg_obj.search(string)。 7)正则分组:分组就是去已经匹配到的内容里面再筛选出需要的内容,相当于二次过滤。实现分组用圆括号(),而获取分组的内容靠的是group()、groups()和groupdict()方法。 group():获取匹配到的整体结果 group(1):获取匹配到的分组1的结果 groups():获取匹配到的分组结果元组 groupdict():获取匹配到的分组中所有命名的字典 对于findall()、finditer():没有group()、groups()、groupdict()方法。 正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。加问号"?"可以去除贪婪匹配(因"?"匹配0次或者1次)。如:re.match(r'^(d+?)(0*)$', '102300').groups()。 88、一些常用的内置模块。 (一)datetime:日期时间模块。 Python中时间日期格式化符号: %y 两位数的年份(00-99) ※ %Y 四位数的年份(000-9999) ※ %m 月(01-12) ※ %d 天(0-31) ※ %H 24小时制(0-23) %I 12小时制(01-12) ※ %M 分(00-59) ※ %S 秒(00-59) #---常用格式:日月小写,其他全大写 %a 本地简化星期名称 %A 本地完整星期名称 %b 本地简化的月份名称 %B 本地完整的月份名称 %c 本地相应的日期表示和时间表示 %j 年内的一天(001-366) %p 本地A.M.或P.M.的等价符 %U 一年中的周数(00-53)星期天为星期的开始 %w 星期几(0-6),星期天为星期的开始 %W 一年中的星期数(00-53)星期一为星期的开始 %x 本地相应的日期表示 %X 本地相应的时间表示 %Z 当前时区的名称 %% '%'号本身 Ⅰ.datetime模块中的【datetime】类:这是一个日期和时间组合的类,由年、月、日、时、分、秒、微秒组成。 => from datetime import datetime (a) datetime.today() # 获取当前本地日期时间 datetime.now([tz]) # 获取当前日期时间,如果提供了参数tz,则获取tz参数所指时区的本地时间 datetime(2017, 5, 20, 0, 0, 0) # 构建日期时间,返回datetime对象 dtobj.year/month/day # 返回年、月、日 dtobj.hour/minute/second # 返回时、分、秒 dtobj.date() # 获取日期部分,返回date对象 dtobj.time() # 获取时间部分,返回time对象 combine(date, time) # 把指定的date和time对象整合成一个datetime对象 (b) 时间戳(timestamp)一旦确定,其UTC(时区)时间就确定了。 如果要存储datetime,最佳方法是将其转换为时间戳(timestamp)再存储,因为时间戳与时区完全无关。时间戳是一个浮点数。 datetime.timestamp('datetime对象') # 日期时间-->时间戳,若是字符串需要把字符串转成datetime对象 datetime.fromtimestamp('时间戳') # 时间戳-->日期时间 import time now_timestamp = time.time() # 获取当前时间戳 (c) dtobj.isoformat([sep]) # 本地格式化。返回一个"%Y-%m-%dT%H:%M:%S"字符串,sep是"日期 时间"的分隔符,默认是"T" datetime.strptime('2017-5-21', '%Y-%m-%d') # 字符串解析(p)-->日期时间 (常用,注意参数的分隔符一致即可) datetime.strftime(now, '%Y-%m-%d %H:%M:%S') # 日期时间格式化(f)-->字符串 (d) 时间的加减,timedelta类属于datetime模块。 now + timedelta(hours=1) (e) 时区(UTC): utc_curr = datetime.utcnow() # 返回当前UTC日期时间的datetime对象 bj_utc = datetime.astimezone(utc_curr, timezone(timedelta(hours=8))) # astimezone(),转换成指定时区 Ⅱ.datetime模块中的【date】类:这是一个日期类,由年、月、日组成。 => from datetime import date date.max # date对象所能表示的最大日期,是一个date对象 date.min # date对象所能表示的最小日期,是一个date对象 date.today() # 返回当前本地日期,是一个date对象 date(year, month, day) # 构建日期,返回date对象 date.fromtimestamp() # 根据给定的时间戳,返回一个date对象 date.weekday(date) # 返回星期,星期一是0(参数必须是一个date对象) date.isoweekday(date) # 返回星期,星期一是1(参数必须是一个date对象) date.isocalendar(date) # 返回date类型对象中的year(年),week(周数),weekday(星期)(参数必须是一个date对象) date_obj.strftime(fmt) # 返回自定义格式的日期字符串 date_obj.isoformat() # 本地格式化。返回一个"%Y-%m-%d"日期字符串 date_obj.replace(year, month, day) # 拷贝一个新的日期对象,用指定的年,月,日参数替换原有对象中的属性 date_obj.year/month/day # 返回日期对象的年/月/日 date_obj < date_obj # 日期可以比较大小,返回布尔 Ⅲ.datetime模块中的【time】类:这是一个时间类,由时、分、秒以及微秒组成。 => from datetime import time time.min、time.max # time类所能表示的最小、最大时间 time_obj = time(09, 59, 59) # 构建时间,返回time对象 time_obj.hour/minute/second # 返回时、分、秒 time_obj.isoformat() # 本地格式化。返回"%H:%M:%S"格式的字符串 time_obj.strftime(fmt) # 返回自定义格式的时间字符串 time_obj.replace(year, month, day) # 拷贝一个新的时间对象,用指定的时、分、秒参数替换原有对象中的属性 (二)subprocess:创建子进程。可以在Python的代码里执行操作系统级别的命令。使用run()方法调用子进程,执行操作系统命令。 run()方法返回的是一个CompletedProcess类型对象,不能直接获取我们通常想要的结果。要获取命令执行的结果或者信息, 在调用run()方法的时候,请指定stdout=subprocess.PIPE,且建议shell=True。 例:s = subprocess.run('ipconfig', shell=True, stdout=subprocess.PIPE) 获取结果:s.stdout.decode('gbk') (三)random:生成伪随机数(实际上计算机随机函数所产生的“随机数”并不随机)。 random.random():返回一个介于左闭右开[0.0, 1.0)区间的浮点数。 random.randint(a, b):返回一个a <= N <= b的随机整数N。 random.uniform(a, b):返回一个介于a和b之间的浮点数。 random.sample(population, k):从population样本或集合中随机抽取(不重复)K个元素形成新的序列。常用于不重复的随机抽样、打乱序列。 random.choice(seq):从非空序列seq中随机选取一个元素。如果seq为空则弹出 IndexError异常。 (四)base64模块:对二进制数据编码,其结果由数字、大小写字母、/、+ 和 = 组成。避免显示乱码(用记事本打开exe、jpg、pdf这些文件时,我们都会看到一大堆乱码); base64是把3字节一组变为4字节一组,所以,Base64编码的长度永远是4的倍数; base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。 base64.b64encode(str.encode()) # 编码 base64.b64decode(str.encode()) # 解码 base64.urlsafe_b64encode(str.encode()) # 对于URL二进制编码时,把字符+和/分别变成-和_ base64.urlsafe_b64decode(str.encode()) (五)hashlib:哈希(摘要)算法模块。把任意长度的输入,通过某种hash算法(单向算法),变换成固定长度的输出,该输出就是散列,也称摘要。 要注意,摘要算法不是加密算法,不能用于加密数据(因为无法通过摘要反推明文),只能用于防止数据被篡改, 但是它的单向计算特性决定了可以在不存储明文口令的情况下验证用户口令。 那么哈希算法有什么用呢?最常用的就是密码加密!密码加密不像数据加密,通常不需要反向解析出明文。 1、hashlib.md5():MD5是最常见的哈希算法,速度很快,结果是一个32位的十六进制字符串表示。如: import hashlib md5 = hashlib.md5('how to use md5 in python hashlib?'.encode()) # 返回hash对象 print(md5.hexdigest()) # digest()获取bytes类型的摘要,hexdigest()获取十六进制str类型的摘要 2、hashlib.sha1():"sha1"中是数字"1",不是字母"l"。与md5()用法一致,结果是一个40位的十六进制字符串。类似的方法还有sha256()、sha512()等。 3、hash_obj.update(bytes):更新hash对象,连续调用该方法相当于连续追加更新摘要。 (六)shutil:主要用于操作文件,如拷贝、移动、删除、压缩、解压。 shutil.copyfile(src, dst):拷贝文件。 shutil.copy(src, dst):拷贝文件,包括权限。 shutil.copy2(src, dst):拷贝文件,包括权限和状态。 shutil.copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2):递归地复制目录及其子目录的文件和状态信息。 参数:symlinks 指定是否复制软链接,小心陷入死循环; ignore 指定不参与复制的文件,其值应该是一个ignore_patterns()方法; copy_function 指定复制的模式。 shutil.rmtree(path[, ignore_errors[, onerror]]):归地删除目录及子目录内的文件。 shutil.move(src, dst): 递归地移动文件。 shutil.make_archive(base_name, format[, root_dir[, base_dir[, verbose[, dry_run[, owner[, group[, logger]]]]]]]):创建压缩文件。 参数:base_name 压缩后的文件名。如果不指定绝对路径,则压缩文件保存在当前目录下。 format 压缩格式,可以是“zip”, “tar”, “bztar” ,“gztar”,“xztar”中的一种。 root_dir 设置压缩包里的根目录,一般使用默认值。 base_dir 要进行压缩的源文件或目录。 owner 用户,默认当前用户。 group 组,默认当前组。 logger 用于记录日志,通常是logging.Logger对象。 shutil.unpack_archive(filename[, extract_dir[, format]]):解压。 参数:filename 压缩文档的完整路径 extract_dir 解压缩路径,默认为当前目录。 format 压缩格式。默认使用文件后缀名代码的压缩格式。 (七)timeit:计时器,测试代码运行时间。 timeit(stmt='pass', setup='pass', timer=<defaulttimer>, number=1000000) 参数:stmt 要执行的“代码块”。字符串形式; setup 执行代码的准备工作,不计入时间,一般是import之类的。如“from __main__ import myclass”; timer 这个在win32下是time.clock(),linux下是time.time(),默认的,不用管; number 要执行stmt遍数,默认百万,记得修改默认值! (八)collections:集合模块。 1、namedtuple(tbname, field):命名的元组。将"tbname"视为表名,"field"视为字段名。如: tb = namedtuple('score', 'chinese, math') tr = tb('99','100') # 赋值 print(tr.chinese, tr.math) # 取值 2、deque(iterable):双向队列。为了高效实现插入和删除操作的双向列表list,适合用于队列。因为list是线性存储,数据量大的时候,插入和删除效率很低。 deque除了实现list的append()和pop()外,还支持appendleft()和popleft()。rotate(i),从倒数第i个位置往前放。 3、defaultdict(default_func):使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict。如: def_dic = defaultdict(lambda:'不存在该值') def_dic['k1'] = 'a1' 4、OrderedDict类:有序字典。使用dict时,Key是无序的,如果要保持Key的顺序,可以用OrderedDict,如: orddic=OrderedDict({'1':'a', '2':'b', '3':'c'}) 5、Counter类:是一个简单的计数器,例如,统计字符出现的个数。 c = Counter() # 实例化计数器 for t in 'wangchunwang': c[t] = c[t]+1 # 使用计数器统计 print(c.most_common()) # 结果是元组构成的列表[('n', 3), ('w', 2), ('a', 2), ('g', 2), ('c', 1), ('h', 1), ('u', 1)] 6、heapq:堆。会自动排序。 heap = [] heapq.heappush(heap, (2, 'two')) heapq.heappush(heap, (1, 'one')) (九)itertools模块:操作可迭代对象,返回值不是list,而是Iterator,只能用"for"循环迭代的时候才真正计算。 1、count类,无限计数。 2、cycle类,无限循环每个元素。 3、repeat类,重复整个参数。 4、chain类,将迭代对象串联。 5、groupby类,把迭代器中相邻的重复元素挑出来放在一起。 (十)contextlib模块:上下文。 @contextmanager,上下文装饰器。 (十一)关于XML。 操作XML常用的两种方法:DOM(基于对象)和SAX(基于事件)。 DOM方法会把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以遍历树的任意节点。 SAX方法是流模式,边读边解析,占用内存小,解析快,缺点是我们需要自己处理事件(继承sax.ContentHandler类,重写方法)。 正常情况下,优先考虑SAX,因为DOM实在太占内存。 ElementTree模块解析XML(XML元素树,解析和写入节点): from xml.etree import ElementTree tree = ElementTree.parse('sax_test.xml') # 获得元素树解析器 # root = tree.getroot() # 获取根节点 # print(root) stations = set({}) for ele in tree.getiterator('station_name'): # 通过迭代器迭代任意节点 stations.add(ele.text) # 获取元素文本;attrib获取属性 (十二)关于HTMLParser。 HTML本质上是XML的子集,但是HTML的语法没有XML那么严格,所以不能用标准的DOM或SAX来解析HTML。Python提供了HTMLParser来非常方便地解析HTML。 (十三)URL:统一资源定位(如HTML页面,图片等)。它的组成可分成6部分,格式为: protocol://hostname[:port]/path/[;param][?query][#frag] 说明: ----------------------------------------------------------------------------------------------------------------------------- 1 | protocol | 必须,协议部分,如http,ftp ----------------------------------------------------------------------------------------------------------------------------- 2 | hostname:port | 必须,主机(ip或域名)与端口部分;ftp可能用到"user:passwd@ip:port" ----------------------------------------------------------------------------------------------------------------------------- 3 | path | 必须,虚拟目录 ----------------------------------------------------------------------------------------------------------------------------- 4 | param | 可选,参数部分,如果向服务器传入参数,极少用 ----------------------------------------------------------------------------------------------------------------------------- 5 | query | 可选,查询部分,如果需要从服务器那里查询内容,很常用 ----------------------------------------------------------------------------------------------------------------------------- 6 | frag | 可选,锚部分,网页中可能会分为不同的片段,如果访问网页后直接到达指定位置,可以在这部分设置,较常用 ----------------------------------------------------------------------------------------------------------------------------- 以上6个部分对应urlparse解析结果(scheme, netloc, path, params, query, fragment) urllib.request模块中的核心函数: request.urlparse(urlstr): 解析成6个部分。 request.urlunparse(urltup): 组成完整的URL。 request.urljoin(baseurl, newurl): 将baseurl根目录和newurl组成新的完整的URL。 request.urlopen(urlstr, data=None): 打开一个URL。data为None,则是Get请求,data不为None,则是Post请求。 urllib.parse.urlencode(dict): 将字典编码成URL的"query"部分。其中空格编译成"+",其余特殊字符编译成"%xx"。注意这个函数在parse里面! request.quote(urldata, safe='/'): 对特殊字符进行编码成"%xx",safe中的字符不编码。 request.unquote(urldata): 对编过码的字符解码。 request.urlretrieve(urlstr, filename): 下载URL文件到filename文件中。 (十四)request请求模块:from urllib import request 1、GET 请求指定的网址:rep = request.urlopen(url='http://www.baidu.com/') # 直接传入url print(rep.status, rep.reason) # 状态,状态描述 print(rep.getheaders()) 添加http头信息:url = request.Request(url='http://www.baidu.com/', headers={'User-Agent': 'Mozilla/6.0'}) # 先用Request构建url,并且添加头部信息 rep = request.urlopen(url=url) # 再传入url print(rep.getheaders()) print(rep.read().decode()) 2、POST 如果要以POST发送一个请求,只需要把urlopen()中参数data以bytes形式传入。 email = input('微博账号:') passwd = input('密码:') login_info = urllib.parse.urlencode({ 'username':email, 'password':passwd, 'entry': 'mweibo', 'client_id': '', 'savestate': '1', 'ec': '', 'pagerefer': 'https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F' }) url = request.Request(url='https://passport.weibo.cn/sso/login', data=login_info.encode()) # data不为None,则是Post请求 # 也可以使用add_header方法添加头信息 url.add_header('Origin', 'https://passport.weibo.cn') url.add_header('User-Agent', 'Mozilla/6.0') url.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F') rep = request.urlopen(url=url) print(rep.status,rep.read()) 3、简单的爬虫(抓取图片) from urllib import request # 获取页面 def getHTMLContent(url): html = request.urlopen(url, timeout=30) return html.read() # 解析出图片的URL def getImgUrl(html): reg_patt = re.compile(pattern=r'<img.+?src="(.+?.jpg)"s+width') img_urls = reg_patt.findall(string=html.decode()) return img_urls # 批量下载图片并保存 def downMultImg(img_urls, n=10, path='Images/'): count = 1 for url in img_urls: request.urlretrieve(url=url, filename='{0}{1}{2}'.format(path, count, '.jpg')) count = count + 1 if count == n+1: print('共下载%d张图片' %(n)) return # 封装 def download(url): html = getHTMLContent(url) img_urls = getImgUrl(html) downMultImg(img_urls, n=20) if __name__ == '__main__': url = 'http://tieba.baidu.com/p/2256306796' download(url) 89、第三方库。基本上,所有的第三方模块都会在PyPI-the Python Package Index上注册,只要找到对应的模块名字,即可用"pip install modulename"安装(如果提示权限不足则在后面加"--user")。 (1)pillow,图像处理库。 在命令行cmd直接输入:pip install pillow 进行安装。例:制作验证码: from PIL import Image, ImageDraw # 导入pillow def rand_draw_color(): return random.randint(0, 200), random.randint(50, 200), random.randint(50, 200) def font_color(): return random.randint(10, 100), random.randint(10, 100), random.randint(10, 100) def rand_char(): return chr(random.randint(65,90)) # 图像的尺寸 width = 40 * 4 height = 60 # 创建图像 img = Image.new('RGB', (width, height), color=(255,255,255)) # 创建画布(用于涂鸦) draw = ImageDraw.Draw(img) # 填充画布颜色,按像素填充 for x in range(width): for y in range(height): draw.point((x, y), fill=rand_draw_color()) # 当调用函数带括号时,表获取函数返回值,即函数有"return"语句 # 当调用函数不带括号时,表仅仅引用函数,即函数无"return"语句 # 输出4个随机字符 for i in range(4): draw.text((40 * i + 10, 10), rand_char(), fill=font_color(), font=ImageFont.truetype(font='Font/simhei.ttf', size=38)) # 高斯模糊处理 # img = img.filter(ImageFilter.GaussianBlur(1)) # 这里必须是赋值处理 img.save('E:/vali_code.jpg', 'JPEG') (2)virtualenv为应用提供了隔离的Python运行环境,解决了不同应用间多版本的冲突问题。 90、图形界面(GUI),调用内置的tkinter。例: import tkinter from tkinter import messagebox topFrame = tkinter.Tk() topFrame.title('提示') def btComm(ev=None): messagebox.showinfo('网址', 'www.wcw.com') butt = tkinter.Button(master=topFrame, text='点击一下', font=('Helvetica',12,'bold'), command=btComm) butt.pack() # 装箱。Packer布局 topFrame.mainloop() # 必须调用循环方法 91、数据库编程。 关系型:闭源收费数据库:Oracle、DB2; 开源免费数据库:MySQL、SQLite(嵌入式数据库,轻量,Python内置) 1、SQLite编程 # 连接数据库文件,若无则新建 conn = sqlite3.connect('test.db') # 创建游标,实际操作者 cursor = conn.cursor() cursor.execute(sql) # 执行query cursor.close() # 关闭游标 conn.commit() # 执行insert等操作后要调用commit()提交事务 conn.close() # 关闭连接 2、MySQL编程--使用pymysql模块 安装驱动:pip install pymysql, 例: import pymysql # 连接数据库,注意编码。在MySQL客户端命令行如有乱码,执行语句"set names gbk;" coon = pymysql.connect(user='root', password='0000', host='127.0.0.1', port=3306, database='test', charset='utf8') cursor = coon.cursor() # 创建游标 user_id = 5 name = 'WcwNina' age = 22 sex = '女' department = '营销部' salary = 9000 # 执行SQL语句,返回影响的行数。变量放入列表里,防止SQL注入! sql = 'insert into user(user_id, name, age, sex, department, salary) values(%s, %s, %s, %s, %s, %s)' effect_row = cursor.execute(sql, (user_id, name, age, sex, department, salary)) effect_row2 = cursor.execute('select salary from user') coon.commit() # 提交 cursor.close() coon.close() print(cursor.fetchmany(2)) cursor.scroll(0) # 游标下移一位 3、MySQL框架:SQLAlchemy驱动模块,把关系数据库的表结构映射到class对象上(即"ORM"技术:对象关系映射)。 常用的SQL语句可能会导致安全问题(因为是字符串的语句,会存在SQL注入)。SQLAlchemy兼容多种关系型数据库。 安装SQLAlchemy:pip install sqlalchemy。例: import sqlalchemy from sqlalchemy import * from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker #--------------------------------------------------------------------------------------------------------------------# ## 1.创建引擎连接数据库,可指定字符集。这里连接库为test,echo参数为True时,会显示每条执行的SQL语句,生产环境下可关闭 ## create_engine()用来连接数据库,格式:'数据库类型+数据库驱动名称://用户名:口令@主机地址:端口号/数据库名?参数' db = sqlalchemy.create_engine('mysql+pymysql://username:pwd@localhost:3306/test?charset=utf8mb4', echo=False) ## 2.声明ORM基类 Base = declarative_base() # 工厂方法,返回一个类对象 [dɪˈklærətɪv] ## 3.建模型 (表结构映射) class Department(Base): __tablename__ = 'department' # 表名 RowID = Column(Integer, primary_key=True, autoincrement=True) # 自增主键字段 Dep_Code = Column(String(3), nullable=False, unique=True, index=True) Dep_Name = Column(String(20), nullable=True, default='--') FK_id = Column(Integer, ForeignKey('table.field')) # 外键 __table_args__ = ( UniqueConstraint('RowID', 'Dep_Code'), # 联合唯一约束 Index('Dep_Code', 'Dep_Name') # 联合索引 ) ## 4.创建表结构 (后台会自动建立一张表) Base.metadata.create_all(db) #--------------------------------------------------------------------------------------------------------------------# # 建立会话,类似游标 db = sqlalchemy.create_engine('mysql+pymysql://username:pwd@localhost:3306/test?charset=utf8mb4', echo=False) Session = sessionmaker(bind=db) # 工厂方法 session = Session() # 插入记录,使用关系映射类 dept_data = Department(Dep_Code='004', Dep_Name='人事部') session.add(dept_data) session.commit() # 查询记录,最后通常需要调用all() data = session.query(Department).all()[:2] # 结果分片 data = session.query(Department).get(pk) # 注意传入主键,返回指定主键对应的行,如不存在,返回None。get_or_404():返回指定主键对应的行,如不存在,返回404。都是只返回一个结果。 data = session.query(Department).filter(Department.RowID>1).offset(2).limit(1).all() # 偏移2个位置,返回1条结果。limit()和offset)()常用于分页 data = session.query(Department.Dep_Code, Department.Dep_Name).filter(Department.RowID<3).order_by(Department.RowID.desc()).all() # 返回指定字段,倒序结果 data = session.query(Department).filter(text("RowID<:rid and Dep_Name=:name")).params(rid=3, name='WcwNina').all() # 占位符设置变量参数 data = session.query(Department).filter(Department.Dep_Code=='003').filter(Department.Dep_Name=='软件部').all() # 多条件查询,类似"and"。注意filter参数是bool型! data = session.query(Department).filter(Department.RowID.between(1,3), Department.Dep_Name.like('%部')).all() # "between"限定字段范围,"like"模糊查询 data = session.query(Department).filter(Department.RowID.in_([1,2,3])).all() # "in_"范围查询 data = session.query(Department).filter(~Department.RowID.in_([1,2,3])).all() # "~"排除范围,类似"!=" data = session.query(Department).filter(and_(Department.RowID<3, Department.Dep_Code!='003')).all() # "and_",and查询 data = session.query(Department).filter(or_(Department.RowID<3, Department.Dep_Code=='003')).all() # "or_",or查询 data = session.query(Users, Favor).filter(Users.id == Favor.nid).all() # 联表(内联)查询 data = session.query(Users).join(Favor, [on '外键条件']).filter(Users.id>1).all() # "join"外键联表查询,默认内联查询 data = session.query(Users).outerjoin(Favor, [on '外键条件']).filter(Users.id>1).all() # "outerjoin"外键联表查询,外联查询 # 更新记录,查出记录后更新 data = session.query(Department).filter(Department.Dep_Code=='003').update({Dep_Name: '软件部'}) # first()返回第一条结果 session.commit() # 删除记录 data = session.query(Department).filter(Department.Dep_Code=='003') session.delete(data) session.commit() # 统计 counts = session.query(Department).filter(Department.Dep_Name.like('%部')).count() # count()函数 # 分组 from sqlalchemy import func # func 模块提供了一些常用函数 session.query(func.count(Department.Dep_Name), func.max(RowID), Department.Dep_Name).group_by(Department.Dep_Name).all() # 回滚 session.rollback() data = session.query(Department).filter(Department.Dep_Code.in_(['004'])) 92、网络模型结构可由7层(国际标准)简化为4层(民间流行):数据链路层(网卡MAC)、网络层(IP)、传输层(协议)、应用层(数据)。 网络编程(C/S架构):主要指网络通信,实现计算机之间的对话和文件传输。 IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是便于阅读。 IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,以字符串表示类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334。 TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。 端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。 Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求,使主机间或者一台计算机上的进程间可以通讯。 (1)TCP协议编程(面向连接、序列化、不重复)。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。 80端口是Web服务的标准端口。SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。 备注:发送和接收的数据是字节,需要编码encode()和解码decode()。 accept()和recv()方法都是阻塞的,所谓的阻塞,指的是程序会暂停在那,一直等到有数据过来。 重点在于数据的接收和发送! socket编程思路: # 一般会用到线程 1)服务端 1.创建套接字:socket.socket(socket.AF_INET, socket.SOCK_STREAM) 2.绑定IP和端口:s.bind(('IP', Port)) 3.监听连接:s.listen(num) 4.写任务函数,使用永久循环不断地接收客户端数据或给客户端发送数据:s.recv(),s.send() # 发送时要编码encode(),接收时要解码decode() 5.在永久循环里开启线程执行任务函数 6.传输完毕后,关闭套接字:s.close() (一般不关闭) 2)客户端 1.创建套接字:socket.socket(socket.AF_INET, socket.SOCK_STREAM) 2.连接服务器地址:s.connect(('IP', Port)) 3.发送数据或接收数据:s.send(),s.recv() # 发送时要编码encode(),接收时要解码decode() 4.传输完毕后,关闭套接字:s.close() 例1:客户端访问网络:必须知道服务器的IP地址和端口号。 import socket # 建议网络连接 sock = socket() sock.connect(('www.sina.com.cn', 80)) sock.send(b'GET/HTTP/1.1 Host:www.sina.com.cn Connection:close ') # 注意HTTP协议格式 # 接收数据,使用永循环 buffer = [] while True: data = sock.recv(1024) if data: buffer.append(data) else: break data = b''.join(buffer) sock.close() # 保存内容 header, html = data.split(b' ', 1) print(header.decode('utf-8')) with open('sina.html', 'wb') as f: f.write(html) 例2:服务器:服务器进程首先要绑定一个端口并监听来自其他客户端的连接,需要一直开启。 #####-服务器端-##### from socket import socket #----1.建立套接字,绑定IP端口---- so_server = socket(type=SOCK_STREAM) # type默认就是SOCK_STREAM,表示"流式"套接字,可靠连接的TCP协议 so_server.bind(('127.0.0.1', 8088)) so_server.listen(5) # 监听端口,入参数指定等待连接的最大数量 print('服务器已开启...') #----2.任务函数,收发数据---- def tcp(so_obj, addr): print('正在连接:', addr) so_obj.send('服务器返回:欢迎访问!'.encode()) # 向客户端发送数据send()。发送时要编码encode() while 1: data = so_obj.recv(1024).decode() # 接收客户端数据recv()。接收时要解码decode() if not data or data == 'exit': break so_obj.send('服务器已接收到数据:{data}'.format(data=data).encode()) so_obj.close() print('{addr}访问结束。'.format(addr=addr)) #----3.通过永久循环且开启线程来接受客户端的连接---- while True: # 如果不永久循环,连接一次就结束了 so, addr = so_server.accept() # 接受客户端连接,返回socket对象和客户端地址 thr = Thread(target=tcp, args=(so, addr)) # socket模块,默认是单线程,同时只能处理一个连接请求,如要实现多用户服务,需要使用多线程 thr.start() thr.join() #####-客户端-##### from socket import socket so_client = socket(type=SOCK_STREAM) so_client.connect(('127.0.0.1', 8088)) # 连接服务器(如果服务器会出现异常,可捕获异常且处理) print(so_client.recv(1024).decode()) # 接收服务器"欢迎"数据。如果数据大,则采用缓存append for data in ['客户端1', '客户端2', '客户端3', '客户端4', '客户端5']: # 向服务器发数据,并接收返回数据 so_client.send(data.encode()) print(so_client.recv(1024).decode()) so_client.send('exit'.encode()) so_client.close() (2)UDP协议编程(不面向连接、无序、可重复)。优点是和TCP比,速度快、开销小。 注意:由于UDP没有握手的过程,因此没有connect()、listen()、accept()方法。 ##### 服务器端 ##### sc = socket(type=SOCK_DGRAM) # type=SOCK_DGRAM,表示"数据报式"套接字,不可靠无连接的UDP协议 sc.bind(('127.0.0.1', 9999)) print('UDP服务器已开启...') def udp(): while 1: data, addr = sc.recvfrom(1024) print('收到请求:', addr, data.decode()) sc.sendto('服务器响应:欢迎访问,已接受到数据:{data}'.format(data=data.decode()).encode(), addr) thr = Thread(target=udp) # 使用线程处理 thr.start() thr.join() ##### 客户端 ##### sc = socket(type=SOCK_DGRAM) for data in ['客户端1', '客户端2', '客户端3', '客户端4', '客户端5']: sc.sendto(data.encode(), ('127.0.0.1', 9999)) print(sc.recv(1024).decode()) sc.close() 区别recvfrom(), sendto(), recv(), send():带有from或to的,需要接收或指定地址;不带from和to的,不需要地址。 (3)高级网络编程使用socketserver模块,更方便,更强大。 使用socketserver要点: 1.创建一个继承自"socketserver.BaseRequestHandler"的类; 2.这个类中必须重写一个名字为"handle"的方法,不能是别的名字,在此进行业务逻辑处理(进行收发数据); 3.将这个新建的类,连同服务器的IP和端口作为参数传递给"ThreadingTCPServer()"实例化; 4.启动"ThreadingTCPServerObj.serve_forever()"。 (例:见https://www.cnblogs.com/wcwnina/p/8893693.html) 93、发邮件协议:SMTP 收邮件协议:POP3、IMAP 例(SMTP): mail_host = "mail.dhcc.com.cn" # 设置SMTP服务器 from_user = "wangchunwang@dhcc.com.cn" # 发件用户名 from_pass = "DHCCwcw@070134" # 发件密码(如果是QQ类型邮箱,须要使用授权码) to_user = '575504815@qq.com' # 收件用户名 subject = 'Python SMTP 邮件测试' # 主题 # 构建消息 message = MIMEText('Python邮件发送测试', 'plain', 'utf-8') # plain表示发送文本,html表示发送html邮件 message['From'] = Header(("**旺<%s>" %from_user), 'utf-8') # 发件人显示名称 message['To'] = Header(("收件人<%s>" %to_user), 'utf-8') # 收件人显示名称 message['Subject'] = Header(subject, 'utf-8') # 主题 # 发送邮件 try: smtp = smtplib.SMTP_SSL(mail_host, 465) # 实例SMTP对象,建议使用SSL! # smtp.set_debuglevel(1) smtp.login(from_user, from_pass) # 登录发件用户 smtp.sendmail(from_user, to_user, message.as_string()) # 开始发送 print("邮件发送成功") smtp.quit() except smtplib.SMTPException as e: print("Error: 无法发送邮件->", e.args) 注意:若报错"[WinError 10060] 由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败",有可能是防火墙引起的。 94、Web开发(B/S架构):即网页、网站的开发。 1)WEB服务器(HTTP服务器):与客户端打交道,处理前端静态内容(session、request、response、HTML、js、CSS等),如Apache、Nginx、IIS。只有Apache是纯web服务器! 2)应用服务器:为应用程序处理后台业务逻辑,客户端应用程序可以调用的方法,生成动态内容,如 Weblogic(Java)、WebSphere(IBM)。 然而,现在大多数应用服务器也包含了Web服务器的功能,如Tomcat、IIS、WSGI。 1、HTTP请求方法:GET与POST,GET仅请求资源,POST会附带用户数据(区别于HTML表单的get、post方法)。一个HTTP请求只处理一个资源。 响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误。 web服务器工作:接受HTTP请求、解析HTTP请求、发送HTTP响应。 HTTP GET请求的格式,每个Header一行一个,换行符是" ": GET /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3 HTTP POST请求的格式,包含body通过" "来分隔: POST /path HTTP/1.1 Header1: Value1 Header2: Value2 Header3: Value3 body data goes here... 2、Web过程: a.浏览器发送一个HTTP请求; b.服务器收到请求,生成一个HTML文档; c.服务器把HTML文档作为HTTP响应的Body发送给浏览器; d.浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。 3、HTML定义了页面的内容,CSS来控制页面元素的样式,而JavaScript负责页面的交互逻辑。 4、CGI:通用网关接口; WSGI:Web服务器网关接口。例: def app(request, response): # web app用来处理请求和响应。request处理请求信息;response处理响应信息。 response('200 ok',[('Content-Type', 'text/html')]) body = '<h1>Hello, world</h1>' return [body.encode()] # 返回列表类型 httpd = make_server('localhost', 2017, app) # IP,Port,app httpd.serve_forever() 95、Web服务框架:很多框架都自带了WSGI,比如 Django,Flask,webpy,CherryPy等。 例: app = Flask(__name__) # 获取首页 @app.route('/', methods=['GET']) def home(): return '<h1>HOME</h1>' 96、MVC模式:M(模型) —— 业务数据(或业务逻辑),这是应用程序的主体; V(视图) —— 用户交互界面(模板); C(控制器)—— 接收来自界面的请求,处理用户界面数据显示。 97、动态执行代码。(1)eval(expression, globals=None, locals=None):执行表达式,globals和locals存放变量,通常为字典,如eval('x**y+99',{'x':2,'y':3}); (2)exec(object, globals=None, locals=None):执行代码块(函数或模块),返回值为None。 exec()不能存取任何数据,解决办法:将引用对象放入字典中。例: import math code = ''' def area_sphere(r): return '{0:e}'.format(4*math.pi*r**2) ''' context = {} context['math'] = math # 将引用对象模块放入字典 exec(code, context) # exec执行对象也能放入字典 area = context['area_sphere'] # 从字典中提取执行结果 print(area(5)) 98、上下文管理器:在正常处理系统资源(文件、线程锁和连接)之前需要先执行一些准备动作,及其之后需要继续执行一些收尾动作。 例如:当需要操作文件或数据库的时候,首先需要获取文件句柄或者数据库连接对象,当执行完相应的操作后,需要执行释放文件句柄或者关闭数据库连接的动作。 又如,当多线程程序需要访问临界资源的时候,线程首先需要获取互斥锁,当执行完成并准备退出临界区的时候,需要释放互斥锁。 上下文管理器需要实现__enter__()和__exit__()特殊方法,通过with...as...语句来使用上下文管理器。 a、__enter__(self):进入上下文管理器时调用此方法,其返回值将被放入with-as语句中as说明符指定的变量中。 b、__exit__(self, exc_type, exc_val, exc_tb):离开上下文管理器调用此方法。如果有异常出现,exc_type、exc_val、exc_tb分别为异常的类型、值和追踪信息。 此方法返回值为True或者False,分别指被引发的异常是否得到了处理,如果返回False,引发的异常会被传递出上下文。 例: class OpenFile(object): def __init__(self, filename, mode): self.filename = filename self.mode = mode def __enter__(self): self.f = open(self.filename, self.mode) return self.f # 作为as说明符指定的变量的值 def __exit__(self, type, value, tb): self.f.close() return False # 异常会被传递出上下文 ###### with OpenFile('my_file.txt', 'w') as f: f.write('Hello,') f.write('World!') 99、描述符(Descriptor):首先它是一个类,且实现了__get__(self, instance, owner),__set__(self, instance, value),__delete__(self, instance) 中一个或多个魔法方法,描述符使用时作为另一个类的类属性(一定是类属性!!!)。其中,instance是这个描述符的属性实例,owner是描述符所在的类。 描述符一般用于较底层的代码,最常见的描述符有 property、staticmethod、classsmethod(用描述符实现的装饰器),用来控制属性访问。例: class Desc(object): def __init__(self): self.dic = {} def __set__(self, instance, value): print('Desc set:', instance, value) if value < 0 or value > 100: raise ValueError('值必须在0~100之间!') self.dic[instance] = value def __get__(self, instance, owner): print('Desc get:', instance, owner) return self.dic[instance] #### class Score(object): descScore = Desc() # 描述符使用时作为另一个类的类属性,一定是类属性!!!不是实例属性!!! def __init__(self, score): self.descScore = score ### s = Score(100) print(s.descScore) 100、抽象基类(abc):类似于Java的接口interface,抽象基类定义了一些抽象属性和抽象方法,抽象类并不能直接实例化。目的:共性共用。 要定义抽象类,需要继承元类ABCMeta,这一步是必须的,因为抽象类的实现离不开元类。 在抽象类中,@abstractmethod和@abstractproperty装饰的方法/特性其子类必须实现,且可以去改变其参数和返回值。 抽象基类支持对已经存在的类进行注册,使其属于该基类,使用register()进行注册,如向抽象基类A注册类B:A.register(B)。 例: import abc class A(metaclass=abc.ABCMeta): # 继承abc.ABCMeta @abc.abstractmethod # 抽象方法,其子类必须实现之 def absfunc(self): pass # 必须空方法 >> 文章持续更新中...(转载请注明出处)
至此。转载请注明出处。记得扫码打赏支持哦,谢谢!