目录
- python常用语法点
- 1.with as 语句中调用构造函数不同写法的总结
- 2.时间戳转化为指定日期格式
- 3.带有时区的日期数据转换为时间戳
- 4.python中利用all()来优化减少判断的代码
- 5. python 中的NoneType 和空值如何判断
- 6. python中浮点数向上取整,向下取整
- 7.python中判断字符串中是否包含简体中文
- 8.python中将utf-8转换为中文
- 9. python中的defaultdict()函数
- 10.str.strip()
- 11.用python实现创建文件目录
- 12.python基于 zipfile 包实现文件打包功能(原文件层级关系不保留)
- 13.python 中反转字符串和list 的方法
- 15.url编码和解码
- 14.求可迭代元素的笛卡尔积,简化for循环
- 15.使用python中的shutil模块,实现文件打包、解压缩(原文件层级关系保留)
- 16.使用shutil.rmtree()删除目录
- 17. os.path.expanduser()
- 18.os.path.join():连接两个或更多的路径名组件
- 19.创建一个excel,在excel中添加多个sheet
- 20.将数据写入一个excel的多个sheet里
- 21、可迭代对象,迭代器
- 22、 生成器
- 23 python的垃圾回收机制(内存垃圾回收:分代回收细节)
- 24 菱形继承
- 25 python的内置函数
- 26 python位运算
- 27 collections.Counter
- 28 堆
- 29 python创建二维数组
- 30 深拷贝和浅拷贝
- 31 装饰器
- 32 try except else finally
- 32 python怎样进行内存管理
- 33.python import 执行流程
- 34.cookie和session的区别
- 34.python 多线程
- 33.进程、线程、协程
- 34.同步和异步
- python json json.dumps(),json.loads(),json.dump(),json.load()
python常用语法点
1.with as 语句中调用构造函数不同写法的总结
with as 简介
有一些操作可能需要事先设置,事后做清理工作。对于这种场景,python 的with 语法提供了一种非常方便的处理方式。
例如:
with open('test.txt') as f:
data = f.read()
相当于try finally 代码:
f = open('test.txt')
try:
data = f.read()
finally:
f.close()
with as 语句中调用构造函数
使用with as 时,可以在with as 语句中调用构造函数,也可以在之前调用,具体有什么区别,代码实例如下:
!!!含难呀
2.时间戳转化为指定日期格式
strftime(format,time):得到format格式控制下的一个代表时间的字符串。(string from time–>strftime)
strptime(date_string,format):根据format从字符串创建出一个时间类的对象。(string produce time)
date,datetime,time对象都支持上述两个函数。
方法一:
import time
# 将时间戳转换为指定格式日期
# 获取当时间戳
now = int(time.time())
# now = 1639597800
# 转换为其他日期格式,如:"%Y-%m-%d %H:%M:%S"
timeArray = time.localtime(now)
otherStyleTime = time.strftime("%Y-%m-%d %H:%M:%S",timeArray)
print(otherStyleTime)
#结果:
2021-12-16 03:50:00
方法二:
import datetime
# 获取当前时间
now = datetime.datetime.now()
otherStyleTime = now.strftime("%Y-%m-%d %H:%M:%S")
print(otherStyleTime)
#结果:
2022-03-01 16:33:43
3.带有时区的日期数据转换为时间戳
import time
import datetime
now = '2021-12-16 03:50:51.648199+00:00'
new_date = time.strptime(now,'%Y-%m-%d %H:%M:%S.%f%z')
stamp = int(time.mktime(new_date))
print(stamp)
#结果:
time.struct_time(tm_year=2021, tm_mon=12, tm_mday=16, tm_hour=3, tm_min=50, tm_sec=51, tm_wday=3, tm_yday=350, tm_isdst=-1)
1639597851
4.python中利用all()来优化减少判断的代码
情景:如果我有一个list,想判断其中的元素是否满足一个条件,后面的元素大于等于前面元素。下面介绍一般的写法和通过all()来进行比较的写法。
test = [1, 2, 3, 4, 5, 4]
一、一般写法
def checker(test):
for i in range(len(test)):
if i+1 < len(test):
if test[i] > test[i+1]:
return False
return True
二、使用all()和zip()的写法
all() : 函数用于判断给定的可迭代参数iterable中的所有元素是否都为True,如果是返回True,否则返回False。如果可迭代参数为空,返回True
zip() : 是函数用于将可迭代对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的对象
def checker_two(test):
return all(i <= j for i,j in zip(test,test[1:]))
# 如果不想zip的第二个参数创建一个list,可以这样写
from itertools import islice
def checker_three(test):
return all(i <= j for i, j in zip(test, islice(test, 1, None)))
"""
zip(*iterables) --> 一个 zip 对象产生的元组,直到输入用尽。
>>> list(zip('abcdefg', range(3), range(4)))
[('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]
zip 对象产生 n 长度的元组,其中 n 是可迭代的数量
作为位置参数传递给 zip()。 每个元组中的第 i 个元素
来自 zip() 的第 i 个可迭代参数。 这种情况一直持续到
最短的参数已用尽。
islice(iterable, stop) --> islice 对象
islice(iterable, start, stop[, step]) --> islice 对象
返回一个迭代器,其 next() 方法从
可迭代对象中返回一个被选中的值。 如果指定了 start,将跳过所有前面的元素;
否则, start 默认为零。 步幅默认为一。 如果
指定为另一个值, step 确定有多少个值
在连续呼叫之间跳过。 像列表中的 slice() 一样工作
但返回一个迭代器。
"""
python3 中zip()的使用
a = [1, 2, 3]
b = [4, 5, 6]
zipped = zip(a, b)
print(zipped) # 返回的是一个对象
print(list(zipped)) # 使用list()将对象转换为列表
#结果:
<zip object at 0x11b6c4b40>
[(1, 4), (2, 5), (3, 6)]
5. python 中的NoneType 和空值如何判断
即(TypeError:object of type ‘NoneType’ has no len())
NoneType和 空值是不一样的,可以理解为Nonetype为不存在这个参数,空值表示参数存在,但值为空。
if test is None:
print("test is NoneTyep")
elif test:
print("test is not null")
else:
print("test is null")
注意:pandas中的单个字符串可以用pd.isna(x)判断是不是nan,但是当x为list、series类型时,判断x是否为NoneType类型不能用,否则会报错ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
6. python中浮点数向上取整,向下取整
import math
a = 1.73
c = 2.15
print(math.floor(a)) # 向下取整
print(math.ceil(a)) #向上取整
print(math.floor(c))
print(math.ceil(c))
结果:
1
2
2
3
7.python中判断字符串中是否包含简体中文
import re
Chinese = re.compile(u'[\u4e00-\u9fa5]')
contents = '[[你好'
match = Chinese.search(contents)
if match:
print('包含中文')
else:
print('没有包含中文')
结果:
包含中文
待补充:正则表达式规则和用法
8.python中将utf-8转换为中文
s1 = r"\u5c31\u4e1a\u670d\u52a1"
# 转为utf-8(明文)
print(s1.encode('utf8').decode('unicode_escape'))
结果:
就业服务
9. python中的defaultdict()函数
defaultdict()是对dict()的改进,返回一个新的类字典对象。是内置类的子类。它覆盖了一种方法并添加了一个可写实例变量。第一个参数提供default_factory属性的初始值,它默认为None.剩余的参数都被视为与传递给dict构造函数一样,包括关键字参数。
(1)使用list as default_factory,很容易将一系列键值对分组到列表字典中:
from collections import defaultdict
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
print(d)
for k, v in s:
d[k].append(v)
print(dict(d))
结果:
defaultdict(<class 'list'>, {})
{'yellow': [1, 3], 'blue': [2, 4], 'red': [1]}
(2)default_factory设置为int,对计数有用
from collections import defaultdict
s = "mississippi"
d = defaultdict(int)
for k in s:
d[k] += 1
print(dict(d))
结果:
{'m': 1, 'i': 4, 's': 4, 'p': 2}
注:
当第一次遇到一个字母时,它会从映射中丢失,因此default_factory函数调用int()以提供默认计数为零。然后递增操作为每个字母建立计数
(3)设置default_factory对set 构建集合字典很有用
from collections import defaultdict
s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
d = defaultdict(set)
for k, v in s:
d[k].add(v)
print(d.items())
结果:
[('blue', {2, 4}), ('red', {1, 3})]
10.str.strip()
用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
注:该方法只能删除头尾的字符,不能删除中间部分的字符
语法:str.strip([chars])
chars:指定移除的字符串序列
bb = "ninini"
aa = " hahaha "
print(aa+bb)
aa = aa.strip()
print(aa+bb)
cc = "000123456roob000"
print(cc.strip('0'))
结果:
hahaha ninini
hahahaninini
123456roob
11.用python实现创建文件目录
def mkdirectory(pathStr): # 创建文件目录
path = pathStr.strip()
# 判断文件目录是否存在
is_exists = os.path.exists(path)
if not is_exists:
os.makedirs(path)
print("目录创建成功")
if __name__=="__main__":
ss=
mkdirectory()
注意:
os.path.exits(path):判断目录是否存在
os.makedirs(path):多层创建目录
os.mkdir(path):创建目录
os.makedirs(path)和os.mkdir(path)区别:os.makedirs()当父目录不存在时,会创建父目录,os.mkdir()不会创建
12.python基于 zipfile 包实现文件打包功能(原文件层级关系不保留)
包结构:
if __name__ == "__main__":
# 创建压缩包
zip_file = zipfile.ZipFile('/Users/zhonghua/Documents/Project/collector/demo.zip', 'w',compression=zipfile.ZIP_DEFLATED)
base_path = '/Users/zhonghua/Documents/Project/collector/demo/'
for first_name in os.listdir(base_path):
first_path = base_path + first_name
if os.path.isfile(first_path):
zip_file.write(first_path,first_name)
else:
for second_name in os.listdir(first_path):
second_path = first_path+"/"+second_name
if os.path.isfile(second_path):
zip_file.write(second_path,second_name)
结果:
13.python 中反转字符串和list 的方法
- 使用reverse()方法(注意:reverse()只对list有效)
def reverseList_one(s):
s.reverse()
return s
- 利用切片实现倒序输出
def reverseList_two(s):
"""
s[::-1]表示反转s中的元素
s[:]表示数组中所有子模块
:param s:
:return:
"""
s[:] = s[::-1] # list反转
return s
def reverseString_one(s):
s = s[::-1]
return s
if __name__ == "__main__":
test_list = [1, 2, 3]
test_str = "abc"
aa_list = reverseList_one(test_list)
print(aa_list)
bb_list = reverseList_two(test_list)
print(bb_list)
cc_str = reverseString_one(test_str)
print(cc_str)
结果:
aa_list=[3, 2, 1]
bb_list=[1, 2, 3]
cc_str=cba
15.url编码和解码
15.1 编码
当url地址中含有中文或者"/"的时候,这时就需要urlencode进行编码转换。
- parse.urlencode():编码
输入:key-value这样的键值对
输出:编码后的字符串
data = {"name":"张三","age":"/","addr":"abcdef"}
data_one = parse.urlencode(data)
print(data_one)
结果:
name=%E5%BC%A0%E4%B8%89&age=%2F&addr=abcdef
- parse.quote()
输入:字符串
输出:编码后的字符串
data = "hahaha你好啊!"
data_two = parse.quote(data)
print(data_two)
结果:
hahaha%E4%BD%A0%E5%A5%BD%E5%95%8A%EF%BC%81
15.2 解码
- parse.unquote()
输入:编码后的字符串
输出 : 解码后的字符串
data = {"name":"张三","age":"/","addr":"abcdef"}
data_one = parse.urlencode(data)
print(data_one)
data = "hahaha你好啊!"
data_two = parse.quote(data)
print(data_two)
print(parse.unquote(data_one))
print(parse.unquote(data_two))
结果:
name=%E5%BC%A0%E4%B8%89&age=%2F&addr=abcdef
hahaha%E4%BD%A0%E5%A5%BD%E5%95%8A%EF%BC%81
name=张三&age=/&addr=abcdef
hahaha你好啊!
14.求可迭代元素的笛卡尔积,简化for循环
(1)利用itertools.product(*iterables,repeat=1)
(2)若要计算可迭代对象与自身的乘积,使用可选的repeat 关键字参数指定重复次数 eg:product(A,repeat=4) product(A,A,A,A)
a = ["x","y","z"]
b = ["A","B","C","D"]
c = ["1","2","3"]
d = [{"x": 1}, {"y": 2}]
result_one = product(a, b)
print(list(result_one))
result_two = product(a, b, c)
print(list(result_two))
result_three = product(a,repeat=2)
print(list(result_three))
result_four = product(d,repeat=2)
print(list(result_four)))
结果:
result_one=
[('x', 'A'), ('x', 'B'), ('x', 'C'), ('x', 'D'), ('y', 'A'), ('y', 'B'), ('y', 'C'), ('y', 'D'), ('z', 'A'), ('z', 'B'), ('z', 'C'), ('z', 'D')]
result_two=
[('x', 'A', '1'), ('x', 'A', '2'), ('x', 'A', '3'), ('x', 'B', '1'), ('x', 'B', '2'), ('x', 'B', '3'), ('x', 'C', '1'), ('x', 'C', '2'), ('x', 'C', '3'), ('x', 'D', '1'), ('x', 'D', '2'), ('x', 'D', '3'), ('y', 'A', '1'), ('y', 'A', '2'), ('y', 'A', '3'), ('y', 'B', '1'), ('y', 'B', '2'), ('y', 'B', '3'), ('y', 'C', '1'), ('y', 'C', '2'), ('y', 'C', '3'), ('y', 'D', '1'), ('y', 'D', '2'), ('y', 'D', '3'), ('z', 'A', '1'), ('z', 'A', '2'), ('z', 'A', '3'), ('z', 'B', '1'), ('z', 'B', '2'), ('z', 'B', '3'), ('z', 'C', '1'), ('z', 'C', '2'), ('z', 'C', '3'), ('z', 'D', '1'), ('z', 'D', '2'), ('z', 'D', '3')]
result_three
[('x', 'x'), ('x', 'y'), ('x', 'z'), ('y', 'x'), ('y', 'y'), ('y', 'z'), ('z', 'x'), ('z', 'y'), ('z', 'z')]
result_four
[({'x': 1}, {'x': 1}), ({'x': 1}, {'y': 2}), ({'y': 2}, {'x': 1}), ({'y': 2}, {'y': 2})]
15.使用python中的shutil模块,实现文件打包、解压缩(原文件层级关系保留)
- shutil.make_archive(base_name,format[,root_dir[,base_dir[,dry_run):创建一个压缩文件,并返回其名称
base_name | 要创建的文件的名称,包括路径,减去特定于格式的扩展名 |
format | 存档格式,可以为:zip、tar、gztar、bztar、xztar |
root_dir | 是一个目录,它将成为档案的根目录,档案中所有路径都是相对于它的;例如我们通常在创建存档之前chdir进入root_dir |
base_dir | 将是存档中所有文件和目录的公共前缀。base_dir必须相对于root_dir给出。root_dir和base_dir都默认为当前目录 |
dry_run | 如果为真,则不会创建存档,但将执行的操作记录到logger中 |
logger | 通常是logging.Logger |
注:这个函数不是线程安全的 |
archive_name = os.path.expanduser(os.path.join('~', 'myarchive'))
root_dir = os.path.expanduser(os.path.join('~', '.ssh'))
print(archive_name)
print(root_dir)
print(make_archive(archive_name, 'zip', root_dir))
结果:
/Users/zhonghua/myarchive
/Users/zhonghua/.ssh
/Users/zhonghua/myarchive.zip
- shutil.unpack_archive(文件名[,extract_dir[,格式]]):解压缩文件。filename是文件的完整路径
filename | 一个path-like对象,代表归档文件的完整路径。path-like对象是表示路径的字符或字节对象 |
extract_dir(可选) | path-like对象,表示解压存档的目标目录的路径。如果未提供,则使用当前工作目录 |
format(可选) | 是存档格式:zip、tar、gztar、bztar、xzar之一。如果未提供,unpack_archive()将使用存档文件扩展名并查看是否为该扩展名注册了解包程序 |
unpack_archive(filename='/Users/zhonghua/myarchive.zip', extract_dir='/Users/zhonghua/Desktop', format='zip'))
结果:
16.使用shutil.rmtree()删除目录
shutil.rmtree(路径,ignore_errors=False,οnerrοr=None):删除整个目录树(递归地删除文件夹以及里面的文件),path必须指向一个目录(但不是指向目录的符号链接)。如果ignore_errors为真,删除失败导致的错误将被忽略;如果为false或省略,则通过调用由onerror指定的处理程序来处理此类错误,如果省略则引发异常。
如果提供了onerror,它必须是一个接受三个参数的可调用对象:function、path和excinfo。function是引发异常的函数;这取决于平台和实现;path是传递给function的路径名;第三个参数是返回的异常信息。
文件结构:
试图删除test.txt
path = '/Users/zhonghua/Documents/Project/collector/test_dir/test_file/test.txt'
shutil.rmtree(path)
报错:
NotADirectoryError: [Errno 20] Not a directory: '/Users/zhonghua/Documents/Project/collector/test_dir/test_file/test.txt'
试图删除test_file:
path = '/Users/zhonghua/Documents/Project/collector/test_dir/test_file/'(最后有没有/都一样)
shutil.rmtree(path)
试图删除test_dir:
path = '/Users/zhonghua/Documents/Project/collector/test_dir/'
shutil.rmtree(path)
结论:可能想删到哪一层目录,path就写到哪里,但是最低层级是目录不是文件
补充shutil模块的其他函数:
shutil.copyfile( src, dst) #从源src复制到dst中去。 如果当前的dst已存在的话就会被覆盖掉
shutil.move( src, dst) #移动文件或重命名
shutil.copymode( src, dst) #只是会复制其权限其他的东西是不会被复制的
shutil.copystat( src, dst) #复制权限、最后访问时间、最后修改时间
shutil.copy( src, dst) #复制一个文件到一个文件或一个目录
"""
shutil.copy2( src, dst) :在copy上的基础上再复制文件,访问时间与修改时间也复制过来了,
类似于cp –p的东西;如果两个位置的文件系统是一样的话相当于是rename操作,只是改名;如果是不在相同的文件系统的话就是做move操作
"""
"""
shutil.copytree( olddir, newdir, True/Flase):把olddir拷贝一份newdir,如果第3个参数是True,则复制目录时将保持文件夹下的符号连接,如果第3个参数是False,则将在复制的目录下生成物理副本
来替代符号连接
"""
17. os.path.expanduser()
在linux下面,一般如果使用自己的系统的时候,是可以用~代表"/home/你的名字"这个路径的,但是python是不认识~这个符号的,如果你写路径的时候直接写“~/某某”,程序是跑不动的,所以如果要用~,就应该用os…path.expanduser把~展开。
举例:
path = os.path.expanduser('~/Project')
print(path)
结果:
'/home/zhonghua/Project'
18.os.path.join():连接两个或更多的路径名组件
- 如果各组件名首字母不包含’/',函数会自动加上
path1 = "home"
path2 = "zhonghua"
path3 = "project"
path_one = os.path.join(path1,path2,path3)
path_two = path1+path2+path3
print(path_one)
print(path_two)
结果:
home/zhonghua/project
homezhonghuaproject
- 如果有一个组件是绝对路径,则在它之前的所有组件均会被舍弃
path1 = "/home"
path2 = "zhonghua"
path3 = "project"
path_one = os.path.join(path1,path2,path3)
path_two = path1+path2+path3
path_three = os.path.join(path2,path1,path3)
print(path_one)
print(path_two)
print(path_three)
结果:
/home/zhonghua/project
/homezhonghuaproject
/home/project
- 如果最后一个组件为空,则生成的路径以一个‘/’分隔符结尾
path1 = "home"
path2 = "zhonghua"
path3 = ""
path_one = os.path.join(path1,path2,path3)
path_two = path1+path2+path3
print(path_one)
print(path_two)
结果:
home/zhonghua/
homezhonghua
19.创建一个excel,在excel中添加多个sheet
wb = openpyxl.Workbook()
name_list = ["a", "b", "c", "d"]
for name in name_list:
sheet = wb.create_sheet(name)
wb.save('/Users/zhonghua/Documents/Project/collector/demo.xlsx')
结果:
20.将数据写入一个excel的多个sheet里
test_df = {
"所属社区": ["花木社区", "花木社区", "花木社区", "花木社区", "花木社区", "花木社区", "花木社区", "花木社区"],
"所属居委": ["一居委", "一居委", "一居委", "二居委", "三居委", "一居委", "二居委", "三居委"],
"所属小区": ["阳光小区", "阳光小区", "阳光小区", "花园小区", "蓝天小区", "瓜瓜小区", "花园小区", "皮皮居委"],
"所属楼宇": ["1", "2", "2", "2", "3", "4", "4", "3"],
"姓名": ["小明", "牛牛", "小红", "小蓝", "小紫", "小绿", "小黑", "小青"]
}
df = pd.DataFrame(test_df)
groups = df.groupby('所属居委')
wb = openpyxl.Workbook()
for table, fields in groups:
sheet = wb.create_sheet(table)
wb.save('/Users/zhonghua/Documents/Project/collector/test.xlsx')
writer =pd.ExcelWriter('/Users/zhonghua/Documents/Project/collector/test.xlsx', engine='xlsxwriter')
for table, fields in groups:
fields.to_excel(writer,sheet_name=table,index=False)
writer.close()
结果:
21、可迭代对象,迭代器
可迭代对象:顾名思义就是可以从里面迭代取值的对象,在python中容器类的数据结构都是可迭代对象,如列表,字典,集合,元组。
迭代器:类似于从可迭代对象中取值的一类工具,严谨的说可以将可迭代对象中的值取出来的对象。
1.可迭代对象
在Python中容器类的数据结构都是可迭代对象,例如:列表、字典、元组、集合、字符串。
除了python自带的数据结构是可迭代对象外,模块里的方法、自定义的类也可能是可迭代对象。
判断一个对象是否是可迭代对象有一个标准,那就是可迭代对象都有方法__iter__,凡是具有该方法的对象都是可迭代对象。
2.迭代器
常见的迭代器是从可迭代对象创建而来。调用可迭代对象的__iter__方法就可以为该可迭代对象创建其专属迭代器。使用iter()方法也可以创建迭代器,iter()方法本质上就是调用可迭代对象的__iter__方法。
可迭代对象只能通过for循环来遍历,而迭代器除了可以通过for循环来遍历,还可以通过next()方法来迭代出元素,调用一次迭代一个元素,直到所有元素都迭代完,抛出StopIteration错误。
总结迭代器的特征:
- 可使用next()方法迭代取值
- 迭代的过程只能向前不能后退
- 迭代是一次性的,迭代完所有元素就无法再次遍历,需要再次遍历只有新建迭代器。
迭代器在python中很常见,比如打开的文件就是一个迭代器,map,filter,reduce等高阶函数的返回也是迭代器。迭代器对象拥有两个方法:iter__和__next。next()方法能迭代出元素就是调用__next__实现的。
可迭代对象和迭代器的关系
可迭代对象是一个集合,而迭代器就是为这个集合创建的迭代方法。迭代器迭代时是直接从可迭代对象集合取值。
arr=[1,2,3,4,5]
iter_arr = iter(arr)
arr.append(100)
arr.append(200)
arr.append(300)
for i in iter_arr:
print(i)
结果:
1
2
3
4
5
100
200
300
这里的流程是:
- 先创建可迭代对象arr
- 然后从arr创建迭代器arr_iter
- 再向arr列表追加元素
- 最后迭代出出来的元素包括后追加的元素
可以说迭代器并不是copy了可迭代对象的元素,而是引用了可迭代对象的元素。在迭代取值时直接使用了可迭代对象的元素。
可迭代对象和迭代器的工作机制
可迭代对象:对象中有__iter__方法,方法的作用是放回一个迭代器
迭代器:对象中__iter__和__next__方法。__iter__方法的作用是返回一个迭代器,就是自己;next__方法的作用是返回集合中下一个元素。
for循环本质
for循环的本质就是给arr创建一个迭代器,然后不断调用next()方法取出值,复制给变量i,直到没有元素抛出捕获StopIteration异常,退出循环。
简单总结工作机制:
可迭代对象:保存元素,自身无法取值。可以调用自己的__iter__方法创建一个专属迭代器来取值
迭代器:拥有__next__方法,可以从指向的可迭代对象中取值。只能遍历一遍,并且只能前进不能后退。
自己手动创建可迭代对象和迭代器
可迭代对象:实现__iter_方法,功能是调用该方法返回迭代器
迭代器:实现__iter__方法,功能是返回迭代器,也就是自身;实现__next,功能是迭代取值,直到抛出异常。
例如:
# 可迭代对象
class MyArr():
def __init__(self):
self.elements=[1,2,3]
# 返回一个迭代器,并将自己元素的引用传递给迭代器
def __iter__(self):
return MyArrIterator(self.elements)
# 迭代器
class MyArrIterator():
def __init__(self,elements):
self.index=0
self.elements=elements
# 返回self,self就是实例化的对象,也就是调用者自己
def __iter__(self):
return self
# 实现取值
def __next__(self):
# 迭代完所有元素抛出异常
if self.index>len(self.elements):
raise StopIteration
value = self.elements[self.index]
self.index+=1
return value
if __name__=="__main__":
arr= MyArr()
# 返回了迭代器
arr_iter=arr.__iter__()
print(next(arr_iter))
print(next(arr_iter))
print(next(arr_iter))
通过这个例子可以清楚地认识可迭代对象的迭代器的实现。可迭代对象的__iter__方法返回值就是一个实例化的迭代器对象。这个迭代器对象保存了可迭代对象的元素的引用,也实现了取值的方法,所以可以通过next()方法取值。
迭代器的优势
迭代器的优势:提供了一种通用不依赖索引的迭起取值方式
迭代器的设计思路来源于设计模式之迭代模式。迭代模式的思想是:提供一种方法,顺序地访问容器中的元素,而又不需要暴露该对象的内部细节。
迭代模式具体到python的迭代器中就是能够将遍历序列的操作和序列底层相分离,提供一种通用的方法去遍历元素。例如列表、字典、集合、元组、字符串。这些数据结构的底层数据结构都不一样,但同样都可以使用for循环来遍历。正是因为每一种数据结构都可以生成迭代器,都可以通过next()方法迭代,所以在使用时不需要关心元素在底层如何保存,不需要考虑内部细节。
复杂数据结构的通用取值实现
比如我们构造一个复杂的数据结构:{(x,x):value},一个字典,key是元组,value是数字,按照迭代的设计模式,实现通用取值方法。
class MyArrIterator():
def __init__(self):
self.index =1
self.elements={(1,1):100,(2,2):200,(3,3):300}
def __iter__(self):
return self
def __next__(self):
if self.index>len(self.elements):
raise StopIteration
value=self.elements[(self.index,self.index)]
self.index+=1
return value
arr_iter=MyArrIterator()
print(next(arr_iter))
print(next(arr_iter))
print(next(arr_iter))
只要实现了__next__方法就可以通过next()取值,不管数据结构多复杂,__next__屏蔽了底层细节。这是一种比较常见的思想,比如驱动设计,第三方平台介入设计等都是屏蔽差异,提供一个统一的调用方法。
22、 生成器
生成器是一种特殊的迭代器,它既具有迭代器的功能:能够通过next方法迭代处元素,又有自己的特殊之处:节省内存
1.生成器的创建方法
- ()语法,将列表生成式的[]换成()就可以创建生成器
- 使用yield关键字将普通函数变成生成器函数
()语法
>>> gen = (i for i in range(3))
>>> type(gen)
<class 'generator'>
>>> from collections import Iterable,Iterator
>>>
>>> isinstance(gen, Iterable)
True
>>> isinstance(gen, Iterator)
True
>>>
>>> next(gen)
0
>>> next(gen)
1
>>> next(gen)
2
>>> next(gen)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
>>>
yield关键字
yield是python关键字,在函数中使用yield就能将普通函数变成生成器。函数中return 是返回标识,代码执行到return就退出了。而代码执行到yield时也会返回yield后面的变量,但是程序只是暂停在当前的位置,当再次运行程序时会从yield之后的部分开始执行。
from collections import Iterator,Iterable
def fun():
a = 1
yield a
b = 100
yield b
gen_fun = fun()
print(f'是可迭代对象:{isinstance(gen_fun, Iterable)}')
print(f'是迭代器:{isinstance(gen_fun, Iterator)}')
print(next(gen_fun))
print(next(gen_fun))
print(next(gen_fun))
执行第一个next()时,程序通过yield a 返回了1,执行流程就暂停在这里。
执行第二个next()时,程序从上次暂停的地方开始运行,然后通过yeild返回了100,最后退出,程序结束。
yield的魔力就是能够记住执行位置,并且能够从执行位置继续执行下去。
2. 生成器方法
生成器既然是一种特殊的迭代器,查看其是否具有迭代器对象的两种方法?查看两种生成器拥有的方法。
>>> dir(gen)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
>>> dir(gen_fun)
['__class__', '__del__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__name__', '__ne__', '__new__', '__next__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'gi_yieldfrom', 'send', 'throw']
两种生成器都拥有迭代器的__iter__
和__next__
方法。
生成器是特殊的迭代器,要想区分出生成器和迭代器就不能使用collections的Iterator了。可以使用isgenerator方法:
>>> from inspect import isgenerator
>>> arr_gen = (i for i in range(10))
>>> isgenerator(arr_gen)
True
>>>
>>> arr = [i for i in range(10)]
>>> isgenerator(arr)
False
>>>
生成器是一种特殊的迭代器,优势就是:节省内存。生成器,通过生成的方法来支持迭代取值。
节省内存的原理:以遍历列表为例,列表元素按照某种算法推算法出来,就可以在循环的过程中不断推算处后续的元素,这样不必创建完整的列表,从而节省大量的空间。
以实现同样的功能为例,迭代出集合中的元素。集合为:[1,2,3,4]
迭代器的做法:
- 生成一个可迭代对象,列表[1,2,3,4]
- 然后创建迭代器,可迭代对象中通过next()取值
arr = [1,2,3,4]
arr_iter = iter(arr)
next(arr_iter)
next(arr_iter)
next(arr_iter)
next(arr_iter)
生成器的做法:
- 创建一个生成器函数
- 通过next()取值
def fun():
n = 1
while n <= 4:
yield n
n += 1
gen_fun = fun()
print(next(gen_fun))
print(next(gen_fun))
print(next(gen_fun))
print(next(gen_fun))
比较这两种做法,迭代器需要创建一个列表来完成迭代,而生成器只需要一个数字就可以完成迭代。在数据量小的情况下还不能体现这个优势,当数据量巨大时这个优势就能体现的淋漓尽致。比如生成10w个数字,迭代器需要10w个元素的列表,而生成器只需要一个元素。当然可以节省内存。
生成器是一种以时间换空间的做法,迭代器是从已经在内存中创建好的集合中取值,所以消耗内存空间,而生成器只保存一个值,取一次就计算一次,消耗cpu但节省内存空间。
生成器应用场景
- 数据的数据规模巨大,内存消耗严重
- 数列有规律,但是依靠列表推导式描述不出来
- 协程。生成器和协程有着千丝万缕的联系
从结果来看,迭代器不能节省内存,生成器可以节省内存。
总结
可迭代对象
属性:一种容器对象
特点:能够保存元素对象,自己无法实现迭代取值,在外界帮助下可迭代取值
特征:有__iter__方法
迭代器
属性:一种工具对象
特点:可以实现迭代取值,取值的来源是可迭代对象保存的集合
特征:有__iter__和__next__方法
优点:实现通用的迭代方法
生成器
属性:一种函数对象
特点:可以实现迭代取值,只保存一个值,通过计算返回迭代的下一个值。以计算换内存。
特征:有__iter__和__next__方法
优点:拥有迭代器特点同时节省内存
python自带的迭代器工具itertools
迭代器在python中占有重要的位置,所以python内置了迭代器功能模块itertools。itertools中所有的方法都是迭代器,可以使用next()取值。方法主要可以分为三类:
- 无限迭代器 count():创建一个无限的迭代器,类似于无限长度的列表,可以从中取值。
- 有限迭代器 chain(): 可以把多个迭代器对象组合起来,形成一个更大的迭代器
- 组合迭代器 product(): 得到的是可迭代对象的笛卡尔积
23 python的垃圾回收机制(内存垃圾回收:分代回收细节)
python的GC模块主要运用了引用计数器来跟踪和回收垃圾;通过"标记-清除"解决容器对象可能产生的循环引用问题;通过分代回收以空间换时间进一步提高垃圾回收的效率。
也即采用"引用计数"(实时性,一旦没有引用,内存就直接释放了)为主,"标记-清除"与"分代收集"两种机制为辅的策略。
1.引用计数
为每一个对象维护一个引用计数器,当一个对象的引用被创建或复制时,对象的引用计数器+1,当一个对象的引用被销毁时,引用计数器的值-1,当计数器的值为0时,就意味着对象再没有被使用了,可以将其内存释放掉。
2. 标记-清除
"标记-清除"的出现打破了循环引用,也就是它只关注那些可能会产生循环引用的对象,python中的循环引用总是发生在容器Container对象之间,也就是能够在内部持有其他对象的对象(比如:list,dict,class等),这使得该方法带来的开销只依赖于容器对象的数量。
原理:
将集合中对象的引用计数复制一份副本,用于寻找 root object集合(该集合中的对象是不能被回收的)。当成功找到root object集合,首先将现有的内存链表一分为二,一条链表维护root object结合,成为root 链表;另一条维护剩下的对象,成为unreachable链表。
一旦在标记的过程中,发现现在在unreachable链表且可能存在被root链表中直接或间接引用的对象,就将其从unreachable链表移到root链表中;当完成标记后,unreachable链表中剩下的所有对象就是垃圾对象了,接下来的垃圾回收只需限制在unreachable链表中即可。
缺点:该机制所带来的额外操作和需要回收的内存块成正比。
3.分代回收
活的越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。
24 菱形继承
继承是面向对象编程的一个重要方式,通过继承,子类就可以扩展父类的功能。在python中一个类能继承自不止一个父类,这叫做python的多重继承。
语法:
class SubClassName(BaseClass1,BaseClass2,BaseClass3...):
pass
在多层继承和多继承同时使用的情况下,就会出现复杂的继承关系,多重多继承。其中一种叫菱形继承。如图:
在这种结构中的调用顺序为:
class A():
def __init__(self):
print("init A")
print("end A...")
class B(A):
def __init__(self):
print("init B")
A.__init__(self)
print("end B")
class C(A):
def __init__(self):
print("init C")
A.__init__(self)
print("end C")
class D(B,C):
def __init__(self):
print("init D")
B.__init__(self)
C.__init__(self)
print("end D")
if __name__ == "__main__":
D()
结果:
init D
init B
init A
end A...
end B
init C
init A
end A...
end C
end D
从输出结果来看,调用顺序为:D->B->A->C->A。可以看出B、C共同继承于A,A被调用了两次。A没必要重复调用两次。
其实,上面问题的根源都跟MRO有关,MRO(Method Resolution Order)也叫方法解析顺序,主要用于在多重继承时调的属性来源于哪个类,其使用了一种叫C3的算法,其基本思想是在避免被同一个类调用多次的前提下,使用广度优先和从左到右的原则去寻找使用的原则和方法。
那如何避免顶层父类中的某个方法被多次调用呢,此时需要super()来发挥作用,super本质上是一个类,内部记录着MRO信息,由于C3算法确保同一个类只会被搜寻一次,这样就避免了顶层父类中的方法被多次执行,上边的代码可以修改为:
class A():
def __init__(self):
print("init A")
print("end A...")
class B(A):
def __init__(self):
print("init B")
super(B,self).__init__()
print("end B")
class C(A):
def __init__(self):
print("init C")
super(C,self).__init__()
print("end C")
class D(B,C):
def __init__(self):
print("init D")
super(D,self).__init__()
print("end D")
if __name__ == "__main__":
D()
结果:
init D
init B
init C
init A
end A...
end C
end B
end D
可以看出调用顺序是D->B->C->A。即采用的是广度优先的遍历方式。
补充:
Python中的类分为两种,一种叫经典类,一种叫新式类。都支持多继承,但是继承顺序不同。
- 新式类:从object继承来的类。(如:class A(object)),采用广度优先的方式继承(即先水平搜索,再向上搜索)
- 经典类:不从object继承来的类。(如:class A()),采用深度优先搜索的方式来继承
python2.x中的类是有经典类和新式类两种,Python3.x中都是新式类。
25 python的内置函数
1.eval()函数
eval()函数用来执行一个字符串表达式,并返回表达式的值。
语法:
eval(expression[,globals[,locals]])
参数:
- expression:表达式
- globals–变量作用域,全局命名空间,如果被提供,则必须是一个字典对象
- locals–变量作用域,局部命名空间,如果被提供,可以是任何映射对象
返回值:
返回表达式的计算结果
if __name__=="__main__":
x = 7
print(eval('3*7'))
print(eval('pow(2,2)'))
print(eval('2 + 2'))
n = 81
print(eval('n+4'))
结果:
21
4
4
85
2.dir()函数
dir()函数不带参数时,返回当前范围内的变量、方法和定义的类型列表;带参数时,返回参数的属性、方法列表。如果参数包含方法__dir__(),该方法将被调用。如果参数不包含__dir__(),该方法将最大限度的收集参数信息。
语法:
dir([object])
参数:
- object – 对象、变量、类型
返回值:
返回模块的属性列表
print(dir())# 获得当前模块的属性列表
print(dir([]))#查看list的方法
结果:
['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
26 python位运算
- 位与 &
- 位或 |
- 位异或^
- 位取反~
- 左移位 <<
- 右移位 >>
27 collections.Counter
哈希表在python中可以用collections.Counter计数来体现。该方法用于统计序列中每个元素的出现次数,以键值对的方式存在字典中。
import collections
nums = [1,2,3,1,2,1]
counts=collections.Counter(nums)
print("counts:",counts)
print(max(counts))# 3 这里只是求得最大的键值
print(max(counts.keys(),key=counts.get)) # 1 这里是按照key方法求最大
结果:
counts: Counter({1: 3, 2: 2, 3: 1})
凭借这个结构,可以计算出序列中出现次数最多的某个元素。即在得到了counts之后求max即可。但这个max需要给依据索引。
这里max是两个参数,前一个代表max的是什么,也就是要返回最大键,后面的key代表要返回的最大依据是什么,默认是本身,但这里给了key方法,count.get也就是求值。所以该方法的依据是返回一个最大键,但这个最大
28 堆
堆,优先队列有两个重要操作,时间复杂度均是O(logk),以小顶堆为例:
1.上浮sift_up:向堆尾新加入一个元素,堆规模+1,依次向上与父节点比较,小于父节点就交换。
2.下沉 sift_down:从堆顶取出一个元素(堆规模-1,用于堆排序)或者更新堆中一个元素(本题),依次向下与子节点比较,如果大于子节点就交换。
对于topk问题:最大堆求topk小,最小堆求topk大
- topk小:构建在一个k个数的最大堆,当读取的数小于根节点时,替换根节点,重新塑造最大堆。
- topk大:构建一个k个数的最小堆,当读取的数大于根节点时,替换根节点,重新塑造最小堆。
堆排序
这里是大顶堆
- 从后往前非叶子节点下沉,依次向上保证每个子树都是大顶堆,构造大顶堆
- 依次把大顶堆根节点与尾部节点交换(不再维护,堆规模-1),新根节点下沉
class Solution:
def heapSort(arr):
def sift_down(arr,root,k):
"""
如果当前节点值<根节点值,节点下沉
"""
val = arr[root]
while root<<1<k:
child = root<<1
# 选出左右节点中较大的值
if child|1 < k and arr[child|1]>arr[child]:
child |= 1
# 与新节点与父节点比较,如果父节点<孩子及节点
if val < arr[child]:
arr[root] = arr[child]
root = child
else:
break
arr[root]=val
arr =[0]+arr
k = len(arr)
for i in range((k-1)>>1,0,-1):
sift_down(arr,i,k)
# 降序
for i in range(k-1,0,-1):
arr[1],arr[i]=arr[i],arr[1]
sift_down(arr,1,i)
return arr[1:]
if __name__=="__main__":
arr = [6,7,3,4]
print(Solution.heapSort(arr))
29 python创建二维数组
python没有内置的数组类型,只有tuple,list,dict,set等内置数据类型,所以只能通过list模拟数组。
一维数组
a = [3 for i in range(4)]
print("a",a)
b = [3]*4
print("b",b)
结果:
a [3, 3, 3, 3]
b [3, 3, 3, 3]
注:这两种实现方式没有区别,后续可以通过下标来访问,修改其中的值
二维数组
c = [[3 for col in range(4)] for row in range(4)]
print("c",c)
c[1][1]=5
print("changed c",c)
结果:
c [[3, 3, 3, 3], [3, 3, 3, 3], [3, 3, 3, 3], [3, 3, 3, 3]]
changed c [[3, 3, 3, 3], [3, 5, 3, 3], [3, 3, 3, 3], [3, 3, 3, 3]]
注:这里使用两个列表推导式的方式定义了类似二维数组的效果,后续可以通过下标来访问和修改值
下面演示一个错误的二维数组的定义方式
d [[3, 3, 3, 3], [3, 3, 3, 3], [3, 3, 3, 3], [3, 3, 3, 3]]
changed d [[3, 5, 3, 3], [3, 5, 3, 3], [3, 5, 3, 3], [3, 5, 3, 3]]
这里使用两个list的乘法操作进行重复,但是其中的值只是引用拷贝,并不是深拷贝,导致d[1][1]这个操作也影响了d[0]和d[2]
30 深拷贝和浅拷贝
1.深拷贝:拷贝的程度深,自己新拷贝了一块内存,将被拷贝内容全部拷贝过来
2.浅拷贝:拷贝的程度浅,只拷贝原数据的首地址,然后通过原数据的首地址去获取内容
两者优缺点比对:
(1)深拷贝程度高,将原始数据复制到新的内存空间中。拷贝后的内容不影响原始数据内容。但是深拷贝耗时长,且占用内存空间
(2) 浅拷贝拷贝程度低,只复制原数据的地址。其实是将副本的地址指向原数据地址,修改副本内容,是通过当前地址指向原数据地址,去修改。所以修改副本内容会影响原数据内容。但是浅拷贝耗时短,占用内存空间少。
浅拷贝
- 有一层数据类型,且数据类型是可变数据类型,如:列表、字典
结果:地址不一致
a = [1,2,3]
b = copy.copy(a)
print(id(a))
print(id(b))
结果:
2099632419904
2099632420864
- 有一层数据类型,且数据类型是不可变数据类型,例如:元组、字符串
结果:地址一致
a = (1,2,3)
b = copy.copy(a)
print(id(a))
print(id(b))
结果:
1516196391040
1516196391040
- 有两层数据类型,外层为可变数据类型,内层为可变数据类型
结果:外地址改变,内地址不变
a =[1,2]
b =[3,4]
c=[a,b]
d = copy.copy(c)
print("c",c)
print("id(a)",id(a))
print("id(c)",id(c))
print("id(d)",id(d))
print("id(d[0])",id(d[0]))
结果:
c [[1, 2], [3, 4]]
id(a) 2148789859392
id(c) 2148789858688
id(d) 2148789860992
id(d[0]) 2148789859392
- 有两层数据类型,外层为可变数据类型,内层为不可变数据类型
结果:外层地址改变,内层地址不变
a =(1,2)
b =(3,4)
c=[a,b]
d = copy.copy(c)
print("c",c)
print("id(a)",id(a))
print("id(c)",id(c))
print("id(d)",id(d))
print("id(d[0])",id(d[0]))
结果:
c [(1, 2), (3, 4)]
id(a) 2849829498432
id(c) 2849832842304
id(d) 2849832843264
id(d[0]) 2849829498432
31 装饰器
python中的装饰器一般采用语法糖的形式,是一种语法格式。例如:@classmethod,@staticmethod,@property,@ xxx.setter@wraps(),@func_name等都是python中的装饰器。
装饰的对象是函数或者方法。各种装饰器的作用都是一样的:改变被装饰函数或方法的功能,性质
1.官方定义
装饰器本质上是一个python函数(其实就是闭包)他可以让其他函数在不需要任何代码改动的前提下增加额外功能,装饰器的返回值也是一个函数对象。装饰器用于以下场景:插入日志、性能测试、事务处理、缓存、权限校验等场景。
2.给函数加上一个装饰器
(1)一般写法
import threading
import time
def how_long(func):
"""
将新增加的功能代码以及被装饰函数运行代码func()一同打包返回,返回的是一个内部函数,这个被返回的函数就是装饰器
:param func:
:return:
"""
def inner():
t_start=time.time()
func()
t_end = time.time()
print("一共花费了{0}".format(t_end-t_start))
return inner
def sleep_5s():
time.sleep(5)
print("%d秒结束了"%(5,))
def sleep_6s():
time.sleep(6)
print("%d秒结束了"%6)
if __name__=="__main__":
# 因为sleep_5s函数的功能就是睡5s,虽然增加了统计运行时间的功能,但是它本身功能没改变
# 所以仍然用原来的函数名接收增了功能的自己
sleep_5s=how_long(sleep_5s)
sleep_6s = how_long(sleep_6s)
t1=threading.Thread(target=sleep_5s)
t2 =threading.Thread(target=sleep_6s)
t1.start()
t2.start()
(2)标准的语法糖写法
import time
import threading
def how_much_time(func):
print("how_much_time函数好了")
def inner():
t_start=time.time()
func()
t_end = time.time()
print("一共花费了{0}秒时间".format(t_end-t_start,))
return inner
def mylog(func):
print("mylog函数开始了")
def inner_1():
print("start")
func()
print("end")
return inner_1
@mylog
@how_much_time
# 等价于mylog(how_much_time(sleep_5s))
def sleep_5s():
time.sleep(5)
print("%d秒结束了"%(5,))
if __name__=="__main__":
sleep_5s()
结果:
how_much_time函数好了
mylog函数开始了
start
5秒结束了
一共花费了5.014297723770142秒时间
end
执行顺序:
1.先执行how_much_time函数的外部代码
2.执行mylog函数的外部代码
3.执行mylog的内部函数代码
4.执行how_much_time函数的内部代码
3.带参数装饰器的典型写法
#coding=utf-8
"""
带参数的装饰器的典型写法: mylog就是带参数装饰器的典型写法,表现为将decorator装饰器再 进行一次嵌套;
decorator就是一个装饰器。如果这个装饰器需要参数,则需要在外边嵌套一个函数,参数则写在外边函数里
"""
def mylog(type):
def decorator(func):
def infunc(*args,**kwargs):
if type == "文件":
print("文件中:日志记录")
else:
print("控制台:日志记录")
return func(*args,**kwargs)
return infunc
return decorator
# 这就是采用语法糖格式生成的装饰器,参数写在@mylog里面,也就是写在新参加的外层函数里面
@mylog("文件")
def fun2(a,b):
print("使用功能2",a,b)
if __name__=="__main__":
fun2(100,200)
执行结果:
文件中:日志记录
使用功能2 100 200
4.类装饰器
类装饰器的写法,主要思路是返回一个增加了新功能的函数对象,只不过这个函数对象是一个类的实例对象。由于装饰器是可调用对象,所以必须在类里实现__call__方法,这样由类生成的各种实例加上()就可以运行了。
(1)不带参数的类装饰器
import time
class Decorator:
def __init__(self,func):
self.func= func
def defer_time(self):
time.sleep(5)
print("延时结束了")
def __call__(self, *args, **kwargs):
self.defer_time()
self.func()
@Decorator
def f1():
print("延时之后我才开始执行")
f1()
执行结果:
延时结束了
延时之后我才开始执行
(2)带参数的类装饰器
# 带参数的类装饰器
import time
class Decorator:
def __init__(self,func):
self.func=func
def defer_time(self,time_sec):
time.sleep(time_sec)
print(f"{time_sec}s延时结束了")
def __call__(self,time):
self.defer_time(time)
self.func()
@Decorator
def f1():
print("延时之后我才开始执行")
if __name__=="__main__":
f1(5)
执行结果:
延时结束了
延时之后我才开始执行
32 try except else finally
try:把可能出现的异常代码放进try中,代表异常处理即将要处理的代码段
except xxx:捕获异常,xx表示异常类型,如果你知道代码会报出什么异常,可以直接把具体异常类型填上。执行过程中,出现了xxx异常,那么该段代码就会执行
else:当try段代码能够正常执行,没有异常出现的情况下,会执行else段代码
finally:不管有没有异常,最终都会被执行
a =3
try:
print(a)
except NameError:
print("未定义异常")
else:
print("正常")
finally:
print("结束")
32 python怎样进行内存管理
从三个方面来书,主要有:对象的引用计数机制、垃圾回收机制、内存池机制
- 1.对象的引用技术机制
python内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用计数
引用计数增加的情况:
(1)一个对象分配一个新名称
(2) 将其放入一个容器中(如:列表、元组)
引用计数减少的情况:
(1)使用del语句对对象的别名显式的销毁
(2)引用超出作用域或被重新赋值
sys.getrefcount()函数可以获得对象的档期那引用计数。多数情况下,引用计数比你猜测的要大得多。对于不可变数据(如数字和字符串),解释器会在程序的不同部分共享内存,以便节约内存。 - 2.垃圾回收
(1)一个对象的引用计数为0时,它将被垃圾收集机制处理掉
(2)当两个对象a和b相互引用时,del语句可以减少a和b的引用计数,并销毁用于引用底层对象的名称。然而由于每个对象都包含一个对其他对象的应用,因此引用计数不会归零,对象也不会销毁。(从而导致内存泄漏)。为解决这一问题,解释器会定期执行一个循环解释器,搜索不可访问对象的循环并删除他们。 - 内存池机制
python提供了对内存的垃圾收集机制,但是它将不用的内存放到内存池而不是返回给操作系统。
(1)pymalloc机制。为了加速python的执行效率,python引入了一个内存池机制,用于管理对小块内存的申请和释放。
(2)python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的malloc。
33.python import 执行流程
在python中使用import语句导入模块时,python通过三个步骤来完成这个行为:
1.在python模块加载路径中查找相应的模块文件
2.将模块文件编译成中间代码
3.执行模块中的代码
34.cookie和session的区别
- 数据放置的位置不同
cookie数据存放在客户的浏览器上,session数据放在服务器上 - 安全程度不同
cookie不是很安全,别人可以分析放置到本地的cookie并进行cookie欺骗,考虑到安全应该使用session - 数据存储大小不同
单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie,而session则存储于服务器,浏览器对其没有限制。 - 性能使用程度不同
session会在一定时间内保存在服务器上。当访问增多时,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie.
34.python 多线程
多线程类似于同时执行多个不同程序,多线程运行有如下优点:
- 使用线程可以把占据长时间的数据中的任务放到后台去处理
- 程序的运行速度可能更快
- 用户界面可以更加吸引人,比如用户点击了一个按钮,去触发某些事件的处理,可以弹出一个进度条来显式处理的速度。
- 在一些等待的任务上实现如用户输入、文件读写、网络收发数据等,线程就比较有用了,在这种情况下我们的可以释放一些珍贵的资源如内存占用等等。
每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存
- 线程可以被抢占(中断)
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠)–这就是线程的退让
线程的分类:
- 内核线程:由操作系统内核创建和撤销
- 用户线程:不需要内核支持而在用户程序中实现的线程
python3线程中常使用的两个模块:
- _thread
- threading
thread模块已经被废弃。用户可以使用threading模块代替。所以在python3中不能再使用thread模块,为了兼容性,python3将thread重命名为_thread
python中使用线程有两种方式:函数或者用类来包装线程对象
python 中的常见线程方法
threading.currentThread() | 返回当前的线程变量 |
threading.enumerate() | 返回一个包含正在运行的线程的list。正在运行线程启动后、结束前,不包括启动前和终止后的线程 |
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate()) 有相同的结果 |
run() | 用来表示线程活动的方法 |
start() | 启动线程活动 |
join([time]) | 等待至线程终止。这阻塞调用线程直到线程的join()方法被调用终止-正常退出或者抛出未处理的异常,或者是可选的超时发生 |
isAlive() | 返回线程是否活动的 |
getName() | 返回线程名 |
setName() | 设置线程名 |
1.使用threading模块创建线程
可以通过直接从threading.Thread继承创建一个新的类,并实例化后调用start()方法启动新线程,即它调用了线程的run()方法。
33.进程、线程、协程
进程:是应用程序的一次动态执行过程。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,进程可以理解为一个程序的基本边界。
线程:是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在程序入口执行的第一个线程被视为这个进程的主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器TLS组成的,CPU寄存器主要记录当前执行线程的状态,调用栈主要用于维护线程调用所用到的内存与数据,TLS主要用于存放线程的状态信息。
线程不是计算机硬件的功能,而是操作系统提供的一种逻辑功能,线程本质上是进程中一段并发运行的代码,所以线程需要操作系统投入CPU资源来运行和调度。
进程和线程的主要区别:
- 进程和线程的主要区别在于它们是不同的操作系统资源管理方式。进程有独立 的地址空间、一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同实行路径。
- 线程有自己的堆栈和局部变量,但线程之间没有独立的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程
- 一个程序至少一个进程,一个进程至少一个线程
- 线程的划分尺度小于进程,这使得多线程程序的并发性高
34.同步和异步
同步:就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后面的操作。
简单来说:同步就是必须一件一件事做,等前一件做完了才能进行下一件。
异步:当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续的操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
通知调用者的三种方式:
- 状态:监听被调用者的状态(轮询),
- 通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能
- 回调:与通知类似,当被调用者执行完成后,会通知调用者提供的回调函数。
同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
python json json.dumps(),json.loads(),json.dump(),json.load()
1.json.dumps()和json.loads()是json格式处理函数
json.dumps()是将一个python数据类型进行json格式的编码(将字典转换为字符串)
json.loads()是将json格式数据转换为字典(将字符串转换为字典)