python常用语法点

目录

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 的方法

  1. 使用reverse()方法(注意:reverse()只对list有效)
def reverseList_one(s):
  s.reverse()
  return s
  1. 利用切片实现倒序输出
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模块,实现文件打包、解压缩(原文件层级关系保留)

  1. 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

在这里插入图片描述

  1. 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():连接两个或更多的路径名组件

  1. 如果各组件名首字母不包含’/',函数会自动加上
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
  1. 如果有一个组件是绝对路径,则在它之前的所有组件均会被舍弃
 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
  1. 如果最后一个组件为空,则生成的路径以一个‘/’分隔符结尾
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错误。
总结迭代器的特征

  1. 可使用next()方法迭代取值
  2. 迭代的过程只能向前不能后退
  3. 迭代是一次性的,迭代完所有元素就无法再次遍历,需要再次遍历只有新建迭代器。
    迭代器在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

这里的流程是:

  1. 先创建可迭代对象arr
  2. 然后从arr创建迭代器arr_iter
  3. 再向arr列表追加元素
  4. 最后迭代出出来的元素包括后追加的元素

可以说迭代器并不是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. 生成一个可迭代对象,列表[1,2,3,4]
  2. 然后创建迭代器,可迭代对象中通过next()取值
arr = [1,2,3,4]
arr_iter = iter(arr)
next(arr_iter)
next(arr_iter)
next(arr_iter)
next(arr_iter)

生成器的做法:

  1. 创建一个生成器函数
  2. 通过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位运算

  1. 位与 &
  2. 位或 |
  3. 位异或^
  4. 位取反~
  5. 左移位 <<
  6. 右移位 >>

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. 从后往前非叶子节点下沉,依次向上保证每个子树都是大顶堆,构造大顶堆
  2. 依次把大顶堆根节点与尾部节点交换(不再维护,堆规模-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的区别

  1. 数据放置的位置不同
    cookie数据存放在客户的浏览器上,session数据放在服务器上
  2. 安全程度不同
    cookie不是很安全,别人可以分析放置到本地的cookie并进行cookie欺骗,考虑到安全应该使用session
  3. 数据存储大小不同
    单个cookie保存的数据不能超过4k,很多浏览器都限制一个站点最多保存20个cookie,而session则存储于服务器,浏览器对其没有限制。
  4. 性能使用程度不同
    session会在一定时间内保存在服务器上。当访问增多时,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie.

34.python 多线程

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

  • 使用线程可以把占据长时间的数据中的任务放到后台去处理
  • 程序的运行速度可能更快
  • 用户界面可以更加吸引人,比如用户点击了一个按钮,去触发某些事件的处理,可以弹出一个进度条来显式处理的速度。
  • 在一些等待的任务上实现如用户输入、文件读写、网络收发数据等,线程就比较有用了,在这种情况下我们的可以释放一些珍贵的资源如内存占用等等。

每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
每个线程都有自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
指令指针和堆栈指针寄存器是线程上下文中两个重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存

  • 线程可以被抢占(中断)
  • 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠)–这就是线程的退让

线程的分类:

  1. 内核线程:由操作系统内核创建和撤销
  2. 用户线程:不需要内核支持而在用户程序中实现的线程

python3线程中常使用的两个模块:

  1. _thread
  2. 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资源来运行和调度。
进程和线程的主要区别:

  1. 进程和线程的主要区别在于它们是不同的操作系统资源管理方式。进程有独立 的地址空间、一个进程崩溃后,在保护模式下不会对其他进程产生影响,而线程只是一个进程中的不同实行路径。
  2. 线程有自己的堆栈和局部变量,但线程之间没有独立的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程
  3. 一个程序至少一个进程,一个进程至少一个线程
  4. 线程的划分尺度小于进程,这使得多线程程序的并发性高

34.同步和异步

同步:就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后面的操作。
简单来说:同步就是必须一件一件事做,等前一件做完了才能进行下一件。
异步:当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续的操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。
通知调用者的三种方式:

  1. 状态:监听被调用者的状态(轮询),
  2. 通知:当被调用者执行完成后,发出通知告知调用者,无需消耗太多性能
  3. 回调:与通知类似,当被调用者执行完成后,会通知调用者提供的回调函数。
    同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。

python json json.dumps(),json.loads(),json.dump(),json.load()

1.json.dumps()和json.loads()是json格式处理函数
json.dumps()是将一个python数据类型进行json格式的编码(将字典转换为字符串)
json.loads()是将json格式数据转换为字典(将字符串转换为字典)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值