Python 复习

;python是一个解释型语言

为了提高运行速度,使用了编译方法,编译之后得到的pyc文件,存储了字节码(特定于python的表现形式,不是机器码)

源代码--》编译--》字节码--》解释--》 机器码

注意一: 第一次运行程序生成字节码(pyc)

                以后运行直接从字节码到机器码(速度更快)   

注意二: 主模块(第一次执行的模块)不会生成字节码

解释器类型

        CPython :C语言开发

        Jython: java开发

        IroPython: .net开发

Python语言内存垃圾管理机制(GC):

        1.引用计数:

                原理:每个对象记录被变量绑定(引用)的数量,当为0 时被销毁

                缺点: 存在循环引用的情况,如下案例

lis = []
lis2 = []
# lis的列表中存在lis2的引用
lis.append(lis2)
# lis2的列表中存在lis的引用
lis2.append(lis)
# 删除了lis,lis2 
del lis, lis2
问题: 删除了变量引用,在内存中列表被销毁了吗?
答: 内存中没有被销毁,因为两个列表之间地址相互引用,所以实际在内存中没有被销毁,这两个垃圾长期占用内存,不能释放

        2.标记清除:

                原理:在内存不够用的时候,对内存空间全盘扫描,检查不在使用的数据(检查引用计数不小于0 的数据)

                可以理解扫描内存的栈区,由栈区作为触发点,扫描堆区数据,对有引用的数据激活,对无引用的数据标记失活在清理,和引用计数想配合

               缺点:速度太慢,需要全盘扫描

        3.分代回收:

                原理:基于“对象或变量存在时间越长,越不可能是垃圾,应该越少的去收集”的假设,产生分代回收,分代回收是一种以空间换时间的操作方式

        将所有对象分成三代(年轻/0代、中年/1代、老年/2代),所有新创建的对象变量默认为0代,当某一个对象经历过垃圾回收依旧存活,则划分为下一代,垃圾回收启动时,会扫描所有的 第0代对象。如果 第0代经过一定次数垃圾回收,那么就触发对0代和1代的扫描清理。当第1代也经历了一定次数的垃圾回收后,那么会触发对 第0,1,2代,即对所有对象进行扫描

        4.优化内存(了解以上内存知识后):

                1.尽少的产生垃圾代码

                2.内置对象池:int/float/bool/str

                        原理:每次创建数据时,都先在池中判断,是否具有相同数据,如果有直接返回地址,没有再开辟空间

                        优点:相同数据在内存中存储一份,从而节省内存   

li = [10]
li2 = [10]
# 两个列表的地址不同
print(id(li)) #2741174782976 
print(id(li2)) # 2741174782592
# 但是相同的数据的引用地址是相同的
print(id(li[0])) # 2741167942224
print(id(li2[0]))# 2741167942224

可以理解为:

class 自定义对象池:
    def __init__(self):
        self.对象池 = 容器
    def 创建数据(self):
        先判断容器中是否存在可用数据
        如果可用直接返回数据地址
        从对象池中删除该数据

         5.垃圾回收的方式

              在以下三种情况下,会触发垃圾回收机制:

                1.自动回收:当gc模块的计数器达到阀值的时候,触发自动回收。

在Python中默认开启自动回收,其中涉及方法如下:

gc.enable:开启垃圾回收机制(默认开启)。
gc.disable:关闭垃圾回收机制。
gc.isenabled:判定是否开启

               2.手动回收:使用gc模块中的collect 方法。使用 gc.collect 方法,手动执行分代回收机制

        

 上面例子中test、ops为循环引用,通过 gc.collect 手动回收垃圾,实现了回收的两个test、ops的对象。此外,gc.collect 方法返回此次垃圾回收的unreachable(不可达)对象个数,上面例子中回收的两个都是unreachable对象,即清除 我们在标记-清除机制中提到的unreachable 链表中的对象

               3. 程序退出

软件编程的三大范式:

1.面向过程

2.面向对象

3.函数式编程

Python 版本

建议安装3.7版本

查看版本号: python -V或python --version 

注意:python -v 不是查看版本号, 需要退出使用  exit()

运行python文件的两种方式:

        1.使用pychar 软件来运行

        2.使用黑窗口运行

                2-1.找到文件的路径

                2-2:cd 文件路径

                2-3:python  文件名称

查看项目解释器

新建python页面给与声明

 设置系统编码utf-8

Python 环境安装(解释器)

下载地址:Download Python | Python.org

变量命名习惯  

习惯一 :
                都用小写字母,单词和单词之间用下划线连接
习惯二小驼峰 :
                第二个单词首字母大写
习惯三大驼峰
                所有单词首字母大写

python 数据类型

        1.数字型

                1.整数int

                2.浮点数 float

                3. 布尔类型  bool   =》  true 真,可以表示1,false 假 可以代表0

                4.复数 complex

        2.非数字型

                1.字符串 string

                 2.列表 list

                3.元组 tuple

                4.集合 set

                5.字典 dictionary

        3.None

                1.首字符是大写的N

                2.空

 python中所有数据都可以转化为布尔值:非零为True

len():获取数据长度

None:表示空

type():查看数据类型

id():查看内存地址

字符串的格式化

1.传统的格式化

        %s   表示一个字符串的占位符

        %d   表示式一个整数的占位符

        %f   表示一个小数的占位符

 2.format方法

3.字符串的 f 表达式(f大小写都可以)

4.保留数值的位数

5. 格式化字符串的长度

传统的格式字符串:

a ='张三'
b = '李四'
c = 12
d = 15
print( '%s是一个孩子,今年%d,%s也是一个孩子,今年%s' % (a,c,b,d) )

==》张三是一个孩子,今年12,李四也是一个孩子,今年15

#还可以使用format方法

print('{}是一个孩子,今年{},{}也是一个孩子,今年{}'.format(a,c,b,d))
==》张三是一个孩子,今年12,李四也是一个孩子,今年15

# 字符串的表达式

name = '张三'
more = 780
print(f'{name}今天来交学费,共交了{more}元,没有拿收据')

==》 张三今天来交学费,共交了780元,没有拿收据


# 传入的数值保留2位

more_money= 3.141589

print('圆周率是{:.2f},保留2位'.format(more_money))


# 传入的数值已百分比显示
print('接口执行显示的通过率是 {}'.format(400/500))  # 接口执行显示的通过率是 0.8
print('接口执行显示的通过率是 {:.2%}'.format(400/500))  # 接口执行显示的通过率是 80.00%
print('分数通过率为{:.0%}'.format(0.2)) #数通过率为20%


# 格式化字符串的长度
# :^20  预留20个字符长度,如果字符不够已空格填充,字符居中显示
# :*<20  预留20个字符长度,如果字符不够已星花填充,字符左对齐显示
# :>20  预留20个字符长度,如果字符不够已空格填充,字符右对齐显示
print('今天什么七七{:^20}'.format('skcssjvv'))
print('今天什么七七{:*<20}'.format('skcs'))
print('今天什么七七{:&>20}'.format('skcs sljjljljpiqh92u21ssjvv'))

==》
今天什么七七      skcssjvv      
今天什么七七skcs****************
今天什么七七skcs sljjljljpiqh92u21ssjvv

6.字符串的索引和切片(相当于浅拷贝)

索引取值: 结果 = 数据[索引值]
正向索引:从0 开始
反向索引:从-1开始

切片:结果 = 数据[起始位置:终止位置]

注意:切片取头不取尾


案例:去除python
a = 'java python php'
s1 = a[5:11]
print(s1)  =》 python


切片还可以加步长(按步长进行分割,在取每个数据的第一个值)
b = 'abcdefghijklmnopqrstuvwsyz'
s2 = b[1:6:2]
print(s2) =》bdf

字符串常用的方法

format:格式化方法

replace(参数1,参数2,参数3):替换方法(‘需要替换的内容’,‘替换后的内容’,替换的次数-默认全部)只能传入字符串

find:查找字符串中指定字符的起始位置(多个的只返回第一个,如果没找到返回-1)

# find方法:find("需要查找的字符","起始位置") 起始位置默认从0开始
syu = "ahakjaladf"
print(syu.find("a",3))  #  5


str1 = """
我生喜际招提客
永巷春深帘幕幽
1
"""
print(str1.find("提"))  # 6

#  这里下标为6 ,是因为多行造成的,需要每行下标+1

index('元素')::作用同上,如果没找到报错

str = 'abdjshds'
s =str.find('e')
print(s)  #  不存在返回 -1,存在返回下标
s2 = str.index('q')
print(s2)  # 报错

join: 字符串拼接

# 字符串拼接
a = 'java'
b = 'python'
c =  'php'
res = ' '.join((a,b,c))
print(res)   ==》java python php

join中的参数必须是一个可迭代的元素(列表,元组,字符串,字典(只会拼接key)和集合),
可迭代元素中的数据类型是str,例如:  ["1","2","3"]   如果是数据类型则报错

nums=[1,2,3.6]
numsStr=''.join(str(e) for e in nums)
print(numsStr)

split('分隔符'): 把一个元素以分隔符分割成列表,列表中的每个数据都是字符串

s= '123pp456pp789pp012'
res = s.split('pp')
print(res) ==》 ['123', '456', '789', '012']

strip():去除元素的前后的空白字符(还可以去除指定的字符,参数是字符串)

lstrip():去除左边的字符

rstrip():去除右边 的字符

upper(): 将字母大写

abs = 'abcdefg'
res1 =abs.upper()
print(res1)  =>ABCDEFG

len(): 获取长度,可以获取字符串,列表,元组,字典

endswith():以什么结尾,如果字符串含有指定的后缀返回True,否则返回False

s ='aaa'
print(s.endswith('a'))  # True
print(s.endswith('b')) # False

 startswith: 以什么开头,原理同上

title():单词首字母大写

x = "alex is like"
print(x.title())# Alex Is Like

partition(): 以字符串中的指定元素分割字符串,把lstr以str分割成三部分,str前,str和str后

s ='where is you name'
print(s.partition('you'))
#('while is ', 'you', ' name')
print(s.isalnum()) # 是否由字母或数字组成
print(s2.isalpha())# 是否由字母组成
print(s3.isdigit()) # 是否由数字组成
num1 = b"4" #bytes
num2 = u"4"# py3中无需加u,u表示unicode
num3 = "四"
num4 = "Ⅳ" # 罗马数字 4

# 判断是否是数字有两种方式,看情况使用
print(num1.isdigit()) # true
print(num2.isdigit())# true
print(num3.isdigit())#true
print(num4.isdigit())#true
# 第二种
print(num2.isnumeric()) # true
print(num3.isnumeric()) # true
print(num4.isnumeric()) # true
def is_Chinese(ch):
    '''
    判断是否中文
    :param ch: 单个文字
    :return:
    '''
    if '\u4e00' <= ch <= '\u9fff':
            return True
    return False

lower(): 将字母小写 

abs = 'ABCDEFG'
res1 =abs.lower()
print(res1)  ==> abcdefg

 

判断和查看数据的类型

        type('数据'):返回数据的类型

        isinstance('数据','类型'):判断数据是否是这个类型的,例如: isinstance(1,int)

isinstance 和 type 的区别在于:

  • type()不会认为子类是一种父类类型。
  • isinstance()会认为子类是一种父类类型
     

作用域:


        local 局部作用域:函数内部

        nonlocal 外部嵌套作用域:函数嵌套

        全局作用域 Global: .py文件内部

        内置作用域:builtins.py文件

list 列表 :有序、可重复、可变

可变序列容器

机制: 预留空间+ 自动扩容(缺点:生成内存垃圾)

        1.开辟空间

        2.拷贝数据(拷贝数据的地址)

        3.替换内存地址

# 列表:list []
# 切片
info = ['张三','里斯','王五','河流']
res = info[2:]
print(res) # ==> ['王五', '河流']  切片的出来的是一个单独的列表,和原列表没关系

print(info[2])  # =》 索引单独取值

 增加list数据:

append(元素):向列表结尾处插入一个元素(append添加数据的时候是增加引用/地址,而不是数据)

list1 = ['zhangsan']
a = [1,2,3]
list1.append(a)  # 列表list1添加列表a
print(list1)
a.append(4)  # 列表a发生变化
list1.append(a)
print(list1)  # 列表list1也会同步变化

['zhangsan', [1, 2, 3]]
['zhangsan', [1, 2, 3, 4], [1, 2, 3, 4]]

insert(索引,元素):向列表指定位置插入(注意:当传入反向下标的时候,insert会先转换为正向下标,然后在插入

extent(‘可迭代对象’):一次性向尾部插入多个:(如果是字符串,会拆分每一个字符,当元素插入)

注意:可迭代对象==》 字符串string,列表list,元组tuple,字典dict,集合set

lis = [1,2,3]
lis.append(6)
print(lis)

lis.insert(3,'张三')
print(lis)   =》  [1, 2, 3, '张三', 6]

list3 = [1,2,3]
list3.insert(-1,7)
print(list3) # [1, 2, 7, 3]
#  注意:当传入反向下标的时候,insert会先转换为正向下标(这里会成为2),然后在插入
 
lis.extend(['a','b','c'])
print(lis)  ==》   [1, 2, 3, '张三', 6, 'a', 'b', 'c']

删除list数据: 

remove():删除指定元素,有多个同样元素只删除第一个

pop():删除指定索引的元素(不传默认删除最后一个)

clear():清空所有元素

st = [12,3,4,5,65,6,87,8,9,10]
#st.clear()  # 清空列表中的数据
# 切片也可以清空数据
st[::] = []

修改list数据

        根据索引修改,没有现成的方法

lisol = [1,2,3]
lisol[0]='张三'
print(lisol)  # ==>['张三', 2, 3]

查找list数据

1.索引取值

2.通过值获取对应的索引:index('元素'),如果没找到则报错

lisol = [1,2,3]
lisol[0]='张三'
print(lisol)  # ==>['张三', 2, 3]
res= lisol.index(2)
print(res) # ==> 1

3.统计列表中某个元素的个数

liso2 = [1,2,3,123,1,3,4]
r = liso2.count(1)
print(r) # => 2

list的其他方法

1.复制:copy()

st = [12,3,4,5,65,6,87,8,9,10]
st2 = st.copy()
# 下面这两种也是复制
num = [12,3,6]
bumc =num[:]   #复制num给到bumc
cumd = list(num) #复制num 给到cumd

2.排序:sort()  只针对数字,默认是从小到大

lit = [1,45,67,34,9,7,0,3435]
# 升序 从小到大
lit.sort()
print(lit)  # [0, 1, 7, 9, 34, 45, 67, 3435]
# 降序 从大到小
lit.sort(reverse=True)
print(lit)  # [3435, 67, 45, 34, 9, 7, 1, 0]

2.1 排序 sorted 

>>> name_mark_age = [('zhangsan','A',15),('LISI','B',14),('WANGWU','A',16)]
>>> sorted(name_mark_age, key = lambda x: x[2])     #根据年龄排序
[('LISI', 'B', 14), ('zhangsan', 'A', 15), ('WANGWU', 'A', 16)]
 
>>> sorted(name_mark_age, key = lambda x: x[1])     #根据等级排序
[('zhangsan', 'A', 15), ('WANGWU', 'A', 16), ('LISI', 'B', 14)]
 
>>> sorted(name_mark_age, key = lambda x: x[0])     #根据姓名排序
[('LISI', 'B', 14), ('WANGWU', 'A', 16), ('zhangsan', 'A', 15)]

2.2 python中还提供了一个选择循环选择指定元组值的模块

>>> from operator import itemgetter    
 
>>> name_mark_age
[('zhangsan', 'A', 15), ('LISI', 'B', 14), ('WANGWU', 'A', 16), ('zhaoliu', 'B', 16)]
 
>>> sorted(name_mark_age,key=itemgetter(2))     #按照年龄排序
[('LISI', 'B', 14), ('zhangsan', 'A', 15), ('WANGWU', 'A', 16), ('zhaoliu', 'B', 16)]
 
>>> sorted(name_mark_age,key=itemgetter(1,2))   #先按照等级排序,相同等级看年龄
[('zhangsan', 'A', 15), ('WANGWU', 'A', 16), ('LISI', 'B', 14), ('zhaoliu', 'B', 16)]
from operator import itemgetter

stu = [
    ('john', 'A', 15),
    ('jane', 'B', 12),
    ('dave', 'B', 10),
    ('ahd', 'C', 10),
    ('davsae', 'D', 10),
]
# 使用operator模块来进行排序(按年龄进行排序,如果年龄相同则按照成绩)
lis = sorted(stu, key=itemgetter(2,1))
#[('dave', 'B', 10), ('ahd', 'C', 10), ('davsae', 'D', 10),
# ('jane', 'B', 12), ('john', 'A', 15)]
print(lis)


#sorted默认两个值排序时:如果年龄相同,按成绩排序时,成绩会按照升序排列
lis2 = sorted(stu,key=lambda x:(x[2],x[1]))
#[('dave', 'B', 10), ('ahd', 'C', 10), ('davsae', 'D', 10),
# ('jane', 'B', 12), ('john', 'A', 15)]
print(lis2)

#sorted默认两个值排序时:如果年龄相同,按成绩排序时,成绩会按照升序排列,如果需要降序,则增加一个reverse=True
lis3 = sorted(stu,key=lambda x:(x[2],x[1]),reverse=True)
# [('john', 'A', 15), ('jane', 'B', 12), ('davsae', 'D', 10),
# ('ahd', 'C', 10), ('dave', 'B', 10)]
print(lis3)

3.reverse():翻转==》需求:将列表翻转

lis3 = ['账单','李四','王五','合流','宋七']
# 需求:将列表翻转
# 方式1:使用reverse()
lis3.reverse()  # =>['宋七', '合流', '王五', '李四', '账单']
print(lis3)
# 方式2:使用切片来操作
lis3 = lis3[::-1]
print(lis3) # =>['账单', '李四', '王五', '合流', '宋七']

4.enumerate():展示索引位置和对应的值

lis =["涨价","降价","商品"]
for i,v in enumerate(lis):
	print(i, v)
0 涨价
1 降价
2 商品

sort和sorted的区别:

1.sort是list自带的方法,只能在list中使用===iterable.sort(key, reverse)

2.sort直接改变源列表数据,sorted会返回一个新对象

3.sorted是python的内置函数,可以针对可迭代对象进行排序 sorted(iterable, key, reverse)

4.sorted返回一个新的列表,不会改变源数据

5.sorted如果在排dict时,会按key来排序,同时将key返回一个列表

dic2 = {1: 'ab', 2: 'degg', 4: 'ght', 9: 'd'}
# 默认返回字典的键列表,升序
ss = sorted(dic2)
print(ss)  # [1, 2, 4, 9]
# 按value的长度来排序
# items():返回一个列表,每个元素是key和value组成的元组
# [(9, 'd'), (1, 'ab'), (4, 'ght'), (2, 'degg')]
sst = sorted(dic2.items(), key=lambda x: len(x[1]))
print(sst)

 ​ 列表推导式--用于生成简单批量的数据

        根据可迭代对象,简单的构建新列表,就可以考虑使用列表推导式

            三要素:  可迭代对象、 简单、新列表

# 列表推导式--》一般用于生成简单的有规律的数据
# lis = ['添加到列表的数据' for i in 可迭代对象 if 条件]

lis = ['info{}'.format(i) for i in range(10,16)]
print(lis)  
#=>['info10', 'info11', 'info12', 'info13', 'info14', 'info15']

拷贝

        1.浅拷贝

                切片就是浅拷贝:只复制一层数据,共享深层数据的过程

                 list.copy():浅拷贝

                优缺点: 语法简单,适用好,占用内存少,但是如果有深层数据不能复制,会被修改,相互影响

        修改li2的值影响lis的值

lis = [1, 2, [3, 4, [5, 6]]]
lis2 = lis[:]
print("lis2:",lis2)  # [1, 2, [3, 4, [5, 6]]]
lis2[2][1] = "python"
print("lis:",lis)  #  lis: [1, 2, [3, 'python', [5, 6]]]
print("lis2:",lis2) # lis2: [1, 2, [3, 'python', [5, 6]]]

        2.深拷贝:将所有指向的数据都拷贝一份

              优缺点:绝不相互影响,但是占用内存较多,适用于深层数据需要被修改时

import  copy
lis = [1, 2, [3, 4, [5, 6]]]
lis2 = copy.deepcopy(lis)
print("lis2:",lis2)  #  lis2: [1, 2, [3, 4, [5, 6]]]
lis2[2][1] = "python"
print("lis:",lis)   # lis: [1, 2, [3, 4, [5, 6]]]
print("lis2:",lis2) # lis2: [1, 2, [3, 'python', [5, 6]]]

列表也可以转换为字典

        列表中的每个元素,必须是能一分为二的,否则会报错

lis=["张三","李四","王五"]
print(dict(lis))
#  {'张': '三', '李': '四', '王': '五'}

lis=["张三1","李四","王五"]
print(dict(lis))
# ValueError: dictionary update sequence element #0 has length 3; 2 is required

下面这种:表示每个数据必须是可以被一分为二的,如果不能可以用容器包裹

lis=[("张三","强"),["李","四狗"],"王五"]
print(dict(lis))  #{'张三': '强', '李': '四狗', '王': '五'}

字典也可以转换为列表

# 字典变成列表
dic = {"name":"张三","age":19,"gender":"男"}
print(list(dic)) #  ['name', 'age', 'gender']
print(list(dic.values())) # ['张三', 19, '男']
print(list(dic.items())) # [('name', '张三'), ('age', 19), ('gender', '男')]

tuple元组:有序、可重复、不可变

特征: 按需分配

1.定义:使用小括号表示 (),

        li = [1,2,3]  列表

        tu = (1,2,3) 元组

2.元组中可以存放任何类型的数据

3.元素中的元素是有序的,支持索引及切片操作

4.元组中的元素是不可变的(定义了就不能增加,删除,修改)

方法:

        count()

         index()

字符串、列表、元组总结

        1.都是序列类型

        2.序列类型有一些共同特征:

                1.都有索引(元素都有序)

                2.支持切片

        3.支持 len()  获取长度

内置函数:

        int(),bool(),float()

序列类型的内置函数:str(),list(),tuple()

列表和元组的优缺点:

        主要不同在于:列表可以修改,元组不可修改,一般存储不变的值

# 问题:用户输入几月几号,输出当前是年度的多少天
user_data =(31,29,31,30,31,30,31,31,30,31,30,31)
month = int(input("请输入月份:"))
date = int(input("请输入几号:"))
count=date
for i in user_data[:month-1:]:
    count+=i

print(count)

类型转换

eval:识别字符串中的有效表达式,简单说就是去除两边的引号,如果是一个无效的表达式就会报错

# 列表转换为字符串
s = [1,2,3,4,5]
s1 = str(s)
print(s1,type(s1)) #===>[1, 2, 3, 4, 5] <class 'str'>

# 字符串转换为列表
v = '[1,2,3,4,5,6]'
v1 = eval(v)
print(v1,type(v1))  #==>[1, 2, 3, 4, 5, 6] <class 'list'>


forzenset(s)	:转换为不可变集合
chr(s)		:将一个整数转换为一个字符
ord(s)		:讲一个字符转换为它的整数值
hex(s)		:将一个整数转换为16进制
oct(s)		:将一个整数转换为8进制
bin(s)		:将一个整数转换为2进制

'''
num = bin(48)
print('转换为二进制',num)
num = oct(48)
print('转换为8进制:',num)
num = hex(48)
print('转换为16进制:',num)
num = ord('a')
print('将字符转换诶整数:',num)
num = chr(97)
print('将整转换字符:',num)

 

字典和集合(dict,set)

       特点:没有索引和切片

1.字典 ==》由一系列键值对组成的可变散列容器

散列:对键进行哈希运算,确定在内存中的存储位置,每条数据存储无先后顺序

通过{}表示,应用:有对应关系的数据,一般建议放在字典中

        {}表示空字典,不代表集合

1.由键值对组成的数据(key,value),键是唯一的,不能重复,如果重复后续的键值会覆盖前面的

2.字典中的键都是不可变类型,一般都是使用字符串作为字典的键

                不可变类型:()

                                   数值(int,float,bool),字符串(string),元组(tuplue)

                可变类型:列表(list),字典(dict),集合(set) ==>  (有增删改查的方法)

  3.字典的增删改查方法

        1.增加--键赋值和update方法

str={'name':'张三','age':12,}
# 方式1:通过键赋值
str['sex'] = '男'
print(str) #==>{'name': '张三', 'age': 12, 'sex': '男'}
# 方式2:通过update:一次性向字典中添加多个元素
str.update({'ip':'127.0.0.3','js':'javascript'})
print(str) #==>{'name': '张三', 'age': 12, 'sex': '男', 'ip': '127.0.0.3', 'js': 'javascript'}

        2.删除--pop('键'),popitem(),del str["name"]

str={'name': '李四', 'age': 12, 'sex': '男', 'ip': '127.0.0.3', 'js': 'javascript'}
# 按照键来删除值
str.pop('name')
print(str)
# 删除最后一个添加的值
res = str.popitem()
print(res)  #=》 ('js', 'javascript')
print(str)  #=》{'age': 12, 'sex': '男', 'ip': '127.0.0.3'}

str.clear()  清空字典

        3.

str={'name':'张三','age':12,}
# 增加--
# 方式1:通过键赋值
str['sex'] = '男'
print(str) #==>{'name': '张三', 'age': 12, 'sex': '男'}
# 方式2:通过update:一次性向字典中添加多个元素
str.update({'ip':'127.0.0.3','js':'javascript'})
print(str) #==>{'name': '张三', 'age': 12, 'sex': '男', 'ip': '127.0.0.3', 'js': 'javascript'}

# 修改————使用键赋值的方式
str['name'] = '李四'
print(str) #=》{'name': '李四', 'age': 12, 'sex': '男', 'ip': '127.0.0.3', 'js': 'javascript'}

        4.查---

        1.通过键赋值     

str={'age': 12, 'sex': '男', 'ip': '127.0.0.3'}
str1 =str['sex']
print(str1) #=> 男   注意:如果查找的键不存在,则会报keyerror错误

      2.get()

str={'age': 12, 'sex': '男', 'ip': '127.0.0.3'}
str2 = str.get('ip')
print(str2)  #127.0.0.3  如果键不存在不会报错,显示None

其他方法:

        1.keys:获取字典中所有的键

str={'age': 12, 'sex': '男', 'ip': '127.0.0.3'}
str2 = list(str.keys())  # 因为keys得到的结果不是基础数据类型,需要用list转换,也可以用tuple
print(str2) #=>['age', 'sex', 'ip']

        2.values:获取字典中所有的值

str={'age': 12, 'sex': '男', 'ip': '127.0.0.3'}
str2 = list(str.values())  # 因为values得到的结果不是基础数据类型,需要用list转换,可以用tuple
print(str2) #=>[12, '男', '127.0.0.3']

        3.items :同时获取字典中的键和值(每个键值对已元组的方式呈现)   

str={'age': 12, 'sex': '男', 'ip': '127.0.0.3'}
str2 = list(str.items())  # 因为items得到的结果不是基础数据类型,需要用list转换
print(str2) #=>[('age', 12), ('sex', '男'), ('ip', '127.0.0.3')]

        4.fromkeys():创建指定值的字典(初始化字典)

# 创建指定值的字典
li = ["a","b","c"]
res = {}.fromkeys(li,None)
print(res) #{'a': None, 'b': None, 'c': None}

        5.setdefault():有则跳过,无则添加

# 字典中有键有值,就不添加,如果无键则添加,返回对应键的值
res = {"name":"alex"}
res.setdefault("name","engo")
print(res) #{'name': 'alex'}

创建字典的三种方式

# 创建字典的三种方式
# 第一种
dic = {'name': '张三', 'age': 19, 'sex': '男'}
print(dic)  # =>{'name': '张三', 'age': 19, 'sex': '男'}
# 第二种 使用内置函数dict()
decs = dict([('name', '李四'), ('age', '45'), ('sex', '女')])
print(decs)  # => {'name': '李四', 'age': '45', 'sex': '女'}
# 第三种方式
dist = dict(name='王五', age=29, sex='不男不女')
print(dist)  # =>{'name': '王五', 'age': 29, 'sex': '不男不女'}

 dict和json的区别?

       1.dict是数据结构,json 数据格式

        2.dict是一个映射类型的数据,通过key映射到value,而json 就是一个根据某种约定格式编写的纯字符串,不具备任何数据结构的特征

        3.Python的字典key可以是任意可hash对象,json只能是字符串

        4.python dict 字符串用单引号,json强制规定双引号

个人理解:


创建字典的时候,比方说 dic['name'] = 'hellow'
这个时候初始化,会生成两张表,一张保存索引,一张保存数据
先使用hash(name) 得到一个name的哈希值
在使用hash值对索引表取余或其他操作,得到一个下标,比方说是3,
那么在索引表下标为3的地方,插入0 ,在数据表中插入name的hash值,key和value
下一次在插入字典,同样得到下标值,在索引表下标的地方插入1,后续一次执行

Python字典发生哈希冲突时,会向下寻找空余位置,直到找到位置。

如果得到下标的时候,发现索引的位置有值了,并且key不一样,则就是hash冲突,那么继续向下寻找空位,有空位就保存
如果下标超出或当前索引表没有位置,则扩容,扩容的时候需要重新计算所有的key的hash值

# 给字典添加一个值,key为hello,value为word
# my_dict['hello'] = 'word'

# 假设是一个空列表,hash表初始如下
indices = [None, None, None, None, None, None]
enteies = []

hash_value = hash('hello')  # 假设值为 12343543
index = hash_value & ( len(indices) - 1)  # 假设index值计算后等于3

# 会找到indices的index为3的位置
indices = [None, None, None, 0, None, None]
# 此时enteies会插入第一个元素
enteies = [
    [12343543, 'hello', 'word']
]

# 我们继续向字典中添加值
my_dict['haimeimei'] = 'lihua'

hash_value = hash('haimeimei')  # 假设值为 34323545
index = hash_value & ( len(indices) - 1)  # 假设index值计算后等于 0

# 会找到indices的index为0的位置
indices = [1, None, None, 0, None, None]
# 此时enteies会插入第一个元素
enteies = [
    [12343543, 'hello', 'word'],
    [34323545, 'haimeimei', 'lihua']
]

常见的哈希冲突解决方法:

1 开放寻址法(open addressing)

开放寻址法中,所有的元素都存放在散列表里,当产生哈希冲突时,通过一个探测函数计算出下一个候选位置,如果下一个获选位置还是有冲突,那么不断通过探测函数往下找,直到找个一个空槽来存放待插入元素。

2 再哈希法

这个方法是按顺序规定多个哈希函数,每次查询的时候按顺序调用哈希函数,调用到第一个为空的时候返回不存在,调用到此键的时候返回其值。

3 链地址法

将所有关键字哈希值相同的记录都存在同一线性链表中,这样不需要占用其他的哈希地址,相同的哈希值在一条链表上,按顺序遍历就可以找到。
 

字典推导式

dic = {number: number ** 2 for number in range(10) if number % 2 == 0}
print(dic) # {0: 0, 2: 4, 4: 16, 6: 36, 8: 64}

# 问题: 将两个列表中的值,组成字典

name = ["张无忌" ,"超敏" ,"周芷若"]
house = [101,102,103]
# 方式一:
dic={}
for i in range(len(name)):
    dic[house[i]] = name[i]
print(dic)
# 方式2
dicy ={name[i]: house[i]  for i in range(len(name))}
print(dicy)
# 方式三
dic=dict(zip(house,name))
print(dic)

    

问题:字典 的key是有序的嘛?

答: python 3.5之前是无序的,3.5之后是有序的,但是这个有序也是伪有序,因为是通过哈希运算得到的位置,所以在内存中不可能保证顺序,这是表现形式是有序的(记录了创建时候的顺序)

集合set:由一系列不可重复的不可变类型变量(元组、数、字符串)组成的可变散列容器

无序,不重复

        定义:

        1.使用{}来定义,如果需要空集合,st = set(), {}表示字典,不代表集合需要注意

        2.集合是可变类型数据(有增删改查方法)

        3.集合中的元素是唯一的,没有重复的元素

                应用场景:数据去重,关系运算(并集差集等)

str = [1,67,6,6,6,23,34,34,5,5,5]
str = set(str)
print(str,type(str)) # => {1, 34, 67, 5, 6, 23}<class 'set'> 这是set类型
print(list(str)) #=》 [1, 34, 67, 5, 6, 23] 再次使用list转换

去重的局限性:

1. 只能针对不可变类型
2.不能保证原来的顺序

        4.集合中的元素是无序的,也就意味着没有下标索引

        5集合中必须存放不可变类型的数据

方法:

# 集合的方法:
s = {"a", "b"}

s.add("abc") # 新增一个
print(s)  # {'a', 'b', 'abc'}
s.remove("abc") #如果删除的元素不存在,则会报错
print(s)  #{'a', 'b'}

s.discard("张三") # 如果元素不存在,不会报错

s.update() :是把要传入的元素拆分,做为个体传入到集合中

小案例: 循环录入省份,输入为空,则退出,要求省份不重复

s = set()
while True:
    pro = input("请输入省份:")
    if not pro:
        break
    s.add(pro)
print(s)

集合的应用二:交集、并集、补集

s1 = {1,2,3}
s2={2,3,4}
# 交集: 返回相同的部分
print(s1 & s2 )  # {2, 3}
# 并集:返回不重复的部分
print(s1 | s2 ) # {1, 2, 3, 4}

# 补集:
# ^: 相互补,返回都没有的数据
print(s1 ^ s2) #{1, 4}
# -:返回s2没有的数据
print(s1 - s2) #{1}
# -:返回s1没有的数据
print(s2 - s1) #{4}

基础扩展:

        1.生成随机数及随机取一个数

import  random

s1= random.randint(0,3) # 在 0-3范围中随机生成一个整数,包含0和3
print(s1)
s2 = random.random() # 随机生成一个0-1之间的小数,不含0和1
print(s2)
# 问题:生成一个10-20之间的小数
# 方式1:randomint(10-19)+random()
# 方式2
s3 = random.uniform(10,20)
print(s3)
str = [1,2,3,4,5,6]
res = random.choice(str) # 随机在列表中获取一个值
print(res)

容器小结:

 

注意事项:

1.不可变数据,使用+= 会创建的新对象

2.可变类型,在使用时会在原数据上增加

# 不可变类型使用 增强型数据运算符时会生成新对象
a = (1, 2)
b = (2, 3)
print(id(a))  # 2431797892224
a += b
print(id(a))  # 2431793525360

# 可变类型,在使用时会在原数据上增加
c =[1,2]
d =[3,2]
print(id(c)) # 2102260085888
c+=d
print(id(c))# 2102260085888

算数运算符

比较运算符: > ,   <,    <= , =>  ,!=     , == 

逻辑运算符: and 与                   or 或            not 非(取反)

成员运算符: in  not in

            a   in  b :a是否在b中

             a  not in  b :a是否不再b中

身份运算符: 判断id指向是否一致

       a  is  b  :a和b是否相等(对比内存地址指向)

      a  is not b: 判断a 和 b 不是一个内存地址

==和is的区别?

is 用于判断两个变量引用对象是否为同一个,就是所引用的对象的内存地址是否一致
== 用于判断引用变量的值是否相等。只判断值和数据类型

压缩赋值

        

# 全部取出来
elax = [12,3,45,6,7]
a,b,c,d,e =elax
print(a,b,c,d,e) # 12 3 45 6 7
# 只去部分值(首,尾,不能取中间的值)
elax = [12,3,45,6,7]
a,b,*_ =elax
print(a,b) # 12 3

while循环的使用

n = 1;
while True:
    n += 1
    print('打印第%d遍' % (n))
    print('打印第{}遍'.format(n))
    if n == 100:
        break

break:终止循环

continue:中止当前循环,进入下次循环

# 问题: 用户输入月份,输入对应的天数
while True:
    try:
        month = input("请输入月份:")
        if month == "结束":
            break
        month = int(month)
        if  month <=12:
            if month == 2:
                print(f"{month}月有29天")
            elif month in (4, 6, 9, 11):
                print(f"{month}月有30天")
            elif month in (1, 3, 5, 7,8,10,12):
                print(f"{month}月有31天")
        else:
            print("输入错误,请重新输入")
            continue
    except:
        print("输入错误,请重新输入")

for循环的使用

lis = ['aa','bb','cc','dd']
for i in lis:
    print(i ,end=' ') # aa bb cc dd  遍历出来列表中每个数据



dic = {'aa': 11, "bb": 22, "cc": 33, "dd": 44}
for p in dic:
    print(p, end=' ') #输出所有的键 aa bb cc dd
for y in dic:
    print(dic[y],end =' ') # 输出所有的值 11 22 33 44
for its in dic.items():
    print(its,end=' ') #输出键和值('aa', 11) ('bb', 22) ('cc', 33) ('dd', 44)
    a,b = its #使用拆包的方式得到键 和值
    print(a,b,end=' ') #aa 11  bb 22  cc 33  dd 44

for a,b in dic.items():# 简洁拆包
    print(a,b)  #aa 11  bb 22  cc 33  dd 44

for-else的应用

# for else 的使用
# 语法:
# for xx in xx:
    # 循环体
# else
    # 循环正常结束的会执行else中的代码
    # 循环通过break强制结束则不会执行else
# 应用场景 判断用户输入的是否在字典中存在
name = input('请输入用户名:')
dic=[{'name':'pyth22'},{'name':'py33'},{'name':'py44'}]
for i in dic:
    if i['name'] == name:
        print('用户存在')
        break;
else:
    print('用户不存在')


拆包

# 拆包
ls = ['a','b','c','d','e']
a1,b1,c1,d1,e1 = ls
print(a1,e1)  # a e

       注意:拆包时属性名在列表或元组中匹配不了数据,打印的时候就会报错 

内置函数range()

        一般配合for使用:

        1.作用:用来生成一个指定长度的序列

        2.range(n):生成一个0- n-1的整数序列,可以使用list转成列表类型

        3.range(n,m):生成一个n-(m-1)的整数序列,可以使用list转成列表类型

        4.range(n,m,k):n为初始值,m-1为结束值,k为步长

ra = list(range(10))
print(ra,type(ra)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] <class 'list'>

rs = list(range(100,110))
print(rs)#[100, 101, 102, 103, 104, 105, 106, 107, 108, 109]

ran = list(range(10,30,2))
print(ran) #[10, 12, 14, 16, 18, 20, 22, 24, 26, 28]
# 九九乘法表
for i in range(10):
    for j in range(i+1):
        print('{}*{}={}'.format(i,j,i*j) ,end=' ')
    print() 

 面试题:有四个数字,取出3个不同且不同的组合

lis = [1, 2, 3, 4]
lio=[]
li = []
for i in lis:
    for j in lis:
        for p in lis:
            if i != j and j != p and i != p:
                li.append([i,j,p])

print(li,len(li))

[[1, 2, 3], [1, 2, 4], [1, 3, 2], [1, 3, 4], [1, 4, 2], [1, 4, 3], [2, 1, 3], [2, 1, 4], [2, 3, 1], [2, 3, 4], [2, 4, 1], [2, 4, 3], [3, 1, 2], [3, 1, 4], [3, 2, 1], [3, 2, 4], [3, 4, 1], [3, 4, 2], [4, 1, 2], [4, 1, 3], [4, 2, 1], [4, 2, 3], [4, 3, 1], [4, 3, 2]] 

24

测试题:

# # 三个筛子所有组合
list_new_02 = [(i,j ,y)for i in range(1,7) for j in range(1,7) for y in range(1,7) ]
print(list_new_02,len(list_new_02))

# 三个筛子所有组合- 三个数不重复
list_new_03 = [(i, j, y) for i in range(1, 7) for j in range(1, 7) for y in range(1, 7) if i != j != y != i]
print(list_new_03, len(list_new_03))

函数

规则:要小而精,不要大而全

        1.之前已经学习过的函数

        print():输出

        id():获取数据的内存地址

        input:输入

        type:查看数据类型

        int,float,bool,str,list,tuple,dict,set:代表对应的数据类型

定义:def 函数名('参数'){函数体}

先定义在调用:定义的函数的时候只检测语法,只有在调用的时候在执行函数体

函数名的命名规则:推荐下划线命名,见名知意

调用函数的时候参数传递类型:

1.位置传参:实参必填

def abs(a,b):
    print(a+b)

abs(3,4)

2.关键字传参:形参可以有默认值,默认值在函数定义阶段被赋值,并且不要定义为可变类型

x = 1
def abs(a,b =x): #  定义阶段b的值就是1,确切的说b被赋予的是内存地址
    print(a+b)
x = 2 # 这里改变的是X的值,和上面b没有关系
abs(a=4)# 4,1

3.混合传参:注意位置参数在前,关键字参数在后

4.不定长参数:调用的时候可以传0个可以传多个

        好处:让调用者不必自行构建容器存储多个数据

        *args  :只能使用位置传参,接收的参数以元组的形式保存

        **kwargs:只能使用关键字传参,接收的参数以字典的形式保存

def  add(*args):
    print(args)
add(1,2,3,4) #=>(1, 2, 3, 4)

def sel(**kwargs):
    print(kwargs)

sel(a=1,b=2,c=3,d=4) #=>{'a': 1, 'b': 2, 'c': 3, 'd': 4}

 # 需求:同时可以接受位置参数和不定长参数(任意参数)

# 需求:同时可以接受位置参数和不定长参数(任意参数)
def func(*args,**kwargs):
    print(args)
    print(kwargs)

func() # () {}
func(11,22) #(11, 22) {}
func(a=1,b=2) # ()  {'a': 1, 'b': 2}
func(90,89,name='张三',age=19) # (90, 89)   {'name': '张三', 'age': 19}

函数参数拆包

* :表示对列表或元组进行拆包

** :表示对字典进行拆包

注意:拆包的时候只能和参数个数相符,不能多不能少

# 函数拆包
def func(a,b,c):
    print(a)
    print(b)
    print(c)
lis=[11,22,33]
func(*lis)
# 11
# 22
# 33

# 函数传入字典进行拆包
def func(a,b,c):
    print(a)
    print(b)
    print(c)

# dic={'aa':12,'bb':18,"cc":90}
# func(**dic) #  报错  字典的键必须和参数名相同
dic={'a':12,'b':18,"c":90}
func(**dic) # 12 18 90

总结: 按顺序或按名称

实际参数:与形参如何对应

        位置实参:按顺序

                函数名(数据1,数据2)

                序列实参:序列拆分多个元素

                        函数名(*序列  ):str,list,tuple   

        关键字实参:按名称

                函数名(名称1=数据,名称2=数据)

                字典实参:一字典拆分多元素

                        函数名(**字典)    

函数的返回值

 1.关键字:return

        函数中没有return 返回值数目=0,返回None

        返回值数目=1,返回object

        返回值数目>1,返回tuple

       注意: 当函数执行到reurn的时候,就会终止执行

2.如果函数内部修改的是可变数据(list,dict,sex),则不需要return ,下面是案例:

lis = [1, 2, 4, 5, 6, 7]

def alter_data(lis):
    for i in range(len(lis) - 1,-1,-1):
        if lis[i] % 2 == 0:
            del lis[i]
alter_data(lis)
print(lis)  # [1, 5, 7]

 

参数的顺序: 位置形参-》星号元组形参-》命名关键形参-》双星号字典形参

全局变量和局部变量

1.定义在python中(模块)的变量,是全局变量,文件中任何地方都可以用

2.定义在函数中的 变量,是局部变量,仅仅在函数内部使用

在局部变量前加一个关键字global ,可以是把局部变量变成全局变量

如果函数中全局变量和局部变量相同,函数在引用的时候优先使用局部变量,如果找不到,则使用全局变量

注意:定义局部变量的时候尽量不要和全局变量相同

匿名函数--lambda

语法:lambda 参数:返回值  :适用于函数内部代码比较简单的函数

def func(x):
    return x*2;

reso = func(10)
print(reso)

# 上面简单的函数可以写成匿名函数--如下
fun = lambda x:2*x;
res=fun(10)
print(res)


funs = lambda x,y:x+y
print(funs(11,22))  # 33

常用的内置函数

mix():获取最小值

max():获取最大值

sum():求和

lis=[1,2,3,4,7,8,54,67,89,43,556]
# 最小值
res = min(lis)
print(res) #1
# 最大值
res2 = max(lis)
print(res2) #556
# 求和
res3 = sum(lis)
print(res3) # 834

# 可迭代对象(iterable):可以使用for进行遍历的数据都是可迭代对象
(常见的字符串,列表,字典,元组,集合)

eval():识别python中的正确表达式,简单说就是去除两边的逗号

enumerate:得到列表,字符串,元组中每个元素和索引

# lis=[1,2,3,4,7,8,54,67,89,43,556]
# # 最小值
# res = min(lis)
# print(res) #1
# # 最大值
# res2 = max(lis)
# print(res2) #556
# # 求和
# res3 = sum(lis)
# print(res3) # 834

# 可迭代对象(iterable):可以使用for进行遍历的数据都是可迭代对象(常见的字符串,列表,字典,元组,集合)
# 需求:获取列表中每个值和索引
liss = ['name','age','sex','group']
# 第一种方式:使用循环和index()
for i in liss:
    print(liss.index(i),i)
print('________________________________')
# r = enumerate(liss)
# print(list(r)) #[(0, 'name'), (1, 'age'), (2, 'sex'), (3, 'group')]
for p in enumerate(liss):
   k,v = p
   print(k,v)

   0
   name
   1
   age
   2
   sex
   3
   group
   ________________________________
   0
   name
   1
   age
   2
   sex
   3
   group

zip:数据的聚合打包: 一般用在把列表转换为字典上

注意:zip聚合后的数据只能使用一次,因为zip返回的数据是一个迭代器(生成器)

lis1=['name','age','sex']
lis2=['张三',19,'男']
lis3=zip(lis1,lis2)
print(lis3)  # <zip object at 0x000001C87F5EB988>
print(dict(lis3)) # {'name': '张三', 'age': 19, 'sex': '男'}

zip聚合的时候会按照最短的数据长度来统计,并且拿每个数据的第一个聚合成元组,放在一个列表中

lio = [1,2,3,4,5]
lioo =[9,8,7,6]
liop = ['a','b','c','d','e']
list_zi = zip(lio,lioo,liop)
print(list(list_zi))  #[(1, 9, 'a'), (2, 8, 'b'), (3, 7, 'c'), (4, 6, 'd')]

过滤器:filter,   只能输出一次,因为返回的是一个迭代器

filter('函数','可迭代对象')

作用:按函数的规则批量过滤数据

# filter简单应用
def func(x):
    return x>80

lists = [90,87,56,93,100,43]
res = filter(func,lists)
print(list(res)) # [90, 87, 93, 100]


# 上面的简单的函数可以使用匿名函数
res2 = filter(lambda x:x>90,lists)
print(list(res2)) # [93, 100]

# 利用过滤器输出100内的奇偶数
a = list(filter(lambda x: x & 1, range(0, 101)))
print(a)
b = list(filter(lambda x: x % 2 == 0, range(1, 101)))
print(b)

# 筛选600-700分的学生
list1 = [('小明', 600), ('小刚', 602), ('小王', 800), ('小李', 400)]
lit = list(filter(lambda x: 600 < x[1] < 700, list1))
print(dict(lit))  # {'小刚': 602}

# 筛选类型不是字符的元素
list2 = ['小明', 18, 176, 85]
lis = list(filter(lambda x: type(x) != str, list2))
print(lis)  # [18, 176, 85]

# 面试题:按照下面列表中每个数据的第二数的大小来排序

# 面试题:按照下面列表中每个数据的第二数的大小来排序
lists = [(2,1,3,5),(5,6,8,9),(2,4,12,3)]
# lists.sort(key=lambda x:x[1])
#  print(lists) [(2, 1, 3, 5), (2, 4, 12, 3), (5, 6, 8, 9)]
# 面试题:按照下面列表中每个数据的第三个数的大小来倒序
def func(x):
    return x[2]
lists.sort(key=func,reverse=True)
print(lists)

# 列表中带字典也是可以排序的

list_d=[{"age":78},{"age":12},{'age':19},{"age":90}]
def funx(x):
    return x['age']
list_d.sort(key=funx,reverse = True)
print(list_d) # [{'age': 90}, {'age': 78}, {'age': 19}, {'age': 12}]
list_d.sort(key=lambda x:x['age'])
print(list_d) # [{'age': 12}, {'age': 19}, {'age': 78}, {'age': 90}]

时间: time

        

import time
'''  得到时间元组  '''
t = time.localtime()
print(t)
# print("年:",t[0])
# print("月:",t[1])
# print("日:",t[2])
# print("时:",t[3])
# print("分:",t[4])
# print("秒:",t[5])
# print("周:",t[6]+1)
#
# '''得到时间戳  1970年1月1日开始'''
tt = time.time()
print(tt)

'''时间戳--》转换为 时间元组'''
p = time.localtime(time.time())
print(p)
# '''时间元组转换为时间戳'''
pp = time.mktime(time.localtime())
print(pp)
#
# '''将时间元组转换为 固定格式的字符串 '''
print(time.strftime("%Y-%m-%d %H:%M:%S", p))
# 2022-11-11 14:47:17

print(time.strftime("%Y-%m-%d %H:%M:%S"))
# 2022-11-11 14:47:17

# 将字符串转换为时间元组
t = "2022-11-11"
time.strptime(t,"%Y-%m-%d")
# (tm_year=2022, tm_mon=11, tm_mday=11, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=4, tm_yday=315, tm_isdst=0)

文件操作

open(参数1,参数2,参数3):

       1. 参数file:传一个文件名(路径)

        2.参数more:文件打开的模式

                #-----------操作文本

              r:只读,全称 read,如果文件不存在,则会报错

              w:写入 ,全称write,会覆盖写入(文件如果有内容会覆盖)如果文件不存在,则会自动创建

             a:追加写入,全称append,(在原有文件内容上进行追加)如果文件不存在,则会自动创建

              r+:表示打开文件进行读写

              w+:表示打开文件进行读写

            r+和w+的区别

                        1、r+  =》 如果文件不存在则报错:FileNotFoundError;如果文件存在,每次打开文件时,从文件起始位置开始读写,写的时候会覆盖原有内容,未覆盖部分保留

                        2、w+ ==》如果文件不存在,不会报错,而是自动创建一个文件;如果文件存在,每次打开文件时,会清空原有内容

              #---------------操作图片,视频等文件

              rb:(二进制的方式打开文件)只读,全称 read,如果文件不存在,则会报错

              wb:(二进制的方式打开文件)写入 ,全称write,会覆盖写入(文件如果有内容会覆盖)如果文件不存在,则会自动创建

             ab:(二进制的方式打开文件)追加写入,全称append,(在原有文件内容上进行追加)如果文件不存在,则会自动创建

        3.参数encoding:指定编码格式--- linux 默认utf-8 ,window默认GBK

操作文件三步骤“:

        打开open()

        操作(读或者写)

                读取:

                        read():读取文件所有内容

                        readline():读取文件中一行内容

                        readlines():按行读取文件中的所有内容,返回一个列表
                写入:

                        write():写入文件内容

        

        关闭

                close()

        刷新

                flush():一般使用在测试场景,强制将数据写入硬盘,不在累计到一定数量在写入

        

        readable():判断是否是读取模式
        writable():判断是否是写入模式

                closed:判断文件是否关闭

# 打开文件
re = open('text.txt',mode='r',encoding='utf-8')
# 操作文件  读或者写
ls = re.read()
print(ls) #这是一个python文件
# 关闭文件
re.close()
# 复制文件案例
#通过'读'模式来读取文件
res = open(file='test.txt', mode='r', encoding='utf-8')
ls = res.read()

#通过'写'模式来生成一个新文件写入
f = open(file = 'test01.txt',mode='w',encoding='utf-8')
fls =f.write(ls)

# 关闭文件
res.close()
f.close()

上下文管理器

with:使用

with:启动对象的上下文管理器的关键字

上下文管理器协议:如果一个类中定下如下的两个方法那么该类就实现了上下文管理器协议(可以通过with去操作)

__enter__:

__exit__: 

# with open打开的文件赋值给变量f
with open(file=r'E:\interface\untitled1\test01.txt', mode='rb') as f:
    #接收读取的返回值
    fl = f.read()
    print(fl)
    f.writable() # 判断是否是写入模式
    f.readable()# 判断是否是读取模式
    f.closed# 判断文件是否关闭

with操作文件的好处:会启动上下文管理器,不用手动关闭文件,会自动关闭文件

 with可以打开多个文件

# with可以打开多个文件
with open("./aaa.txt","r",encoding="utf-8") as f,\
        open("./bbb.txt","w",encoding="utf-8") as f2:
    res = f.read()
    f2.write(res)

循环读取数据的两种方式

 #while循环读取文件
with open("aaa.txt",mode="rb") as f1:
    while True:
            res = f1.read(1024)# 限制每次读取数据的字节数,下次在结束处读取
            if len(res) == 0:
                break
            print(len(res))
# copy数据小工具
def  mix_copy(is_stat,is_end):
    with open(is_stat,mode="rb") as f,\
            open(is_end,mode="wb") as ff :
        for item in f:# 以二进制方式循环读取每行的内容
            ff.write(item)
mix_copy("1.jpg","2.jpg")

读取文件练习

'''
张三  19  小二班
李四  20  小三班
王五  12  小一班

需求: 将小字换成大字
需求二:将每个姓名后面增加“学生”
'''
# 需求一
# with open("aaa.txt",mode="r",encoding="utf-8") as f:
#     res =f.read()
#     txt = res.replace("小","大")
# with open("aaa.txt",mode="w",encoding="utf-8") as ff:
#     ff.write(txt)
# 需求二
import  os
with open("aaa.txt",mode="r",encoding="utf-8") as f,\
     open(".aaa.txt.swap",mode="w",encoding="utf-8") as ff:
    for line in f:
        '''
            这里也可以替换字符,相比较需求一的read()全部读取到内存,这种行读取比较省内存,
            也就是说拿硬盘空间换内存空间
        '''
        ff.write(line[:2]+"学生"+line[2:])

os.remove("aaa.txt")# 删除文件
os.rename(".aaa.txt.swap","aaa.txt") # 更换文件名

文件的指针操作

# 指针移动的单位都是以字节bytes为单位
# seek(n,模式) : n是指移动的字节个数
# 模式:
#     0:参照物是文件开头位置
#     1:参照物是当前指针所在的位置
#     2:参照物是文件末尾的位置,倒着移动,类似切片
# tell(): 当前指针的位置
with open("aaa.txt",mode = "rb") as f:
    f.seek(-9,2)
    f.seek(-3,2)
    print(f.tell())
    print(f.read().decode("utf-8").encode("utf-8"))
    print(b'\xe5\xa5\xbd'.decode("utf-8"))

模块和包

命名规范:数字,字母,下划线组成,不要以数字开头,同变量命名规范

        模块-Moudle:是一个python文件,以.py结尾

        包-Package:python中包就是一个包含__init__.py文件的目录(文件夹)

导入关键字: from   import

模块导入: import 模块名

包导入:from ‘项目的根目录路径’ import '模块名'

如果在当前文件调试代码但又不希望导入的时候执行,可以使用 if __name__ = '__main__':

if __name__ == '__main__':

扩展:导包时搜索路径的顺序

# 导包时,系统查找的顺序,可以使用sys模块来查看
import  sys
for i in sys.path:
    print(i)
# F:\python\python.exe G:/vue/9-30YKY_Project/test/demo_Moudle_Package/demo_M_P_06.py
# G:\vue\9-30YKY_Project\test\demo_Moudle_Package     启动文件的所在目录
# G:\vue\9-30YKY_Project  项目的根目录
# F:\python\python36.zip   安装目录
# F:\python\DLLs            安装目录
# F:\python\lib             安装目录
# F:\python                 安装目录
# F:\python\lib\site-packages
# F:\python\lib\site-packages\win32
# F:\python\lib\site-packages\win32\lib
# F:\python\lib\site-packages\Pythonwi

异常捕获 : 保障程序按既定流程执行

  使用场景: 一般用于逻辑错误, 因为数据超过有效范围而引发的错误

关键字:try  except  else finally  raise

完成的语法:  

​
# try:
#     可能出错的代码
# except:
#     如果出错就执行这里的代码,多个except只会执行一个
# else:
#     如果不出错执行这里的代码
# finally:
#     不论是否出错,都执行这里的代码

​

常见的错误类型

       1.nameError:变量没定义

        2.SyntaxError:语法错误

        3,KeyError:键不存在

        4.MoudleNotFoundError : 没找到模块

捕获所有常规异常

# 捕获所有常规异常
try:
    num = int(input('请输入一个整数:'));
except Exception as e:
    print('操作错误');
    print('错误原因:{}'.format(e))
请输入一个整数:s
操作错误
错误原因:invalid literal for int() with base 10: 's'

 捕获指定异常 

# 捕获指定异常
try:
    print(name)
except NameError as e:
    print('没有定义变量')
    print('错误类型:{}'.format(e))
没有定义变量
错误类型:name 'name' is not defined

注意:try中如果有多行代码,只有有一行代码报错,后续代码都不会执行,会直接进入到except中

捕获多个指定异常

# 捕获多个异常类型:不同异常做不同处理
try:
    pass
except NameError as e:
    print('处理NameError的方案')
except KeyError as e:
    print('keyerror的方案')

raise :主动抛出异常查看  ,发送错误消息消息

        

class Wire:
    def __init__(self, name="", age=0):

        self.name = name
        self.age = age

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if 20 <= value <= 30:
            self.__age = value
        else:
        
            raise Exception("年龄太 大/小 了,我不要", 10011)


while True:
    try:
        age = int(input("请输入年龄:"))
        w = Wire("双儿", age)
        break
    except Exception as e:
        print(e)

python的关键字--》35个关键字

import keyword
print(keyword.kwlist)    
['False', 'None', 'True', 'and', 'as', 'assert', 
 'break', 'class', 'continue', 'def', 'del', 'elif', 
 'else', 'except', 'finally', 'for', 'from', 'global',
 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not',
 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

面试题:查询一个年月日的天数或两个年月日之间的天数

class Calculation:
    def __init__(self, start_day, end_day):
        self.start_day = start_day
        self.end_day = end_day

    def main(self):
        # 1.获取开始和结束的年份
        s_year = int(self.start_day.split("-")[0])
        e_year = int(self.end_day.split("-")[0])
        # 如果开始年份小于结束年份,并且=1,表示是相邻的年份
        if s_year < e_year and e_year - s_year == 1:
            # 得到开始年月日之后的天数
            s_sup_day = self.__is_year_data(self.start_day)[1]
            # 4.得到结束年月日的之前天数
            e_sup_day = self.__is_year_data(self.end_day)[0]
            return sum([s_sup_day, e_sup_day])
        # 如果是不相邻的年份,先算出两个年份的总天数,在减去开始年份之前的和结束年份之后的天数
        elif s_year < e_year and e_year - s_year >= 2:
            # # 2.两个年份之间天数
            day = self.__run(s_year, e_year)
            # 3.得到开始年份之前的天数
            s_sup_day = self.__is_year_data(self.start_day)[0]
            # 4.得到结束年份的之后天数
            e_sup_day = self.__is_year_data(self.end_day)[1]
            return day - (s_sup_day + e_sup_day)
        # 如果年份相等,传入结束年份,返回结束年份之前的天数
        elif s_year == e_year:
            return self.__is_year_data(self.end_day)[0]

    # 判断是否是润年还是平年
    def __is_run(self, year):
        '''
        判断是否是闰年并返回月份元组
        :param year: 年
        :return: 元组
        '''
        if year % 4 == 0 and year % 100 != 0 or year % 400 == 0:
            print(year, "是润年366天")
            return (31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
        print(year, "是平年365天")
        return (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

    # 计算两个年份的总天数
    def __run(self, s_year: int, e_year: int):
        '''
        给两个年份计算出总天数
        :param s_year: 开始年份
        :param e_year: 结束年份
        :return: 总天数
        '''
        count = 0
        ran = (e_year - s_year)
        if ran > 0:
            for i in range(ran + 1):
                count += sum(self.__is_run(s_year))
                s_year += 1
        elif ran == 0:
            count += sum(self.__is_run(s_year))
        else:
            return False
        return count

    def __is_year_data(self, ymd: str):
        '''
        根据年月日返回之前及之后的天数
        :param ymd: 2002-10-18
        :return: (之前的天数,之后的天数)
        '''
        lis = ymd.split("-")
        user_data = self.__is_run(int(lis[0]))

        befor_after = []

        if lis[2] == "31":
            '''
                        根据天数元组得到当前年月日之前的天数
             '''
            befor_after.append(sum(user_data[:int(lis[1]) - 1]) + int(lis[2]))
            '''
              根据天数元组得到当前年月日之后的天数()
            '''
            befor_after.append(sum(user_data[-1:-(len(user_data) - int(lis[1]) + 2):-1]) - int(lis[2]))
        else:
            # 根据天数元组得到当前年月日之前的天数
            befor_after.append(sum(user_data[:int(lis[1]) - 1]) + int(lis[2]) - 1)
            '''
              根据天数元组得到当前年月日之后的天数(包含当天)
            '''
            befor_after.append(sum(user_data[-1:-(len(user_data) - int(lis[1]) + 2):-1]) - int(lis[2]) + 1)

        return befor_after


# ------------------------------------------------------------------
a_year = "2021-1-1"
b_year = "2021-12-31"

c = Calculation(start_day=a_year, end_day=b_year)
print(c.main())

类和对象

定义:

# 方式一
class 类名:
    pass
# 方式二
class 类名():
    pass

类命名的规范:大驼峰的规范,不能使用数字开头,不能使用关键字

# 创建猫类的实例对象
class Cat():
    pass
c = Cat() # c 就是Cat类的实例对象

类的方法和属性

        属性:

                1.类属性:定义在类中的变量(表示这类事物共同属性,并且属性值一样)

                2.对象(实例)属性:对象自己单独有的属性,

                        定义:对象.属性名 = 属性值

# 创建猫类的实例对象
class Cat():
     # 类属性
    name = '猫'
c = Cat() # c 就是Cat类的实例对象
# 对象属性
c.name = '叮当猫'
print(c.name) #叮当猫

        方法:简单来说就是行为

类属性的调用:可以通过类来调用也可以使用对象来调用

实例(对象)属性的调用:只能通过对象调用

方法的调用:

                1.不能通过类名去调用

                2.只能通过对象去调用

__init__():也成为构造函数

魔术方法:以双下划线开头和结尾的方法,比如__init__,另外双下划线不需要手动调用

__init__()

class Person():
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

p  =Person('张三',19,'男')
print(p.name,p.age,p.sex)  # 张三 19 男

self:表示实例本身,一般来说就是谁调用就代表谁

__str__ ()

1>调用实际

        使用print(),打印的时候会自动调用

        如果没有定义__str__方法,默认打印的时对象的引用地址

        如果定了此方法,则打印方法的返回值

2>应用场景

        使用print打印对象的属性信息的时候

3> 注意事项

        必须返回一个字符串类型

实例化一个对象的时候,内存创建多大的空间?

1.主要看__init__方法中有几个self 

2.实例化对象会自动调用__init__()

类属性的特征

        1.随类的加载而加载

        2.存在优先于对象

        3.只存储一份(也就是说多个对象引用的都是一个类属性)

对象属性查找和类属性查找的区别

对象属性查找:  对象<  类 < 父类 < object
类属性查找: 类<  父类 < object < 元类


对象属性查找到object就结束了
类属性查找会到元类才结束

元类和class机制研究

1.class机制

'''
基于一切皆对象的原则
我们将一个类也看成对象,那么这个对象的类就是元类
元类---》实例化---》类(people)---》实例化---》对象
那么如何查看 该类的元类
'''


class People:
    def __init__(self, name, age):
        self.name = name
        self.age = age


print(type(People))  # <class 'type'> 内置的元类
print(type(int))

'''

使用type方法查看类的元类,我们发现自定义类的元类是内置type,内置类的元类也是type
由此在推断class关键字底层如何造出来类的?
1.类的三大特征
    类名
    基类
    执行体
'''

# 1、得到类名
class_name = "People"
# 2.得到基类
class_obj = (object,)
# 3.得到类体代码
class_body = '''
def __init__(self, name, age):
        self.name = name
        self.age = age
'''
#创建名称空间
class_dic = {}

# 4.调用exec方法,将类体执行,产生名字后放入名称空间
exec(class_body,{},class_dic) #将类体代码执行过程中,产生的名字放在名称空间中
# print(class_dic) # {'__init__': <function __init__ at 0x000001EFC6BFC4C0>}
# 5.调用内置元类type( 这里是否可以考虑自定义元类??)
class_name=type(class_name,class_obj,class_dic)
print(class_name)

2.自定义元类

       1.自定义元类的init方法

class Myate(type):  # 继承了内置元类type的类才是自定义元类
    def __init__(self, x, y, z):
        print(self)  # People类
        print(x)  # 类名
        print(y)  # 基类
        print(z)  # 类体的命名空间

        '''
        在初始化的时候,我们就可以控制类的创建,例如:我们需要初始化类必须有注释
            "__doc__" not in z: 判断命名空间中是否有"__doc__"这个键
            z['__doc__'].strip(' \n'): 得到这个键的值在去除左右的空格
        '''
        # 如果键不存在或注释长度==0 就报错
        if "__doc__" not in z or len(z['__doc__'].strip(' \n')) == 0:
            raise NameError("没有注释")

'''
基于一切皆对象的概念,People类本质上也是一个对象
创建对象的三步:
1.创建一个空的对象
2.调用init方法进行初始化对象
3.返回初始化好的对象
'''

# 自定义类
class People(metaclass=Myate):  # People绑定自定义元类Myate
    '''
        People类的注释
    '''
    def __init__(self, name):
        self.name = name

    def say(self):
        print("say方法")

2.自定义__new__和__call__方法

class Myate(type):  # 继承了内置元类type的类才是自定义元类
    '''
    根据创建对象的步骤,我们知道初始化前,我们需要创建一个空对象
    这里创建一个 __new__方法,调用基类的new方法创建一个空对象
    '''

    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)
        # return type.__new__(cls, *args, **kwargs)

    '''
        初始化: 调用基类的初始化,传入参数
    '''
    def __init__(self, class_name, class_bases, class_dic):
        # type.__init__(self, class_name, class_bases, class_dic)
        # super(Myate, self).__init__( class_name, class_bases, class_dic)
        super().__init__(class_name, class_bases, class_dic)

    '''
    空对象和初始化都实现了,那么如何返回一个初始化好的对象
    这里需要知道:对象() ==> 返回的就是__call__()方法的返回值
    所以 People() 得到就是call方法返回的对象
    '''
    def __call__(self, *args, **kwargs):
        # 初始化--得到空对象
        obj = self.__new__(self, *args, **kwargs)
        # 使用空对象初始化
        self.__init__(obj, *args, **kwargs)
        # 返回 初始化好的对象
        return obj


'''
基于一切皆对象的概念,People类本质上也是一个对象
创建对象的三步:
1.创建一个空的对象
2.调用init方法进行初始化对象
3.返回初始化好的对象
'''


# 自定义类
class People(object, metaclass=Myate):  # People绑定自定义元类Myate
    '''
        People类的注释
    '''

    def __init__(self, name):
        self.name = name

    def say(self):
        print("say方法")

    def __str__(self):
        return self.name


p = People("张三")
print(p)

3.最终自定义

class Myate(type):  # 继承了内置元类type的类才是自定义元类
    
    '''
        终极代码: 只需要有一个call方法,内部都调用父类的方法
    '''
    def __call__(self, *args, **kwargs):
        # 初始化--得到空对象 ==》 一级一级调用父类的new方法
        obj = self.__new__(self, *args, **kwargs)
        # 使用空对象初始化==》 一级一级调用父类的init方法
        self.__init__(obj, *args, **kwargs)
        # 返回 初始化好的对象
        return obj

'''
基于一切皆对象的概念,People类本质上也是一个对象
创建对象的三步:
1.创建一个空的对象
2.调用init方法进行初始化对象
3.返回初始化好的对象
'''


# 自定义类
class People(object, metaclass=Myate):  # People绑定自定义元类Myate
    '''
        People类的注释
    '''

    def __init__(self, name):
        self.name = name

    def say(self):
        print("say方法")

    def __str__(self):
        return self.name


p = People("张三")
print(p)



# 总结

'''
1.自定义类默认的元类是内置元类type,使用type(类名)查看
2.自定义类可以指定元类,自定义类(metaclass = 自定义元类)
3.自定义元类必须要继承内置元类type,否则只是一个普通类
4.自定义元类中有单个内置方法:__new__、__init__、__call__
5.__new__需要有 “cls、类名、基类、类体的命名空间”等参数
6.__new__中可以针对类体中的数据进行操作。例如:判断是否有注释、将类属性变成大写等
7.__new__中最终返回一个空对象,该对象可以调用父类同名方法产生: super().__new__(cls,类名、基类、类体的命名空间)
8.__init__需要有“self、类名、基类、类体的命名空间”等参数
9.__init__最终返回一个实例化好的对象,可以调用自己或父类同名方法产生: self.__init__(self,*args,**kwargs)
10.__call__需要 “self、位置参数、关键字参数”
11.__call__内执行创建对象和实例化对象,以及最终返回对象
12.__call__需要“self、实例化的参数(位置参数、关键字参数)”,
在这里可以获取到实例化参数做一些自定义操作,比如:不需要初始化init方法,直接将实例化参数遍历
放到空对象中

'''

单例模式

'''
单例模式:  指同一个类多次实例化的结果指向同一个对象
单例模式的几种实现途径

'''
'''1.模块导入: 多个文件导入的同时都是同一个对象'''
from 单例模式.test import pop
from 单例模式.test import pop

'''2.类绑定方法:调用类的类方法得到的都是同一个对象'''
# class People:
#     _instance = None
#
#     def __init__(self, name, age):
#         self.name = name
#         self.age = age
#
#     @classmethod
#     def get_instance(cls, *args, **kwargs):
#         if not cls._instance:
#             cls._instance = cls(*args, **kwargs)
#         return cls._instance
''''''
'''3.函数装饰器'''
# def seting(cls):
#     _instance = None
#
#     def waring(*args, **kwargs):
#         nonlocal _instance
#         if not _instance:
#             _instance = cls(*args, **kwargs)
#         return _instance
#
#     return waring
#
#
# @seting
# class People:
#     def __init__(self, name, age):
#         self.name = name
#         self.age = age
'''4.类的__new__() 和 类绑定方法类似'''
class People():
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __new__(cls, *args, **kwargs):
        # 判断当前类是否有 "_instance" 这属性
        if not hasattr(cls, "_instance"):
            # 如果没有就调用父类的new方法创建一个空对象返回
            cls._instance = super().__new__(cls)
        return cls._instance
'''5.元类'''
# class Mytype(type):
#     def __call__(cls, *args, **kwargs):
#         if not hasattr(cls,"_instance"):
#             # 该步骤可以拆分下面两步
#             # cls._instance = super().__call__(*args, **kwargs)
#             cls._instance = cls.__new__(cls)
#             cls.__init__(cls._instance,*args, **kwargs)
#         return cls._instance
#
# class People(metaclass=Mytype):
#     def __init__(self,name,age):
#         self.name = name
#         self.age = age

if __name__ == '__main__':
    p = People("长发现", 19)
    p2 = People("长发现11", 20)
    print(p, p.__dict__)
    print(p2, p2.__dict__)

机制方法

 私有属性定义

'''
    私有属性:仅供内部使用
        _单下划线开头:声明私有属性,没有真正的私有化,实际外部还可以调用
        __双下划线开头:真正的私有化
'''

私有方法定义 

'''
私有方法
    单下划线开头:声明私有化,实际没有真正的私有化,外部还是可以调用
    双下划线开头:真正的私有化,外部不能调用,如果使用报错
'''
class MyClass:
    # 私有属性
    __name='这是一个私有属性'
    _age = '声明私有,其实外部还是可以调用'

    #私有方法
    def __ask_info__(self):
        print('这是一个私有方法')
    def info(self):
        print('可以调用自己的私有属性和方法')
        self.__ask_info__()
        print('{}'.format( self.__name))
        print('%s'%(self._age))

m = MyClass();
m.info()
print('------------------------------------------')
print(m._age)
可以调用自己的私有属性和方法
这是一个私有方法
这是一个私有属性
声明私有,其实外部还是可以调用
------------------------------------------
声明私有,其实外部还是可以调用

 私有属性和方法的本质:障眼法

class Person:
    __name = "张三"

    def __info(self):
        print("打印私有属性:",self.__name)

p = Person()
#print(p.__name) # 报错 AttributeError: 'Person' object has no attribute '__name'
#print(p.__info())# 私有方法和属性只能在类的内容调用,外部使用会报错
'''那么私有属性的本质是什么?  本质就是一个障眼法,我们可以使用__dict__来查看属性'''

print(Person.__dict__)  #  '_Person__name': '张三'
'''
python 把 __name  转换为==>   _类名__属性名: _Person__name
'''
print(p._Person__name) # 张三
print(p._Person__info)

pycharm 中快捷更换变量名

shift + F6

property 属性的使用

属性的保护问题: 如何限制类属性在初始化时,现在在一个正确的范围?

        

'''
    属性的保护原理
        目的:保护类中的数据,在有效范围内
        
'''

# class Write:
#     def __init__(self, name, age):
#         self.name = name
#         self.age = age
# w = Write("阿珂", 23)
# print(w.name)
# print(w.age)
'''以上这样设计,无法满足保护属性在一个合理的范围,如果需要解决可以使用方法来操作'''


class Write:
    def __init__(self, name, age):
        self.name = name
        self.set_age(age)

    # 设置属性
    def set_age(self, value):
        if value > 80:
            value = 80
        elif value < 20:
            value = 20
        # 私有的实例变量
        self.__age = value

    # 读取属性
    def get_age(self):
        return self.__age

w = Write('阿珂', 19)
print(f'{w.name}的年龄是:',w.get_age()) # 20
'''以上这样设计,就能满足限制属性在一定的范围,不会出现超出的错误,但是显示的不明确有待于进一步优化'''

 针对上方问题的解决代码砸实际使用中不是很方便,如何优化?

    第一步优化 

class Write:

    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 设置属性
    def set_age(self, value):
        if value > 80:
            value = 80
        elif value < 20:
            value = 20
        # 私有的实例变量
        self.__age = value

    # 读取属性
    def get_age(self):
        return self.__age

        '''
       0. 有了property,可以在初始化方法中,按照原来 self.age = age 这样的方式来书写
       1.age首先是一个类变量,它随类的加载而加载,优先于 self.age这个实例变量创建,
       所以当执行到self.age的时候,操作的是一个类变量,而不是一个实例变量
       2.property 是一个类,它建立了self.age和set_age/get_age两个 读取方法之间的联系
       3.如果没有property,属性和读取方法之间将没有关系,self.age属性可以自由设置
    '''
    # 有了这行代码,初始化中的 self.age 就是属性,否则就是实例变量
    age = property(get_age, set_age) 


w = Write("双儿", 19)
w.age = 200  # 本质  w.set_age(200)
print(w.age)
print(w.name)  # 本质: w.get_age()

第二步优化

class Enemy:
    def __init__(self, name, agg, total):
        self.name = name
        self.agg = agg
        self.total = total

    @property
    def agg(self):  # agg = property(agg)
        return self.__agg

    @agg.setter  # agg = agg.setter(agg)
    def agg(self, value):
        if value > 100:
            value = 100
        elif value < 0:
            value = 0
        self.__agg = value

    # agg  = property(get_agg,set_agg)

e =  Enemy("赵云",190,600)
print(e.agg)  # 100

最终优化:快捷键生成并且 有读写属性和只读属性

class Enemy:
    '''
        作用1.读写属性:
            在实例变量读写过程中进行控制(数据验证/修改)
    '''
    def __init__(self, name, agg, total):
        self.name = name
        self.agg = agg
        self.total = total

    #  快捷键   props + 回车 自动生成
    @property
    def age(self):
        return self.__agg

    @age.setter
    def age(self, value):
        self.__agg = value
---------------------------------------------------------------
class Enemy:
    '''
        作用2.只读属性:外部不能修改,只能访问
            
    '''
    def __init__(self, name, agg, total):
        self.name = name
        self.__agg = agg

    #  快捷键   props + 回车 自动生成
    @property
    def age(self):
        return self.__agg

---------------------------------------------------------------
class Enemy:
    '''
        作用3.只写属性:只能修改,不能读取
            
    '''
    def __init__(self, name, agg, total):
        self.name = name
        self.agg = agg

    # 先设置一个类变量
    agg = property()
    # 针对 agg 进行赋值
    @age.setter
    def age(self, value):
        self.__agg = value

实例方法

        1.只能通过对象调用(第一个参数是self:代表对象,简单说就是谁调用就代表谁)

        2.适用场景:方法内部如果需要使用对象的属性或方法时,就定义成对象方法

类方法

        定义:需要先使用装饰器 @classmethod

        第一个参数是cls:代表类本身

        1.可以通过类名调用,也可以通过对象名调用

        2.适用场景:方法内部需要使用类属性或类方法(不需要使用对象属性或对象方法),就定义成类方法

静态方法

        定义:使用装饰器@staticmethod

        1.可以通过类名调用,也可以通过对象名调用

        2.适用场景:方法内部既不需要使用类属性和类方法,也不需要使用对象属性和对象方法,就定义成静态方法

class MyClass:
  def func(self):
    print('---这是实例方法')
  @classmethod
  def func_cla(cls):
   print('-----这是类方法')
  @staticmethod
  def func_sta():
   print('-----这是静态方法')

my = MyClass()
# 实例方法只能使用对象来调用,不能使用类名调用
my.func()
# MyClass.func()  会报错

# 类方法可以使用对象来调用,也可以使用类名来调用
my.func_cla()
MyClass.func_cla()

# 静态方法可以使用对象来调用,也可以使用类名来调用
my.func_sta()
MyClass.func_sta()

继承

        将多个相关类型抽象为一个父类

        父类型统一、约束相关类型的行为

        隔离客户端代码和相关类型变化点之间的关系

查看子类继承的父类有多少:

        类名.__bases__

python中所有类的顶级父类(基类)都是Object,简单说都是继承了Object

# demo类继承了object,demo2继承了demo类
class demo(object):
 pass
class demo2(demo):
 pass

作用:提高开发效率和代码的重用率,把共有的功能放在父类中,子类可以继承父类所有的属性和方法(私有的除外)

class Pthon():
    __price = 12980
    name = '电话'
    def call(self):
        print('打电话')


class Pthon2(Pthon):
    mess = '消息'
    def send_message(self):
        print('发消息')


class Pthon3(Pthon2):
    muss = '音乐'
    def muiss(self):
        print('听音乐')


p = Pthon3()
p.call() #打电话
p.send_message() #发消息
p.muiss() #听音乐
print(p.mess,p.name,p.muss)
print(p.__price) # 私有属性不能继承,如果使用会报错

在继承了父类后,如果父类中需要传参,在实例化子类时也需要传参冲

多继承的执行顺序

'''
多继承的顺序
1.每定义一个类,python都会计算一个方法解析列表:MRO,该列表就是一个基类的线性顺序列表
2.MRO列表通过C3线性算法来实现的,遵循三条基准法则:
    1.子类优先于父类被检查
    2.多个父类会根据他们在列表中的顺序被检查
    3.如果对下一个类存在两个合法的选择,优先选择第一个父类
3.选从对象中查找,在按MRO列表依次对类进行查找,可以使用:类/对象/:mro() 来查看
4.多继承时(非菱形)会按分支依次查找(深度优先),案例如下
5.多继承时(菱形)会按广度优先依次查找
'''
class b:
    def test(self):
        print("这是b对象")
class a:
    def test(self):
        print("这是a对象")
class A(b):
    def test(self):
        print("这是A对象")
class B(a):
    def test(self):
        print("这是B对象")
class C(A):
    def test(self):
        print("这是C对象")
class D(C,B):
    def test(self):
        print("这是D对象")
class E(D):
    def test(self):
        print("这是E对象")

print(E.__mro__)
多继承的缺点:

    1.可读性变差
    2.违背的正常人的思维
    3.可能会出现菱形问题
Mixins 机制:在多继承背景下,尽可能地提升多继承的可读性

    ps:让多继承满足人的思维习惯 ==> 什么 是 什么



'''
Mixins机制小案例

鸡鸭鹅 都属于家禽类,但是鸭鹅都可以游泳,如果将游泳这个功能在家禽类中,势必会导致
鸡也会有游泳功能。那么如何只让鸭鹅有游泳,鸡没有,这就需要单独创建一个mixin类
在语义上表示 鸡鸭鹅只有一个家禽类,而 SwimMixIn 只是一个插入的功能
'''

class Fowl: # 家禽类
    pass

class SwimMixIn:# xxxMixIn  MixIn是后缀
    def swimming(self):
        print("游泳")

class Chicken(Fowl):# 鸡
    pass

class Duck(SwimMixIn,Fowl):# 鸭
    pass

class Doose(SwimMixIn,Fowl):# 鹅
    pass


d =Doose() # 只有鸭 鹅才有游泳 
d.swimming()

 Super执行机制

'''

Super查找属性,不是直接去父类查找,而是要参照对象所属类的Mro列表,且不是从mro列表的第一个类开始找
    而是从mro列表里Super所处类的下一个类开始查找
'''


class A:
    def f1(self):
        print("A.f1 ") # 打印了
        super().f1() # 这个时候执行super,需要参考对象所属类的mro列表
                        # 且从super所处类的下一个类查找,super处在A类中,那么就从下一个B类中查找
                        # 所以执行B的发f1函数
        # [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]


class B:
    def f1(self):
        print("b.f1")


class C(A,B):
    pass


c = C()
c.f1() # 自己类没有,就去父类中找,先去A类中

print(C.mro())
# [<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]

重写方法

如果子类中也有和父类相同的方法时,这就是重写父类方法,优先使用自己的,如果自己没有在使用父类的,例如: __init__方法

class Person:
    def __init__(self,name):
       self.name = name
    def func(self):
        print(f'{self.name}')

class B_Person(Person):
    def __init__(self,name,age): # 在子类中重写了父类的同名方法
        # 方式一:需要再重写的方法中,调用父类的方法,主要要加self
        # Person.__init__(self,name)
        # 第二种调用父类的方法 super()
        super().__init__(name)
        self.age = age
    def send(self):
        print('{}这是子类的消息{}'.format(self.name,self.age))

b  = B_Person('张三',18)
b.send()#张三这是子类的消息18
b.func() #张三

重写父类方法的时候,有两种方式来调用父类的方法

1.类名.,方法名(self,'参数'......):必须要增加self

2.super().方法名(参数)  :不在需要self

重写pyhon的内置函数案例(自定义对象案例)

        1.__str__        : 打印自定义对象的格式(return的一定是字符串)

'''
为什么要重写,因为父类的方法不能满足我们实际需求的时候就要重写

__str__:   默认输出的是对象地址,但是我们如果需要按照自己需要的格式输出,就需要重新

'''
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return f"{self.name}和{self.age}"


p = Person("战三", 19)
# 这个时候打印的就是 p 这个对象的内存地址
print(p)
# 问题: 如果我需要打印出对象的姓名和年龄,怎么办
# 方式一:使用对象.变量
print(p.name, p.age)
# 方式二:重写__str__,返回的就是 自己 __str__方法返回的数据
print(p)

         2.自定义对象中算数运算符的重写

        算数运算符会返回新数据

'''
算数运算符的重写
__add__:  +  加法
__sub__:  - 减法
__mul__:  * 乘法
__truediv__:  / 除法
__floordiv__:  //    地板除法
__mod__:  % 取余
__pow__:  **    幂计算
'''


class Active(object):
    def __init__(self, age):
        self.age = age

    def __add__(self, other):
        # 判断传入的类型,是向量还是数值
        if type(other) == Active:
            return self.age + other.age
        else:
            return self.age + other

    def __sub__(self, other):
        # 判断传入的类型,是向量还是数值
        if type(other) == Active:
            return self.age - other.age
        else:
            return self.age - other

    def __mul__(self, other):
        # 判断传入的类型,是向量还是数值
        if type(other) == Active:
            return self.age * other.age
        else:
            return self.age * other

    def __truediv__(self, other):
        return self.age / other.age

    def __floordiv__(self, other):
        return self.age // other.age

    def __mod__(self, other):
        return self.age // other.age

    def __pow__(self, power, modulo=None):
        return self.age ** power.age


a = Active(11)
b = Active(2)

'''
如果没有重写父类的__sub__方法,这就会报错
重写后,就会使用自己的__sub__方法
'''
print(a+100) # 111
print(a + b)  # 13
print(a - b)  # 9
print(a * b)  # 22
print(a / b)  # 5.5
print(a // b)  # 5
print(a % b)  # 5
print(a ** b)  # 121

3.自定义对象中增强型算数运算符的重写

        会返回旧数据

        

'''
增强型运算符的重写  : 不同于普通运算符返回新数据,这里返回原数据
__iadd__:   +=
__isub__:   -=
__imul__:   *=
__itruediv__:   /=
__ifloordiv__:   //=
__imod__:   %=
__ipow__:   +**=

'''


class Active(object):
    def __init__(self, age):
        self.age = age

    def __add__(self, other):
        # 返回的是新数据
        return self.age + other.age

    def __iadd__(self, other):
        # 返回的是原数据
        self.age += other.age
        return self
'''
如果没有重写 __iadd__(),那么使用的就是__add__()
下面对象a 和 自加后的a 内存地址就不一样
而有了 __iadd__()后,返回的是原数据,
对象a 和 自加后的a 内存地址就一样
'''
a = Active(10)
b = Active(20)
print(id(a))
a += b
print(id(a))

4.自定义对象中比较运算符的重写(如果列表中是自定义对象,需要使用内置方法时,需要重写,例如 max/index/count/ in )

'''
比较运算符
__lt__: <   小于
__le__: <=   小于等于
__gt__: >   大于
__ge__: <   大于等于
__eq__: <   等于
__ne__: <   不等于

'''


class Active:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    # 重写了eq方法,改变了比较方法,只要值想同,那么就相等
    # 决定对象是否相等
    def __eq__(self, other):
        return self.a == other.a and self.b == other.b
    # 重写了__lt__ 方法,改变自定义对象的比较方式
    # 决定对象大小的依据
    def __lt__(self, other):
        if self.a < other.a:
            return self.a < other.a
        elif self.a == other.a:
            return self.b < other.b
# a1 = Active(10, 20)
# a2 = Active(10, 20)
'''
# 都知道是False,因为对象的内存地址不一样
==  默认比较的是 内存地址
'''
# print(a1 == a2)
'''
如果只要对象的值一样,那么就相等,怎么做?
这就要重写 __eq__()  方法,返回的就是True
'''
# print(a1 == a2)
'''
如果有一个自定义对象的列表,需要做以下操作
'''
lis_all = [
    Active(19, 20),
    Active(11, 21),
    Active(9, 22),
    Active(14, 26),
    Active(14, 24),
]
# 在没有重写__eq__()时,比较是内存地址,重写后比较的是 值,所以返回True,之前是False
# print(Active(10, 20) in lis_all)
# 在没有重写__eq__()时,比较是内存地址,重写后比较的是 值,所以返回1,之前是0
# print(lis_all.count(Active(10, 20))) # 1

'''
需要对上面的自定义对象列表做排序,需要重写 __lt__等方法
如果不重写就会报错
当前比较的规则是: 先比较a 如果a相同,在比较b  和sql中的 ordery by 字段 desc ,字段 desc 类似
'''
lis_all.sort()
for i in lis_all:
    print(i.__dict__)
'''
{'a': 9, 'b': 22}
{'a': 11, 'b': 21}
{'a': 14, 'b': 24}  a相同 在比较b 小的放在前面
{'a': 14, 'b': 26}
{'a': 19, 'b': 20
'''

python中的重写和重载的区别?

        1.重写指的是一个类继承了另一个类,而在子类中有和父类中同名且参数、返回类型都相同的方法。那么这种情况下子类中的方法会将父类方法覆盖,这个被称之为方法覆盖,也就是重写了。因为虽然方法的结构没有改变,但是里面的代码可能是完全不同的

2.重载也是类方法的一种表现形式,不同于重写是方法名、参数、返回类型相同,方法重载只要求方法名相同。因为重载指的就是在一个类下面有很多同名函数,但是这些函数的参数以及返回类型都不同,那么在实例化类之后就可以通过调用同名方法但传入不同参数来实现不同的功能

3.python区分和重载和重写的方式就是观察它们的参数,两个方法参数一样并且分别在父类和子类中就是重写,而一个类中方法同名但参数不一样的就是重载

 多态:       

       概念 理解:对父类的一个方法,不同子类有不同反应

        编码时调用父类

        运行时调用子类 ,以重写的方式来实现具体细节

动态属性设置

        1.获取一个类的所有属性

                可以使用类名.__dict__

class Person:
    pass

print(Person.__dict__)

        2.动态设置属性

                setattr(类名(对象),属性名,属性值)

class Person:
    pass
# 把下面字典中的值设置为上面类的属性和属性值
dic = {'name':'张三','age':90,'sex':'男'}
for k,v in dic.items():
    setattr(Person,k,v)

print(Person.__dict__)  #  'name': '张三', 'age': 90, 'sex': '男'

               给对象设置属性

class Person:
    pass
# 把下面字典中的值设置为上面类的属性和属性值
dic = {'name':'张三','age':90,'sex':'男'}
p  = Person()
for k,v in dic.items():
    setattr(p,k,v)

print(p.__dict__)  # {'name': '张三', 'age': 90, 'sex': '男'}

        3.动态获取属性

               值 =  getattr(类名(对象),键,设置一个值不存在的时候显示默认值)

class Person:
    name = '张三'
    age = 19
# 获取属性值,根据输入的键来获取值
key = input('')
try:
    p = getattr(Person,key)
except Exception as e:
    print('输入的键没有值')
else:
    print(p)

        4.动态删除属性

                关键字:del  用来删除数据,不能删除不可变的数据 ,比方说元组,

str = 'zhangqia'
lis = [1,2,3,43,5]
dic = {'a':12,'b':18}
del str
del lis[2]
del dic['a']
print(lis) # [1, 2, 43, 5]
print(dic) # {'b': 18}

               delattr(类名(对象),属性名)

        5.判断属性是否存在 

                hasattr(类名(对象),属性名):返回值是一个布尔值

    多继承-》一个子类继承多个父类

                

class A:
    def info(self):
       print('-------A的方法')
class B:
    def info(self):
        print('---------B的方法')
        
# 可以同时继承多个父类,如果多个父类有相同的方法,会选择第一个父类,自己的方法和父类相同
# 选择自己的方法
class C(A,B):
    def funx(self):
        print('-------C的方法')
c = C()
c.info() #-------A的方法
c.funx() # -------C的方法

多继承的执行顺序:

        相比单继承的深度优先,多继承是广度优先

class A:
    def __init__(self):
        print("A")
        print("A -- end")


class B(A):
    def __init__(self):
        print("B")
        super().__init__()
        print("B -- end")


class C(A):
    def __init__(self):
        print("C")
        super().__init__()
        print("C -- end")


class D(B, C):
    def __init__(self):
        print("D")
        super().__init__()
        print("D -- end")


if __name__ == '__main__':
    d = D()
    # 查看D类的继承顺序
    print(D.__mro__)

D
B
C
A
A -- end
C -- end
B -- end
D -- end

(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

注意: 如果子类的多个父类继承的不是同一个祖类,则按照深度优先执行,如下:

class A:
    def __init__(self):
        print(A)


class B:
    def __init__(self):
        print(B)


class C(B):
    def __init__(self):
        print(C)
        super().__init__()


class D(A):
    def __init__(self):
        print(D)
        super().__init__()


class E(C,D):
    def __init__(self):
        print(E)
        super().__init__()
e = E()
<class '__main__.E'>
<class '__main__.C'>
<class '__main__.B'>

Python 语言结构

        

 模块:可以理解为文件夹

包: 有__init__文件夹的才是包

        1.在第一次被导入的时候执行,对外提供必要的功能

为什么要有包?

        简单说就是代码太多了

        复杂说 : 使结构更加清晰

导入方式:

        import  模块.路径

        from  路径 import 成员  :  from  包.模块  import  类

                需要在 __init__.py  配置

迭代

        迭代的定义: 每一次对过程的重复称为一次“迭代“,而每一次迭代的结果会作为下一次迭代的的初始值

1.可迭代对象iterable

        定义: 具有__iter__函数的对象,可以返回迭代器的对象

class 可迭代对象名称:
    def __iter__(self):
        return 迭代器

#使用

    for 变量名 in 可迭代对象:
            语句

面试: 能够参与for循环的条件是什么?

        答:对象具有__iter__函数,简单说就是能够获取迭代器对象就是可迭代器对象

2.迭代器对象iterator

案例一:

# 可迭代对象:就是对象可以使用__iter__函数
lis = ["张三","李四","王五","赵六","孙七",]

# 第一部:获取得到一个迭代器
iter = lis.__iter__()
while True:
    try:
        # 每次得到下一个
        item =  iter.__next__()
        # 输出列表中的每一个
        print(item)
    except StopIteration:
        # 如果下标溢出,则结束
        break

案例二:

 #利用迭代思想打印字典中的每个键值对
dic = {
    "1": "张三",
    "2": "李四",
    "3": "王五",
    "4": "赵六",
    "5": "孙七",
}

# 得到迭代器对象
iter = dic.__iter__()
while True:
    try:
        item = iter.__next__()
        print(item, dic[item])
    except StopIteration:
        break

 面试题:自定义类实现迭代


# 自定义类实现迭代思想
'''迭代器'''

class StudentIter(object):
    def __init__(self, data):
        self.data = data
        self.start_id = -1

    def __next__(self):
        if self.start_id >= len(self.data)-1:
            raise StopIteration()
        self.start_id += 1
        return self.data[self.start_id]


'''创建自定义类'''

class StuController:
    def __init__(self):
        self.lis_name = []

    def __iter__(self):
        # 返回一个迭代器,传入需要的迭代的列表
        return StudentIter(self.lis_name)


'''实例化类,得到对象'''
controller = StuController()
# 向列表添加数据
controller.lis_name.append("张三")
controller.lis_name.append("李四")
controller.lis_name.append("王五")

# 问题: 如何在自定义类中实现迭代
'''首先对象要有 __iter__函数,返回一个迭代器对象,在类中创建iter()'''
item = controller.__iter__()
while True:
    ''' 然后 迭代器中要有一个 __next__() 方法'''
    try:
        parame = item.__next__()
        print(parame)
    except StopIteration:
        break




'''
第二次手写方案
'''



# 自定义可迭代对象
class MyIterable:
    def __init__(self, data):
        self.__data = data

    def __iter__(self):
        return MyIterator(self.__data)

# 自定义迭代器
class MyIterator:
    def __init__(self, data):
        self.__data = data
        self.__index = 0

    def __iter__(self):
        return self

    def __next__(self):
        try:
            value = self.__data[self.__index]
            self.__index += 1
        except:
            raise StopIteration
        return value


itab = MyIterable("1234")
print(itab)
print(isinstance(itab,Iterable))
for i in itab:
    print(i)

可迭代对象和迭代器对象的总结:

1.字符串、列表、元组、字典、集合、文件都是可迭代对象,他们都具有__iter__()方法
2.文件同时具有__next__() 方法,所以在调用next()后就变成迭代器对象
3.迭代器对象也具有__iter__()方法,返回的还是迭代器对象本身,这是为了for循环来做的处理
4.综上,一个对象是迭代器对象那么肯定是可迭代对象,反之,可迭代对象不一定是迭代器对象

生成器generator(自定义迭代器)

定义:能够动态提供数据的可迭代对象

理解:是可迭代对象又是迭代器对象的就是生成器

核心: 生成器的核心是迭代器

作用: 在循环过程中,按照某种算法推算数据,不必创建容器存储完成的结果,从而节省内存空间,数据量越大,优势越明显,通俗的讲就是在需要的时候才计算结果,而不是一次构建所有结果

0.使用迭代器和生成器的思想推算range()函数

# 迭代器思想实现简单的range()

class Ran:
    def __init__(self, a):
        # 传入的数
        self._a = a
        # 自定义的数
        self.start_id = -1

    def __next__(self): # 有next函数所有是迭代器
        # 如果start_id+1 大于或等于传入的数,就报错
        if self.start_id + 1 >= self._a:
            raise StopIteration
        # 否则自加后返回
        self.start_id += 1
        return self.start_id 


class MyRange: 
    def __init__(self, a):
        self._a = a

    def __iter__(self): # 有iter函数所以是一个可迭代对象
        return Ran(self._a) # 调用迭代器

if __name__ == '__main__':

    myrange = MyRange(5)
    obj = myrange.__iter__()
    while True:
        try:
            item = obj.__next__()
            print(item)
        except StopIteration:
            break



----------------------简单的生成器版本: 将迭代器的类删除,使用yield---------------------------------
# 生成器思想实现range()
# 使用yield ,不需要创建迭代器类

class MyRange:
    def __init__(self, a):
        self._a = a

    def __iter__(self):
        end = 0
        while end < self._a:
            yield end
            end +=1


if __name__ == '__main__':

    myrange = MyRange(5)
    obj = myrange.__iter__()
    while True:
        try:
            item = obj.__next__()
            print(item)
        except StopIteration:
            break
-----------------------目前的完成版本:将类变成一个函数-----------------------------------
def myrange( a):
    end = 0
    while end < a:
        yield end
        end += 1


if __name__ == '__main__':

    mr = myrange(5)
    obj = mr.__iter__()
    while True:
        try:
            item = obj.__next__()
            print(item)
        except StopIteration:
            break

# 生成器的伪代码
    # 简单理解
    class generator:
        def __iter__(self):
            return self
        def __next__(self):
            # 做数据处理
            pass

1.生成器函数:含有yield 语句的函数,返回值为生成器对象

----------------------创建-----------------------------------

def 函数名():
    yield 数据

#调用
 for 变量 in 函数名():
    语句

------------------------使用------------------------------

lis2 = [43, "我有空", "八戒", "唐僧", 89, "98", 0,12.43]


def get_numer(lis):
    for item in lis:
        if isinstance(item,(int,float)):
            yield item

num = get_numer(lis2) # 不执行函数体中的代码,只返回生成器对象
for item in num:
    print(item)

生成器执行的过程:

        1.调用生成器函数会自动创建迭代器对象

        2.调用得带器对象的next()方法才会执行生成器函数

        3. 每次执行到yield语句时返回数据,暂时离开

        4.等待下次调用next()方式时继续从离开处继续执行

生成器对象规则:

        1.将yield关键字以前的代码放到next方法中

        2.将yield关键字后面的数据作为next方法的返回值

        3.可以为yield赋值

def dog(name):
    print("dog准备吃饭了》》》》!")
    while True:
        x = yield
        print("dog--%s今天天吃的是%s"%(name,x))
'''
    send():必须给值
'''
d = dog("alex") # 得到生成器
# next(d)
d.send(None)# 等同于 next(d),代码同样在遇到yield会挂起
d.send("稀饭")# 先赋值给X,在执行后面的代码,遇到yield后挂起
d.send("骨头")

2.内置生成器

        1.枚举函数 enumerate

# 1.将列表中所有奇数设置为None
lis = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


def get_odd(lis):
    # i 是索引  item是元素
    for i, item in enumerate(lis):
        if item % 2 != 0:
            lis[i] = 0


# get_odd(lis)
# print(lis)


# 2.将列表中所有偶数自增1
def get_even(lis):
    for i, item in enumerate(lis):
        if item % 2 == 0:
            lis[i] += 1
get_even(lis)
print(lis)

        2.zip:压缩


lis = [1,2,3,4,5]
lis2 = ["a","b","c","d"]
for item in zip(lis,lis2):
    print(item)
    # (1, 'a')
    # (2, 'b')
    # (3, 'c')
    # (4, 'd')


---------------------------案例2------------------------------------
class Student:
    def __init__(self, name="", age=0, sex=""):
        self.name = name
        self.age = age
        self.sex = sex

    def __str__(self):
        return f"{self.name},{self.age},{self.sex}"


lis1 = [28, 29, 20]
lis2 = ["悟空", "八戒", "悟净"]
lis3 = ["男", "男", "男"]
list_stu = []
for item in zip(lis2, lis1, lis3):
    stu = Student(*item)
    list_stu.append(stu)
print(list_stu[0])

list_stu2 = [Student(*item) for item in zip(lis2, lis1, lis3)]
print(list_stu2[0])

3.生成器表达式

        可以理解为 是列表推导式的“升级版”(节省内存)

nums = [12, 3, 4, 4, 4, 6, 7, 8]
# 将列表推导式的中括号,改变成小括号就是生成器表达式
generator = ( item for item in nums if item %2==0)
print(generator) #<generator object <genexpr> at 0x000001A8FD2BBCF0>
for item in generator: # 打印出偶数
    print(item)

4.生成器和列表的优缺点

        列表

                优点:操作数据方便

                缺点:占用内存较多

        生成器

                优点:占用内存较少,几乎忽略

                缺点:操作数据不方便(不能使用索引/切片,不能删除修改)

        适用性:

                从头到尾读取一次

        注意: 实际使用中,优先使用生成器,如果需要对数据进行操作,可以使用

                list(生成器)

                tuple(生成器)

                set(生成器)

5.生成器yielf和return的区别

        

相同点: yield和return都可以返回值
不同点: 
    1.return 返回值后就会结束函数,yield 返回值会挂起函数,不会终止函数,等待下次next调用
    2. yield 可以使用send() 方法接收值,return不能

6.各推导式(生成式)

        

'''
列表生成式:
集合生成式:
字典生成式
生成器生成式
'''
lis = [ i for i in range(10) if i % 2 ==0]
print(lis)
se = { i for i in range(19) }# 没有if 就相当于 if True
print(se)
dic = {k:None for k in lis}
print(dic)

genter = (i for i  in range(8))
print(genter) # 得到一个生成器

print(list(genter))

递归:直接或间接调用自己

     

import sys
sys.setrecursionlimit(100000)# 调整递归的层级,默然是1000层

'''递归实现菲波那切数列'''
def feibo(num):
    if num == 1 or num == 2:
        return 1
    else:
        return feibo(num - 1) + feibo(num - 2)

print(feibo(10))

'''
循环实现斐波那契
'''
def feibo(num):
    index = 0
    a, b = 0, 1
    for i in range(num):
        index += 1
        print(f"第{index}次:", b)
        a, b = b, b + a
# feibo(9)

'''
yield 实现斐波那契
'''
def feibo_yield(num):
    a,b = 0,1
    while num >0:
        yield  b
        a,b = b,b+a
        num-=1
f = feibo_yield(9)
for i in f:
    print(i)





'''
二分法思想:求一个数是否在一个列表中


'''


lis = [1,23,98,23]
lis.sort()
print(lis)
print("中间值是:",lis[len(lis)//2])
def two_create_li(index,data):
    if not len(data):# 判断是否是空列表,如果是则直接退出
        print('值不存在')
        return
    # 1.获取列表程度,地板除2,得到中间值
    num = len(data)//2
    # 2.判断是否大于中间值,
    if index > data[num]:
    #    如果大于则得到中间值之后的所有数据
        data = data[num+1:] # 如果查询的值不存在,这里会返回空列表
        print("大于:",data)
        two_create_li(index,data)
    # 3.小于中间值
    elif index< data[num]:
    #     得到中间值之前的所有数据
        data = data[:num]
        print("小于:",data)
        two_create_li(index, data)
    # 4.如果相等
    elif index == data[num]:
    #     则输出找到了
        print("找到了")
two_create_li(98,lis)

     全排序和二分法的小案例


# 全排序一
lis = 'abc'


def pwor(lis, path='', result=[]):
    if len(lis) == 0:
        result.append(path)
        return
    for char in lis:
        mning = lis.replace(char, '', 1)
        pwor(mning, path + char, result)

    return result


# print(pwor(lis))

# 全排序二


def pwryf(liss, lever=0):
    if len(liss) == lever:
        print(liss)
    for i in range(lever, len(liss)):
        liss[lever], liss[i] = liss[i], liss[lever]
        pwryf(liss, lever + 1)
        liss[lever], liss[i] = liss[i], liss[lever]


# pwryf(list('abc'), 0)

# 二分法

lis = [4, 5, 2, 3, 8, 6, 9, 45, 9, 4, 60,128,119,90,32,89]
lis = list(set(lis))
lis.sort()
print(lis)


def lis_two(lis, index):
    left, right = 0, len(lis)-1
    while left <= right:
        mid = (left + right) // 2
        if lis[mid] == index:
            return mid ,lis[mid] # 返回 索引
        elif lis[mid] < index:
            left = mid + 1
        else:  # lis[mid] < index
            right = mid - 1
    return -1


print(lis_two(lis, 60))

  

函数式编程

        定义: 用一系列函数解决问题,将函数作为参数传入到另外一个函数

函数作为参数

        0.高阶函数

                定义:参数是函数的函数或返回值是函数的函数,成为高阶函数

                

class IterAbleTools:
    @staticmethod
    def get_sign(lis_app, func):
        '''
        返回一个符合条件的数据
        :param lis_app: 可迭代对象
        :param func: 函数类型的参数
        :return: 一个数据
        '''
        for item in lis_app:
            if func(item):
                return item

    @staticmethod
    def get_all(lis_app, func):
        '''
        返回多个符合条件的数据
        :param lis_app: 可迭代对象
        :param func: 函数类型的参数
        :return: 生成器
        '''
        for item in lis_app:
            if func(item):
                yield item
-----------------------------引用--------------------------------------

from comon.iterable_tools import IterAbleTools


class StudentController:
    def __init__(self, name="", age=0, sex=""):
        self.name = name
        self.age = age
        self.sex = sex


lis_app = [
    StudentController("唐僧", 89, "男"),
    StudentController("孙悟空", 890, "男"),
    StudentController("小贝龙", 129, "男"),
    StudentController("租八戒", 1893, "男"),
    StudentController("沙和尚", 1893, "男"),
]


def sign(item):
    return item.age == 89

def all(item):
    return item.age == 1893

print(IterAbleTools.get_sign(lis_app, sign).__dict__)
for item in IterAbleTools.get_all(lis_app, all):
    print(item.__dict__)

        1.lambda表达式

                lambda  参数:返回值

lis_li = [1, 2, 3, 4, 5, 76, 8, 9, 65, 100]

print(IterAbleTools.get_sign(lis_li, lambda item: item % 2 == 0)) # 2
print(list(IterAbleTools.get_all(lis_li, lambda item: item % 2 == 0))) # [2, 4, 76, 8, 100]

        2.内置高阶函数

                map():f返回序列中的值,

                filter()

                sorted()

        

class StudentController:
    def __init__(self, name="", age=0, sex="", salary=0):
        self.name = name
        self.age = age
        self.sex = sex
        self.salary = salary


lis_app = [
    StudentController("唐僧", 89, "男", 19092),
    StudentController("唐僧", 89, "男", 22092),
    StudentController("孙悟空", 890, "男", 178272),
    StudentController("小贝龙", 129, "男", 23092),
    StudentController("租八戒", 1893, "男", 69092),
    StudentController("沙和尚", 1893, "男", 70092),
    StudentController("黑熊精", 1093, "男", 29092),
    StudentController("白骨精", 1293, "男", 29092),
]

# 得到序列中每个对象的名字
for item in map(lambda item: item.name, lis_app):
    print(item)
# 过滤  得到序列中薪水大于30000的数据
for item in filter(lambda item: item.salary > 30000, lis_app):
    print(item.__dict__)
# 排序 返回一个新对象
for item in sorted(lis_app, key=lambda item: item.age,reverse=False):
    print(item.__dict__)

                        

函数作为返回值

        1.闭包三要素

        内嵌一个函数

        内嵌函数必须引用外部函数中的变量

        外部函数返回值必须是内嵌函数 

        

def verfiy_permissions():
    print("验证权限")


def insert():
    print("插入")


def delete():
    print("删除")


def func(insert):
    def verfiy_permissions():
        print("验证权限")
        insert()

    return verfiy_permissions


def func2(delete):
    def verfiy_permissions():
        print("验证权限")
        delete()

    return verfiy_permissions


insert = func(insert)
delete = func2(delete)

insert()
delete()



func2.__name__:返回的时候函数名
func2.__doc__: 返回的是函数注释说明信息

装饰器: 使用闭包的思想

        在不改变旧功能的情况下,在新功能上增加新功能

'''
使用@sax 装饰器的时候干了三件事
1.调用外函数
2.将 下面的函数(func函数) 作为参数传入外函数
3.将外函数的返回值重新给下面的函数(func函数)赋值

'''

'''自定义装饰器--- 使用闭包思想'''
# 外函数
def sax(func):
    # 内函数
    def sex(*args):
        print("新功能")
        func(*args)
    return sex

# func = sax(func) 这么写实际应用中复杂,直接写成装饰器的样式

# 装饰器的样式直接调用上面的函数
'''
使用@sax 装饰器的时候干了三件事
1.调用外函数
2.将 下面的函数(func函数) 作为参数传入外函数
3.将外函数的返回值重新给下面的函数(func函数)赋值

'''
@sax
def func(p1, p2):
    print("旧的功能", p1, p2)




func(10, 20)
# 新功能
# 旧的功能 10 20

装饰器深入

'''
装饰器的深入

需求:warpprt函数伪装成index,使用index.__xxx__得到的值和原值一样
__name__: 获取函数名
__doc__:获取函数注释
'''
# 方式一 : 手动调用,缺点:属性太多,手动调用麻烦且有遗漏
def howper(func):
    def warpper(*args,**kwargs):
        '''warpper的主页'''
        func()
    # 手动将warpper属性值换位原函数值
    warpper.__name__ = func.__name__
    warpper.__doc__ = func.__doc__
    return warpper

@howper  # index=howper(index)  index指向的是warpper的内存地址
def index():
    '''index主页功能'''
    print("这里是index")
# 调用前:
print(index.__name__) # warpper
print(index.__doc__) # warpper的主页
# 偷梁换柱: 将__name__和__doc__的值换成原函数的值
# 在warpper手动更换
# 调用后
print(index.__name__) # index
print(index.__doc__) # index主页功能

# 方式二: 自动调用 functools 下的wraps
from functools import wraps
def howper(func):
    @wraps(func)# 为warpper增加装饰器并传入原函数
    def warpper(*args,**kwargs):
        '''warpper的主页'''
        func()
    return warpper

@howper  # index=howper(index)  index指向的是warpper的内存地址
def index():
    '''index主页功能'''
    print("这里是index")
# 调用后
print(index.__name__) # index
print(index.__doc__) # index主页功能

有参装饰器

'''
有参装饰器
---》在原装饰器上在增加一层
需求: 在index上增加一层验证后,再次增加参数传递
'''
def deco(file,n,p):
    def auto(func):
        def wrapper(*args,**kwargs):
            name = input("请输入账号:").strip()
            password = input("请输入密码:").strip()
            if file == "file":
                print("从文件获取的信息")
                if name == n and password == p:
                    func(*args,**kwargs)  #  index(111,222)
                else:
                    print("账号密码错误")
            elif file =="mysql":
                print("从mysql获取的信息")
                if name == name and password == password:
                    func(*args, **kwargs)
                else:
                    print("账号密码错误")
        return wrapper
    return auto


@deco("mysql","zq","123") # 根据参数不同,打印不同信息
def index(x,y):
    print("这里是X{},Y是{}".format(x,y))

# auto = deco("mysql","zq","123")
# index = auto(index)
# index(111,222)

index(111,222)

多个装饰器的加载和运行顺序

'''
总结:
    多个装饰器的加载及执行顺序:
        加载:自下而上
        执行:自上而下
'''
def auth1(func):# func = auth2.wrapper2的内存地址
    def wrapper1(*args,**kwargs):
            print("这里是auth1.wrapper1==》开始")
            func(*args,**kwargs) # # func = auth2.wrapper2的内存地址
            print("这里是auth1.wrapper1==》结束")
    return wrapper1
def auth2(func): # func = auth3.deco.wrapper3的内存地址
    def wrapper2(*args,**kwargs):
            print("这里是auth2.wrapper2==》开始")
            func(*args,**kwargs) # # func = auth3.deco.wrapper3的内存地址
            print("这里是auth2.wrapper2==>结束")
    return wrapper2

def auth3(x):
    def deco(func): # func = index(11,22)
        def wrapper3(*args,**kwargs):
                print("这里是auth3.deco.wrapper3==>开始")
                func(*args,**kwargs) #index(11,22)
                print("这里是auth3.deco.wrapper3==>结束")
        return wrapper3
    return  deco

@auth1     # index=auth1(wrapper2的内存地址) ==> index =wrapper1的内存地址
@auth2     # index = auth2(wrapper3的内存地址)==>index=wrapper2的内存地址
@auth3(10) # index = auth3(10)==>index=deco(index) ==> index=wrapper3的内存地址
def index(x,y):
    print(f"这里是index函数的{x}和{y}")

index(11,22)

python类型检测标注:为变量增加类型

class Student:
    def __init__(self, data, age=0):
        self.age: int = age  # self.age 标注类型,也可以使用下方的语法
        self.data = data  # type:int
        self.data2 =[] # type:List[str]   alt+回车 导入typing.List: 标注列表中数据的类型

    def funx(self, p: str) -> int:
        '''参数是字符串类型,返回值是int类型'''
        pass

内置模块

1.sys 系统模块

import sys
'''
sys模块是与python解释器交互的一个接口
'''
# 值是一个列表,存储一系列文件夹,导入模块按列表顺序查询
print(sys.path)
# 返回操作系统平台名称
print(sys.platform)
#返回系统默认编码
print(sys.getdefaultencoding())
# 返回已加载到内存的模块
print(sys.modules)

2.time 时间模块

import time
#一. 时间戳 1970年至今的秒数
s = time.time()
# print(s,type(s)) # float 类型
# 二.格式化字符串形式
ss = time.strftime("%Y-%m-%d %X")
# print(ss)
# 三.结构化时间:获取元组形式的时间
sss = time.localtime()
# print(sss)

'''
以上三种格式之间如何相互转化

结构化时间==》时间戳: mktime()
时间戳==》结构化时间:localtime()
格式化字符串==》结构化时间:strptime()
结构化时间==》格式化字符串:strftime()
'''
print(time.mktime(sss))
print(time.localtime(s))
print(time.strftime("%Y-%m-%d %H:%M:%S",time.localtime()))
print(time.strptime("2023-04-13 14:18:47",'%Y-%m-%d %X'))

3.os 路径操作

import os

print(__file__)#显示当前文件的位置
print(os.path.dirname(__file__))# 显示当前文件的上一层==》爸爸
print(os.path.dirname(os.path.dirname(__file__)))# 显示当前文件的上上一层==》 爷爷
print("当前文件的文件夹:",os.getcwd())
print("可生成多层递归目录:",os.makedirs(r"aaa/bbbb/ccc"))
print("生成单级目录:",os.mkdir(r"tokces"))
print("删除单级空目录,若目录不为空则无法删除:",os.rmdir(r"tokce"))
print("显示目录下的所有文件和子目录:",os.listdir(r"G:\vue\ck_30\test\demo\aaa"))
print("删除一个文件:", os.remove(r"G:\vue\ck_30\test\demo\1.jpg"))
print("重命名文件或目录名:",os.rename(r".\aaa\ccc",r".\aaa\cccfg"))
print("获取文件/目录信息:",os.stat(r"G:\vue\ck_30\test\demo\13.迭代器.py"))
print("获取当前运行的平台名称:",os.name)
print("获取path的绝对路径:",os.path.abspath(r"G:\vue\ck_30\test\demo\常用内置函数.py"))
print("将path分割成目录和文件名两部分:",os.path.split(r"G:\vue\ck_30\test\demo\常用内置函数.py"))
print("获取path最后的文件名:",os.path.basename(r"G:\vue\ck_30\test\demo\常用内置函数.py"))
print("判断path路径是否存在,不存在返回false:",os.path.exists(r"G:\vue\ck_30\test\demo"))
print("判断是否是一个文件:",os.path.isfile(r"G:\vue\ck_30\test\demo\常用内置函数.py"))
print("判断是否是一个目录:",os.path.isdir(r"G:\vue\ck_30\test\demo"))
print("路径合并:",os.path.join(r"G:\vue\ck_30\test\demo","index.py"))
print("显示文件的大小--》字节数:",os.path.getsize(".\常用内置函数.py"))
print('将文件拆分为文件名和扩展名两部分',os.path.splitext('2024_08_06.xlsx'))#('2024_8_6', '.xls')



'''获取文件的大小'''
def file_size(path):
    res = os.listdir(path)
    count = 0
    for i in res:
        # 如果是一个目录,则递归
        if os.path.isdir(i):
            file_size(i)
        # 如果是一个文件则计算文件大小累加
        res = os.path.getsize(i)
        count+=res
    return count

4.random 随机数

import random
lis = ["a","b","c","d","e","f","g","h",[1,2],[3,4],[7,8]]
print(random.random())# 生成大于等于0,小于1的数
print(random.randint(1,3))# 生成大于等于1,小于等于3
print(random.randrange(1,3))# 生成大于等于1,小于3
print("choice:",random.choice(lis)) # 随机选取一个数
print("choices:",random.choices(lis))# 随机选取一个数,组成列表
print("sample:",random.sample(lis,3))# 随机获取3个数
print("uniform:",random.uniform(1,9))# 1-9之间的小数
print("自定义小数:",random.randint(1,9)+random.random())

random.shuffle(lis)
print("打乱顺序:",lis)
'''
 案例:随机验证码
'''
def make_doc(n):
    res = ""
    for i in range(n):
        sr = chr(random.randint(65,90))#阿斯克码表 随机选择一个大写字母
        it = str(random.randint(0,9))
        res += random.choice([sr,it])
    return res


print("随机验证码:",make_doc(6))

4.shutil 文件操作

import shutil
'''1.拷贝文件--将一个文件内容复制到另外一个文件中'''
# shutil.copyfileobj(open('1.txt',"r"),open("2.txt","w"))

'''2.拷贝文件,目标文件无需存在'''
shutil.copyfile("1.txt","2.txt")
'''3.仅仅拷贝文件权限,内容、组、用户不变:目标文件必须存在'''
shutil.copymode("1.txt","2.txt")
'''4.仅仅拷贝文件状态信息,目标文件必须存在'''
shutil.copystat("1.txt","2.txt")
'''5.拷贝文件和权限'''
shutil.copy("1.txt","2.txt")

'''6.拷贝文件夹--》 如果存在 pyc结尾或tmp开头就不拷贝'''
shutil.copytree('aaa', 'bbb', symlinks=True, ignore=shutil.ignore_patterns('*.pyc', 'tmp*'))

'''7.移动文件/重命名'''
shutil.move("路径1","路径2")
'''8.压缩和解压缩'''
shutil.make_archive()

5.json序列化和反序列化

import json
# 序列化
res = json.dumps([1,"aaa",True,False,{"aaa":122,"bbb":567}])
print(res,type(res))# 结果为 str
# 反序列化
l  = json.loads(res)
print(l,type(l)) # 结果为 list
'''序列化和反序列化的简单使用'''
with open("1.json",mode="wt",encoding="utf-8") as f:
    # 将数据序列化写入到文件
    json.dump([1,"aaa",True,False,{"aaa":122,"bbb":567}],f)

with open("1.json",mode="rt",encoding="utf-8") as f:
    # 读取文件内容,并进行反序列化操作
    res = json.load(f)
    print(res,type(res))

'''补充'''
不能识别集合类型

6.configparser 配置文件操作

import configparser

config = configparser.ConfigParser()
config.read("1.ini")
'''1.查看所有标题'''
print(config.sections()) # ['section1', 'section2']
'''2.查看某个标题下所有key'''
print(config.options("section1")) # ['key', 'value', 'url']
'''3.查看某个标题下所有key,value'''
print(config.items("section1")) # [('key', '10'), ('value', '"寮犱笁"'), ('url', '"https://www.baidu.com"')]
'''4.查看某个key的值'''
print(config.get("section1","url")) # "https://www.baidu.com"
'''5.查看整形、浮点型、布尔型的方法'''
config.getint()
config.getfloat()
config.getboolean()

7.hashlib 加密模块

'''
hash:是一种算法,hashlib模块提供包括MD5/SHA256/SHA224等方式
hash值的特点:
    1.只要传入的内容一样,那么hash值一定一样
    2.hash值不能被返解内容
    3.只要hash算法不变,不管校验的内容多大,hash值长度是固定的
'''
import hashlib
m = hashlib.md5()
# 多次添加
m.update("张三".encode("gbk"))# window 默认 GBK Linux默认utf-8
m.update("LISI".encode("gbk"))
print(m.hexdigest())
#是使用其他加密方法
m = hashlib.sha3_224()
m.update("张三".encode("gbk"))
print(m.hexdigest())
# 简写模式
f = hashlib.md5("张三LISI".encode("gbk"))
print(f.hexdigest())
'''
以上加密算法虽然依然非常厉害,但时候存在缺陷,
即:通过撞库可以反解。所以,有必要对加密算法中添加自定义key再来做加密
'''
import hmac
# 指定key为123
h = hmac.new("123".encode("gbk"),digestmod="md5")
h.update("w".encode("gbk"))
h.update("orld".encode("gbk"))
print("二进制加密信息:",h.digest())
print("十六进制加密信息:",h.hexdigest())
# 简写
f = hmac.new(b"123",b"world",digestmod="md5")
print("简写信息:",f.hexdigest())

'''
密码加盐: 在需要加密的数据上添加数据,增加破解难度
代码的思路: 在文件中开头读取2个字符串,结尾读取2个字符串,在和需要加密的数据混合一起在加密
'''
lis=[]
with open("1.txt",mode="rb") as f:
    f.seek(100,0)
    re = f.read(8)
    print(f.tell())
    lis.append(re.decode("utf-8","ignore"))

    f.seek(1000, 2)
    re = f.read(8)
    print(f.tell())
    lis.append(re.decode("utf-8", "ignore"))
print(lis)
import hashlib
mm =hashlib.md5(b"hello")
print("没有加盐前的信息:",mm.hexdigest())
ff = hashlib.md5(lis[0].encode("utf-8")+b"hello"+lis[1].encode("utf-8"))
print("密码加盐后的信息:",ff.hexdigest())

8.logging日志模块

'''
logging 基础理解
'''
import logging
# 创建日志对象
logg = logging.getLogger(__name__)
logg.setLevel("WARNING")# 只产生warning级别以上的日志

# 创建输出对象Handler
f1 = logging.FileHandler("a1.log")
f1.setLevel("INFO")# 只输出info级别以上的日志
f2 = logging.StreamHandler()
f2.setLevel("ERROR")# 只输出error以上的日志
# 创建日志格式对象
lf = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s -%(module)s:  %(message)s')

# 将输出对象和格式对象绑定
f1.setFormatter(lf)
f2.setFormatter(lf)
# 日志对象和输出对象绑定
logg.addHandler(f1)
logg.addHandler(f2)

# 调用logg.debug()等方法使用
logg.debug("debug级别")
logg.info("info级别")
logg.warning("warning级别")
logg.error("error级别")
logg.critical("critical级别")















'''实际使用方式--使用日志字典'''
'''
指定几种输出日志格式,给后面调用
'''
standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \
                  '[%(levelname)s][%(message)s]'

simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'

test_format = '%(asctime)s] %(message)s'

'''
 日志配置字典
'''
LOGGING_DIC = {
    'version': 1,  # 版本信息,非必写
    # 多个日志格式,调用上面生成的三个,“standard”名字可以自定义
    'formatters': {
        'standard': {
            'format': standard_format
        },
        'simple': {
            'format': simple_format
        },
        'test': {
            'format': test_format
        },
    },
    'filters': {},
    # handles 是日志的接收者,负责将收到的日志信息输出到不同的位置(控制台或文本)
    'handlers': {  # standard,default等可以自定义
        # 打印到终端的日志
        'standard': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',  # 打印到屏幕
            'formatter': 'simple'
        },
        # 打印到文件的日志,收集info及以上的日志
        'default': {
            'level': 'DEBUG',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,日志轮转
            'formatter': 'standard',
            # 可以定制日志文件路径
            # BASE_DIR = os.path.dirname(os.path.abspath(__file__))  # log文件的目录
            # LOG_PATH = os.path.join(BASE_DIR,'a1.log')
            'filename': 'a1.log',  # 日志文件
            'maxBytes': 1024 * 1024 * 5,  # 日志大小 5M
            'backupCount': 5,  # 只保留5个文件
            'encoding': 'utf-8',  # 日志文件的编码,再也不用担心中文log乱码了
        },
        'other': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',  # 保存到文件
            'formatter': 'test',
            'filename': 'a2.log',  # 文件路径
            'encoding': 'utf-8',
        },
    },
    # loggers是日志的生产者,负责生产不同类型的日志
    'loggers': {
        # logging.getLogger(__name__)拿到的logger配置
        '专门采集': {
            'handlers': ['default', 'standard'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕
            'level': 'DEBUG',  # loggers(第一层日志级别关限制)--->handlers(第二层日志级别关卡限制)
            'propagate': False,  # 默认为True,向上(更高level的logger)传递,通常设置为False即可,否则会一份日志向上层层传递
        },
        '': {  # 没有key,在调用的时候不写,就调用这个
            'handlers': ['other', ],
            'level': 'DEBUG',
            'propagate': False,
        },
    }
}

from logging import config, getLogger

config.dictConfig(LOGGING_DIC)
logging1 = getLogger('专门采集')
logging1.debug("这是一个dug日志")

完整

# 日志配置字典
LOGGING_DIC = {
    # 日志版本
    'version': 1.0,
    # 是否禁用
    'disable_existing_loggers': False,
    # 日志格式
    'formatters': {
        # 第一种:完整
        'standard': {
            'format': '%(asctime)s %(threadName)s:%(thread)d [%(name)s] %(levelname)s [%(pathname)s:%(lineno)d] %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
        # 第二种:简洁
        'simple': {
            'format': '%(asctime)s [%(name)s] %(levelname)s  %(message)s',
            'datefmt': '%Y-%m-%d %H:%M:%S',
        },
        # 第二种:简单
        'test': {
            'format': '%(asctime)s %(message)s',
        }
    },
    # 过滤器
    'filters': {},
    # 日志处理器
    'handlers': {
        #,输出到控制台的debug级别的日志处理器
        'console_debug_handler': {
            'level': 'DEBUG',  # 日志处理的级别限制
            'class': 'logging.StreamHandler',  # 输出到终端
            'formatter': 'standard'  # 日志格式
        },
        #写入文件的info级别的日志处理器
        'file_info_handler': {
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',  # 保存到文件,日志轮转
            'filename': './logg_text/abc.log',       # 日志存放路径
            'maxBytes': 1024,  # 日志大小 1024kb
            'backupCount': 100,  # 日志文件保存数量限制
            'encoding': 'utf-8',    # 日志文件编码
            'formatter': 'standard',# 日志格式
        },
        #写入文件的debug级别的日志处理器
        'file_debug_handler': {
            'level': 'DEBUG',
            'class': 'logging.FileHandler',  # 保存到文件
            'filename': './logg_text/test.log',  # 日志存放的路径
            'encoding': 'utf-8',  # 日志文件的编码
            'formatter': 'test',
        },
    },
    # 日志记录器
    'loggers': {
        'logger1': {  # 导入时logging.getLogger时使用的app_name
            'handlers': ['console_debug_handler'],  # 日志分配到哪个handlers中
            'level': 'DEBUG',  # 日志记录的级别限制
            'propagate': False,  # 默认为True,向上(更高级别的logger)传递,设置为False即可,否则会一份日志向上层层传递
        },
        'logger2': {
            'handlers': ['console_debug_handler', 'file_debug_handler'],
            'level': 'INFO',
            'propagate': False,
        },
        '': {
            'handlers': ['console_debug_handler', 'file_info_handler'],
            'level': 'INFO',
            'propagate': False,
        },
    }
}

if __name__ == '__main__':
    # 1.导入包
    import logging.config
    # 2.将配置字典载入模块中
    logging.config.dictConfig(LOGGING_DIC)
    # 3.日志记录器
    log = logging.getLogger('日志处理的日常')
    log.info("记录测试日志---")

unittest

  1.unittest的核心概念:

        TestCase:一个TestCase实例就是一个测试用例

        TestSuite:多个测试用例集合在一起。TestLoader:用来加载TestCase到TestSite中

        TextTestRunner:用来执行测试用例

        fixtrue:测试用例环境的搭建和销毁,测试前搭建环境setUp,执行测试代码run,测试后环境还原teardown

        TestLoader:

2.编写规范

        1.定义一个测试用例,必须继承unittest中的TestCase

        2.测试用例类中,一个test开头的方法就是一个测试用例

        3.将测试用例的执行的代码写入到对应的测试方法中

                 步骤:1.准备用例数据

                             2.调用接口

                              3.断言

import unittest

class TestLogin(unittest.TestCase):

    def test01(self):
       assert  1==1




import  unittest
# 1.创建测试套件,加载测试用例
suite = unittest.defaultTestLoader.discover(r'G:\vue\9-30YKY_Project\unittest_demo\tetstcase')
# 创建一个测试程序
runer = unittest.TextTestRunner()

# 运行测试用例
runer.run(suite)

PyPi:python的第三方库,所有第三方插件都在这里,使用 pip install '插件或库名'

unittestreport:

unittestreport是基于unittest开发的的一个功能扩展库

  • HTML测试报告生成
  • unittest数据驱动
  • 测试用例失败重运行
  • 多线程并发执行用例
  • 发送测试结果及报告到邮箱
  • 测试结果推送到钉钉
  • 测试结构推送到企业微信
import unittest
from unittestreport import TestRunner
suite = unittest.defaultTestLoader.discover(r'G:\vue\9-30YKY_Project\unittest_demo\tetstcase')
runner = TestRunner(suite)
runner.run()

import unittest
'''
fixtrue:测试夹具
setUp:用例级别的前置:每条用例执行前都会执行它
teardown:用例级别的后置:每条用例执行后会执行它
'''

class Test_Infp(unittest.TestCase):
    def setUp(self) -> None:
        print('用例级别的前置')
    def tearDown(self) -> None:
        print('用例级别的后置')
    def test_01(self):
        print('这是一个测试用例')


# 用例级别的前置
# 这是一个测试用例
# 用例级别的后置

unittest的跳过方式

1.直接跳过

        @unittest.skip('跳过的原因')

2.根据条件来判断是否跳过

        @unittest.kipif(判断条件,reason = "原因")

ddt的使用 

        ddt其实是一个装饰器

        pip install ddt

        我们使用的unittestreport 中包含了ddt,所以不用下载

        from unittestreport import ddt,list_data

        1.在测试类前使用@ddt

        2.用例前使用@list_data('数据')

        3.在用例的括号中,增加一个变量,代表‘数据中每个元素’

openpyxl:读取或回写excel内容
         pip install openpyxl

        注意:只能操作xlsx格式的excel

Excel中三大对象:

        1.工作簿对象=》 WorkBook

        2.表单对象=》Sheet

        3.表格对象=》Cell

# 导入excel操作模块
import  openpyxl

# 创建excel文件对象
op = openpyxl.load_workbook('test001.xlsx')
# 获取所有表单的名字
oph  = op.sheetnames
print(oph)
sh = op['Sheet3']
# 创建表格对象
c = sh.cell(row=1,column=1)
# 获取表格数据
print(c.value) 

标准简洁写法

# 导入excel操作模块
import  openpyxl
# 创建excel文件对象
op = openpyxl.load_workbook('test001.xlsx')
# 获取表单中的数据
p = op['Sheet3'].cell(row = 1,column = 1).value
print(p)

读取表单中所有数据 rows  columns

# 导入excel操作模块
import  openpyxl
# 创建excel文件对象
op = openpyxl.load_workbook('test001.xlsx')
# 获取表单中的数据
p = op['Sheet3']
#按行读取数据
# res = list(p.rows)
# for i in res:
#     print(i)
# 按列读取数据
res = list(p.columns)
for i in res:
    print(i)

获取最大行
p.max_row
获取最大列
p.max_column

#获取所有行,values_only=True直接过去内容
#iter_rows(min_row=1, max_row=6, min_col=1, max_col=2,values_only=True)

for item in sheet1.iter_rows(values_only=True):#获取所有行,values_only=True直接过去内容
    print(list(item))

'''
#插入行
ws2.insert_rows(idx=数字编号,amount=要插入行数)
#删除行
ws2.delete_rows(idx=数字编号,amount=要插入行数)
#插入列
ws2.insert_cols(idx=数字编号,amount=要插入列数)
#删除列
ws2.delete_cols(idx=数字编号,amount=要插入列数)
'''
#从第二行开始插入三行
ws2.insert_rows(idx=2,amount=3)
wb2.save('ftz.xlsx')

 设置颜色和字体

import openpyxl

from openpyxl.styles import PatternFill,Font

filename = r'G:\vue\basic\0806package\xlsx_work_01\2024_8_6.xlsx'

workbook = openpyxl.load_workbook(filename)
print(workbook.sheetnames)
sheet1 = workbook['Sheet1']

# 创建底色
fill = PatternFill(start_color='FE8B74',end_color='FE8B74',fill_type='solid')
# 创建字体颜和大小
font = Font(color="7589FD", size=14)
sheet1['A1'].fill = fill
sheet1['A1'].font = font

workbook.save(filename)

案例

import openpyxl

# 文件对象
wk = openpyxl.load_workbook('testcase.xlsx')
sh = wk['Sheet1']
res = list(sh.rows)
title = [i.value for i in res[0]]
# print(title)
case=[]
for item in res[1::]:
    lis = [i.value for i in item]
    dic = dict(zip(title,lis))
    case.append(dic)
print(case)
[{'case_id': 1, 'title': '登录成功', 'expected': '{"code":0,"msg":"登录成功"}', 'data': '{"username":"python35","password":"llemondan"}'},
 {'case_id': 2, 'title': '账号为空', 'expected': '{"code":1,"msg":"参数不能为空"}', 'data': '{password":"llemondan"}'},
 {'case_id': 3, 'title': '密码为空', 'expected': '{"code":1,"msg":"参数不能为空"}', 'data': '{"username":"python35"}'}, 
 {'case_id': 4, 'title': '密码错误', 'expected': '{"code":1,"msg":"账号或密码不正确"}', 
                                                                                                                                                                                                                                                                                                                                                              'data': '{"username":"python351","password":"llemondan"}'}]

 封装了一个读取和写入Excel数据的方法

import openpyxl

class HandExcel:
    # 初始化方法
    def __init__(self, filename, sheetname):
        '''
        :param filename: excel名称
        :param sheetname: excel表名
        '''
        self.filename = filename
        self.sheetname = sheetname

    def createExcel(self):
        '''
        :return:  返回一个列表(每个元素都是字典)
        '''
        wk = openpyxl.load_workbook(self.filename)
        sh = wk[self.sheetname]
        res = list(sh.rows)
        title = [i.value for i in res[0]]
        case = []
        for item in res[1::]:
            lis = [i.value for i in item]
            dic = dict(zip(title, lis))
            case.append(dic)
        return case

    def cellExcel(self, row, column, value):
        '''

        :param row: 行
        :param column: 列
        :param value: 数据
        :return: 这是一个写入excel数据的方法
        '''
        wk = openpyxl.load_workbook(self.filename)
        sh = wk[self.sheetname]
        sh.cell(row, column, value)
        wk.save(self.filename)

日志模块--logging

        不需要安装,官方库

        1.日志的级别

                1.DEBUG   -->主要用户调试,输出详细的运行情况

                2.INFO      --> 确认一切按预期运行,一般用于输出重要运行情况

                3.WARNING  --> 一些意想不到的情况发生了,但是软件还能按预期工作,会在不久的将来出现问题

                4.ERROR   -->发生了错误,软件没执行一些功能,还可以继续执行

                5.CRITICAL  -->严重的错误,软件不能执行

这五种日志的级别也对应5中日志的方法:debug /  info  / warning / error / critical  。默认的是WARNING,当在WARNING以上才能被跟踪。

import logging
logging.debug('debug 信息')
logging.warning('只有这个会输出。。。')
logging.info('info 信息') # WARNING:root:只有这个会输出。。。

 日志收集器可以在收集和输出的时候控制等级

同时输出到控制台和文件中

import logging

# 创建日志收集器对象
lg = logging.getLogger('zq')
# 设置日志收集器的等级
lg.setLevel('DEBUG')

# 创建控制台渠道
sh = logging.StreamHandler()
# 设置控制台输出等级
sh.setLevel('INFO')
lg.addHandler(sh)
# 创建文件渠道
fh = logging.FileHandler('zq.log',encoding='utf-8')
fh.setLevel('DEBUG')
lg.addHandler(fh)


lg.debug('-----------debug')
lg.info('-------------info')
lg.warning('----------------warning')
lg.error('-----------------error')
lg.critical('--------------critical')

注意:如果没有创建输出渠道,那么默认输出warning 等级的日志,如果创建了输出渠道,没有设置输出等级,那么默认是debug等级

日志输出格式对象

 format =   logging.Formatter('格式')

1.创建日志收集器对象 getLogger(日志对象名)
2.设置日志收集器对象的收集等级 setLevel(等级名)
3.创建日志输出渠道--控制台and 文件(需要给文件名) StreamHandler()  FileHandler(文件名)
4 输出渠道和日志收集器对象绑定 日志对象.addHandler(输出渠道)
5 创建日志输出格式对象(需要文件格式) logging.Formatter(格式)
6 输出渠道和格式对象绑定 输出渠道.setFormatter(格式对象)
7 日志对象.方法输出日志      lg.debug('日志说明')
import logging

# 创建日志收集器对象
lg = logging.getLogger('zq')
# 设置日志收集器的等级
lg.setLevel('DEBUG')

# 创建控制台渠道
sh = logging.StreamHandler()
# 设置控制台输出等级
sh.setLevel('INFO')
lg.addHandler(sh)
# 创建文件渠道
fh = logging.FileHandler('zq.log',encoding='utf-8')
fh.setLevel('DEBUG')
lg.addHandler(fh)

# 设置日志输出的格式对象    ---> 时间 文件名 行号 等级名称
format  = logging.Formatter('%(asctime)s--[%(filename)s-->line:%(lineno)d] - %(levelname)s:%(message)s') %(message)s--')
sh.setFormatter(format)
fh.setFormatter(format)


lg.debug('-----------debug')
lg.info('-------------info')
lg.warning('----------------warning')
lg.error('-----------------error')
lg.critical('--------------critical')

2021-11-09 23:49:41,755--[loggin_g.py-->line:25] - INFO:-------------info
2021-11-09 23:49:41,755--[loggin_g.py-->line:26] - WARNING:----------------warning
2021-11-09 23:49:41,755--[loggin_g.py-->line:27] - ERROR:-----------------error
2021-11-09 23:49:41,755--[loggin_g.py-->line:28] - CRITICAL:--------------critical

 封装了一个添加日志的方法

import logging

def logg_Handler(name='zq',level='DEBUG',slevel='DEBUG',fh_name='logg.log',flevel='DEBUG'):
    # 1.创建日志生成器对象
    log= logging.getLogger(name)
    # 设置日志收集等级
    log.setLevel(level)

    # 2.创建控制台渠道
    sh = logging.StreamHandler()
        #设置控制台渠道的输出等级
    sh.setLevel(slevel)
        #将日志对象和渠道绑定
    log.addHandler(sh)

    #3. 创建文件渠道
    fh = logging.FileHandler(fh_name,encoding='utf-8')
        #设置日志输出等级
    fh.setLevel(flevel)
        #绑定日志对象和问道渠道
    log.addHandler(fh)
    # 4.设置日志输出的格式(包含日期-行号-文件名等信息)
    lf =  logging.Formatter('%(asctime)s--[%(filename)s-->line:%(lineno)d] - %(levelname)s:%(message)s')
    sh.setFormatter(lf)
    fh.setFormatter(lf)

    return log

my_log = Hander_Logs(
    name=conf.get('logg', 'name'),
    level=conf.get('logg', 'level'),
    slevel=conf.get('logg', 'slevel'),
    fh_name=os.path.join(LOG_DIR, conf.get('logg', 'fh_name')),
    flevel=conf.get('logg', 'flevel'),
)

if __name__ == '__main__':
   my_log.error('cuowu')

配置文件

作用:提高代码的重用性,,不用每次都来修改代码

常见的配置格式: ini  conf  .cfg         项目中实用ini

在pycharm 中下载一个ini插件,这样配置文件中会有高亮提示

 配置文件对象

        section:配置块

        option:配置项

配置文件中添加注释:

;注释

# 注释

ConfigParser模块--读取文件

from configparser import ConfigParser
# 创建一个配置文件解析对象
cp = ConfigParser()
# 读取文件
cp.read('musen.ini',encoding='utf-8')
# 根据配置块和配置项的名称来获取值
# 方式一:使用get读取出来的都是字符串
res = cp.get('logging','level')
print(res,type(res)) #DEBUG <class 'str'>
#方式二:使用getint读取出来的是数值
psd = cp.getint('mysql','id')
print(psd,type(psd))
# DEBUG <class 'str'>
# 127 <class 'int'>

方式三:读取布尔类型的数据
 cp.boolean()
方式三:读取浮点
cp.float()

 configparser模块-写入文件

from configparser import ConfigParser
# 创建配置文件对象
cp = ConfigParser()
# 读取文件--文件路径,编码
cp.read('./conf.ini',encoding="utf-8")
# get方法获取值 - section, option
res = cp.get('login','mobile')
res2 = cp.getint('login','password')
print(res,type(res)) # 字符串类型
print(res2,type(res2)) # int 类型

# 写入文件内容 section  option value
cp.set('logging','sh_evel','WARNING')
# 保存文件,write需要一个句柄参数
# cp.write(fp = open('./conf.ini','w',encoding='utf-8'))
with open('./conf.ini','w',encoding='utf-8') as f:
    cp.write(f)
import os
from configparser import ConfigParser
from py36.common.hander_os import CONF_DIR


class Config(ConfigParser):
    def __init__(self, filenames):
        # 调用父类的__init__方法
        super().__init__()
        self.read(filenames, encoding='utf-8')


conf = Config(os.path.join(CONF_DIR, 'conf.ini'))

yaml文件的读取

下载一个yaml库,作用:写yaml文件的时候有高亮提示

 pip install pyyaml   下载yaml模块

import yaml  引入yaml

json文件的读取

定义:需要双引号,不能使用单引号

{
  "lode_1": {
    "name": "张三",
    "sex": "男",
    "age": 90
  },
  "load_2":[
    1,2,3,4,5,66,77
  ]
}
import json

with open('data.json','r',encoding='utf-8') as f:
    #读取文件中json数据并自动转换为对应的py中的数据类型
    res = json.load(f)
print(res,type(res)) #<class 'dict'>
#{'lode_1': {'name': '张三', 'sex': '男', 'age': 90}, 'load_2': [1, 2, 3, 4, 5, 66, 77]}

字典转换为json格式的方法:dumps('字典')

import  json
# python的字典转换为json数据
dic = {'aa':'张三','bb':"李思思","cc":"王五"}
# ensure_ascii=False  表示不进行unicode编码 转换,不加的情况下会以16进制方式存储
res = json.dumps(dic,ensure_ascii=False)
print(res,type(res))
# {"aa": "张三", "bb": "李思思", "cc": "王五"}
# {"aa": "\u5f20\u4e09", "bb": "\u674e\u601d\u601d", "cc": "\u738b\u4e94"}

json数据转为python字典:loads('数据')

import  json

jso = '{"aa":null,"bb":true,"cc":false}'
res = json.loads(jso)
print(res) # {'aa': None, 'bb': True, 'cc': False}

总结:load():读取json    loads:,json数据转为python字典   dumps():字典转换为json格式的方法

os模块

熟记:os.path.dirname() 和os.path.join()  os.path.abspath()

 魔法变量:变量的值在不同的情况下是不一样的

# 当前模块下
fn  = __name__
fl = __file__
print(fn)
print(fl)
#__main__
# G:/vue/9-30YKY_Project/py36/demo.py

# 导入别的模块中使用后的值
from py36.demo import fl,fn
print(fl)
print(fn)
# py36.demo
# G:\vue\9-30YKY_Project\py36\demo.py


__name__:
        启动文件中的__name__的值:__main__
        不是启动文件的情况下:打印的值是模块名

__file__:代表当前文件的文件名字,在python环境中会自动补全,显示的是文件的绝对路径


import os
r = os.path.abspath('.')# 给一个相对路径,获取绝对路径

fil = __file__  # 获取当前文件的名字
fl = os.path.abspath(__file__)# 获取当前文件的绝对路径
print(fil)  #os_17.py
print(fl) # G:\vue\9-30YKY_Project\python_1109\os_17.py

# 给一个文件路径,获取所在目录的绝对路径
res = os.path.dirname(fl)
print(res) # G:\vue\9-30YKY_Project\python_1109
import os

BASE_URL = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 公共文件
COM_DIR = os.path.join(BASE_URL, 'common')
# 配置文件
CONF_DIR = os.path.join(BASE_URL, 'conf')
# 数据文件
DATA_DIR = os.path.join(BASE_URL, 'datas')
# 日志文件
LOG_DIR = os.path.join(BASE_URL, 'logs')
# 报告文件
REPORT_DIR = os.path.join(BASE_URL, 'reports')
# 测试方法文件
TESTCASE_DIR = os.path.join(BASE_URL, 'testcase')


if __name__ == '__main__':
    print(COM_DIR)
    print(CONF_DIR)
    print(DATA_DIR)
    print(LOG_DIR)
    print(REPORT_DIR)
    print(TESTCASE_DIR)

http协议介绍

常见的接口协议:

        1.webservice协议接口:使用soap协议通过http传输,请求和响应报文都是xml格式的

        2.http协议接口:使用http协议来传输数据,常见的请求方法有get,post,put等

        什么是接口测试:本质就是基于某种协议,发送一个请求给服务器,然后服务器返回一个响应数据,测试响应数据和预约结果是否一致,从而验证接口是或否正确

        HTTP端口:80

        HTTPS端口:443

        3.URL地址组成:

                1.协议  scheme

                2.IP名或域名: host

                3.端口:port

                4.资源路径:path

                5.参数:query-string  发送给服务器的数据

                6.锚点:anchor

        4.HTTP请求是无状态的,每一次请求都是独立的

        5.cookies和session的关系

                0.cookies保存在浏览器,session保存在服务器

                1.用户开始访问时,浏览器会自动携带cookies中的信息访问服务器,服务器判断是否有用户登录信息,没有 的情况下,用户需要去登录,服务器检查账号密码,都正确的情况下,创建sessionID和session对象(类似键值对形式),,session(服务器缓存或数据库),然后随着响应把sessionID发送给浏览器,保存在cookies中,再次请求时会把用户ID携带给session对象中保存,如果有的情况下,判断这个session是否存在,是否过期,如果不存在或已过期,重新登录,如果存在且未过期则返回这条session对应用户的对应页面

               2.Cookie是在HTTP协议下,服务器或脚本维护用户信息的一种方式,包含用户相关的信息,无论用户何时访问服务器,都会带上该服务器的cookie

               3.session将数据存在服务器中,每个用户创建一条session,用户访问服务器时候需要携带sessionID核实身份

                4.session的实现基于cookie,session需要借助于cookie来存储sessionID

                5.cookie支持的数据类型受浏览器限制,Session直接使用服务器存储,几乎支持所有的数据类型

                6.cookie 大小默认4kB,session大小约为服务的大小

                7.cookie数据可以随意获取,不安全,Session数据存储在服务器,多为加密存储安全较高

常见的鉴权方式:

        1.cookie+session()

import requests
# 创建一个回话对象,使用这个session对象去发送请求,会自动记录请求中的cookie信息
# 下次请求会自动添加
s = requests.session()
s.post()
s.get()

        2.token 相对于上面的方式更加高效,通常放在响应头中,也有放在响应体中,服务端不保存用户信息,

import requests
# 需求:登录后进行充值
# 先进行用户登录
url = "http://api.lemonban.com/futureloan/member/login"
# 设置请求参数  参数类型为application/json
params = {
    "mobile_phone": "17621253598",
    "pwd": "zq111111"
}
# 设置请求
header = {
    "X-Lemonban-Media-Type": "lemonban.v2"
}
# 发送请求
response = requests.post(url=url, json=params, headers=header)
res = response.json()
# 提取token和用户id
token = res['data']['token_info']['token']
mobile_id = res['data']['id']

# 调用充值接口
url2 = "http://api.lemonban.com/futureloan/member/recharge"
#请求参数 类型是application/json
params2 = {
    "member_id":mobile_id,
    "amount":1599
}
# 请求头 ,需要携带token
header2 = {
    "X-Lemonban-Media-Type":"lemonban.v2",
    "Authorization":"Bearer "+token
}
response2 = requests.post(url=url2,json=params2,headers= header2)
print(response2.json())

python发送请求的第三方库:requests模块

        1.安装  pip install requests

        2.pip install jsonpath

        3.jsonschema 的应用--》校验接⼝返回响应结果的全部字段(更进一步的断言)

                pip install jsonschema -i https://pypi.douban.com/simple/

响应信息的获取  

# requests的基本使用 pip install requests
import  requests
url = "https://www.baidu.com"
response = requests.get(url)
# 1.获取响应状态码
print(response.status_code)  # 200

#2.获取响应头信息
print(response.headers)

# 3.获取响应体信息
#       方式一: text属性,自动识别编码来解码,可能会出现乱码的情况
print(response.text)
#       方式二:content属性,手动添加编码来解码,不会出现乱码
print(response.content.decode('utf-8'))
#       方式三:json() 方法,返回的数据必须是json格式不然会报错
print(response.json()) 

请求信息的获取

# requests的基本使用 pip install requests
import  requests
url = "https://www.baidu.com"
response = requests.get(url)

# 1.获取请求头
print(response.request.headers)
# 2.获取请求体
print(response.request.body)

requests模块的使用---基于urllib库

        1.发送get请求

                方法:requests.get()

                参数:使用parmas传递参数

import requests
# get请求参数(查询字符串参数)
# 方式一:查询字符串参数:可以拼接在url地址后面
url = "http://api.lemonban.com/futureloan/loans?pageIndex=1&pageSize=20"
head = {
    "X-Lemonban-Media-Type":"lemonban.v1"
}
response = requests.get(url=url,headers = head)
print(response.text)
print('-------------------------------------------------------------------')
# 方式二:使用params参数进行传递
url_l = "http://api.lemonban.com/futureloan/loans"
head = {
    "X-Lemonban-Media-Type":"lemonban.v1"
}
params={
    "pageIndex":1,
    "pageSize":5
}
response = requests.get(url=url_l,params=params,headers = head)
print(response.text)

       2.发送post请求

                方法:requests.post()

                参数传递方法:

                        application/x-www-from-urlencode类型参数(表单类型):

                                使用data传值

# requests的使用接口
import  requests
# 课堂派登录接口接口
'''
请求类型:appliction/x-www-form-urlencode  表单类型
'''
url = "https://openapiv53.ketangpai.com//UserApi/login"
# 参数
parmar = {
    "email": "17621253581",
    "password": "zq111111",
    "remember": "0",
}
# data专门用来传递表单类型的参数
response = requests.post(url=url,data=parmar)
print(response.json())

                        appliction/json类型(json类型)

                                使用json进行传值

# requests的使用接口
import  requests
'''
请求参数类型:application/json
'''
url = "http://api.lemonban.com/futureloan/member/register"
# 手动定义请求头
head = {
    "X-Lemonban-Media-Type":"lemonban.v1"
}
# 请求参数
params = {
    "mobile_phone":"17621253581",
    "pwd":"12345678"
}
# 请求头传递+headers字段
# 参数类型:application/json 使用 json 字段传递
response = requests.post(url=url,json=params,headers = head)

print(response.json())

                        muletipart/form-data类型(form_data类型,一般上传文件使用)

                                使用files传递

import requests
bd = open('百度logo.png','rb')
file = {'file':bd}
response = requests.post('http://httpbin.org/post',files=file)
print(response.text)


# {"name": fileObj}
files = {
    'file1': open('/Downloads/hello.txt', 'rb'),
    'file2': open('/Downloads/hello2.txt', 'rb')
}

# [("name", fileObj)]
files = [
  ('file1', open('/Downloads/hello.txt', 'rb')),
  ('file2', open('/Downloads/hello2.txt', 'rb'))
]

query = {}
data = {"key": "val"}
response = requests.post('http://httpbin.org/post', data=data
              params=query, 
              files=files, 
              timeout=10)

        3.发送patch请求

        4.发送put请求

2.jsonpath的使用(json数据提取)

        安装:pip install jsonpath

        导入:from jsonpath import jsonpath

        语法:jsonpath方法需要两个参数(数据,jsonpath表达式)

        注意:如果没有匹配到返回false,如果匹配到返回一个包含数据的列表

        $ : 代表根节点。最外面的一层,一般是{}

        . :一个点代表直接子节点

        .. : 二个点代表子孙节点(不考虑层级)

        [] :选择子节点/ 选择索引

        [,] : 获取多个字段的值

        @ :表示当前节点(和条件过滤一起使用)

        [?(过滤条件)] :

from jsonpath import jsonpath

data = {
    "code": 0,
    "msg": "OK",
    "token": [
        {"name": 1},
        {"name": 2},
        {"name": 3}
    ],
    "data": {
        "id": 217571,
        "leave_amount": 0.0,
        "mobile_phone": "17621253598",
        "reg_name": "小柠檬",
        "reg_time": "2021-11-21 22:07:43.0",
        "type": 1,
        "token_info": {
            "id": 1,
            "token_type": "Bearer",
            "expires_in": "2021-11-21 22:16:51",
            "token": "eyJhbGciOiJIUzUxMiJ9.eyJtZW1iZXJfaWQiOjIxNzU3MSwiZXhwIjoxNjM3NTA0MjExfQ.Izj2CqOM3mcW7RXaV9kYCy-dDhgvFsrO7sol8XIXADQZFSCZKvJhPGoXB5YEBHjDy_17nKvHOnUiVCbxzy41Zg"
        }
    },
    "copyright": "Copyright 柠檬班 © 2017-2019 湖南省零檬信息技术有限公司 All Rights Reserved"
}

res = jsonpath(data, "$.code")  # [0]
res2 = jsonpath(data, "$..id")  # [217571, 1]
res3 = jsonpath(data, "$[msg]")  # ['OK']
res4 = jsonpath(data,"$[token,msg]") # [[{'1': 1}, {'2': 2}, {'3': 3}], 'OK']
res5 = jsonpath(data ,"$.token[2]")  # [{'3': 3}]
# 需求:通过条件判断来获取token中的name值
res6 = jsonpath(data,"$.token[?(@.name==2)]") #[{'name': 2}]

print(res6)

 如果一个接口因为传参不一样,导致返回结果不一样,这个时候就需要成员断言

返回的结果字段不一样,断言预期结果是否存在返回结果中,并且值是否一致

字典的成员断言-A字典是否在B字典中存在

def genderDict(excepted,res):
    for k,v in excepted.items():
        if res.get(k) == v:
            print("{} 存在于 {}中".format(excepted,res))
            pass
        else:
            print("{} 不存在于 {}中".format(excepted, res))
            raise AssertionError('{} not in {}'.format(excepted,res))


if __name__ == '__main__':
    res = {"name":"张三","age":18,"id":8}
    excepted = {"id":9}
    genderDict(excepted,res)

3.全量字段校验--》 jsonschema     

校验内容:
        字段值
        字段名 或 字段类型
校验流程:
        定义json 语法校验格式
        ⽐对接口实际响应数据是否符合json 校验格式

安装 jsonschema:
        
pip install jsonschema -i https://pypi.douban.com/simple/
查验:
        pip 查验: pip list pip show jsonschema
        pycharm 中 查验: fifile --- settings --- 项目名中查看 python 解释器列表
在线校验地址:
        
https://www.jsonschemavalidator.net

python代码实现步骤

实现步骤:
1 导包 import jsonschema
2 定义 jsonschema格式 数据校验规则
3 调⽤ jsonschema.validate(instance="json数据", schema="jsonshema规则")

校验通过:返回 None


校验失败
schema 规则错误,返回 SchemaError
json 数据错误,返回 ValidationError

JSON Schema语法

type:表示带校验元素的类型
    integer —— 整数
    string —— 字符串
    object —— 对象
    array —— 数组 --> python:list 列表
    number —— 整数/⼩数
    null —— 空值 --> python:None
    boolean —— 布尔值
properties:定义校验json对象中,各个key--value中对value的限制条件
required:必须存在的key,字段为字符串且唯一
const:json元素必须等于指定的内容
pattern:使用正则表达式来约束字符串类型的数据
    基础正则举例:
        1 包含字符串:hello
        2 以字符串开头 ^: ^hello 如:hello,world
        3 以字符串结尾 $: hello$ 如:中国,hello 
        4 匹配[]内任意1个字符[]: [0-9]匹配任意⼀个数字 [a-z]匹任意一个小写字母 [cjfew9823]匹配任意一个 
        5 匹配指定次数{}: [0-9]{11}匹配11位数字。

案例一:简单练习一层校验

# 全量字段校验练习json
import jsonschema

data = {
    "name": "账单",
    "age": 18,
    "sex": "男",
    "height": 1.78,
    "city": {
        "cy": "上海",
        "temperature": 36
    }
}

schema = {
    "type": "object",
    "properties": {
        "name": {"type": "string"},
        "age": {"type": "integer"},
        "sex": {"type": "string"},
        "height": {"type": "number"},
        "city": {"type": "object"}
    }
}

rsp = jsonschema.validate(instance = data,schema = schema)
print(rsp)  # None 表示匹配成功

案例二:多层校验

import jsonschema

data = {
    "name": "账单",
    "age": 18,
    "sex": "男",
    "height": 1.78,
    "city": {
        "cy": "上海",
        "temperature": 36
    }
}

schema = {
    "type": "object",
    "properties": {
        # name的值必须是账单
        "name": {"const": "账单"},
        "age": {"type": "integer"},
        "sex": {"type": "string"},
        "height": {"type": "number"},
        "city": {
                # 这个字段包含的也是一个对象
            "type": "object",
                # 每个值进行校验
            "properties": {
                # cy 的值必须包含上海
                "cy": {"pattern": "上海"},
                # temperature 必须是36
                "temperature": {"const": 36}
            }
        }
    },
    # 必须字段
    "required": ["name", "height", "city"]

}

rsp = jsonschema.validate(instance=data, schema=schema)
print(rsp)  # None 表示匹配成功

python连接数据库: pymysql模块

pip install pymysql==0.9.3

指定版本: pip install pymysql==0.9.3

删除模块 : pip uninstall pymysql

步骤: 注意:默认开启事务

        1.连接数据库

        2.创建游标

        3.准备sql语句

        4.执行sql

        5.提交事务(涉及到增、删、改操作的时候需要提交事务)

        5断开连接

import pymysql

# 创建数据对象
con = pymysql.connect(
    # 用户名
    user="future",
    # 密码
    password="123456",
    # 数据库ip
    host="api.lemonban.com",
    # 端口号
    port=3306,
    # 编码格式
    charset="utf8",
    # 数据库
    database="futureloan"
)

# 创建游标对象
cur = con.cursor()
sql = "select * from  member where mobile_phone = '13453457687'"
result = cur.execute(sql)
# 获取查询的数量
print(result) # 1
# 使用游标对象的方法获取具体数据
print(cur.fetchall())
# ((1, '檬檬', '7BB4880520192D282D68659D9FF084DC', '13453457687', 1, Decimal('16218199.44'),
# datetime.datetime(2021, 11, 1, 19, 58, 29)),)


# 提交事物
con.commit()
# 断开游标对象
cur.close()
# 断开数据库对象
con.close()

使用使用上下文管理的方式来简化连接获取数据的步骤:with会自动提交事务

import pymysql

# 创建连接对象
con = pymysql.connect(
    host="api.lemonban.com",
    port=3306,
    user='future',
    password="123456",
    database= 'futureloan',
    cursorclass = pymysql.cursors.DictCursor# 设置返回的数据格式是字典,如果不写则是元组
)
sql = "select * from member limit 10"
with con as cur:
    res = cur.execute(sql)

print(res)
print(cur.fetchall())
# 关闭游标
cur.close()
# 关闭连接
con.close()

封装一个连接数据库的类

扩展数据库参数:

默认返回的是一个元组格式的数据,如果需要返回的是列表,可以在连接对象中添加参数

# 设置返回的参数为字典格式,默认是元组格式
cursorclass=pymysql.cursors.DictCursor
import pymysql


class Handle_mysql:
    '''
    实现了查询数据库的信息
    '''

    def __init__(self, host, port, user, password, database):
        '''
        :param host: ip地址
        :param port: 端口号
        :param user: 用户名
        :param password: 用户密码
        :param database: 数据库名称
        '''
        self.con = pymysql.connect(host=host,
                                   port=port,
                                   user=user,
                                   password=password,
                                   database=database,
                                   charset='utf8',
                                   cursorclass=pymysql.cursors.DictCursor
                                   )

    # 获取查询到的数量
    def sql_count(self, sql):
        '''
        :param sql: sql语句
        :return:
        '''
        # 得到游标对象
        with self.con as cur:
            res = cur.execute(sql)
            cur.close()
            return res

    # 获取查询到的全部数据
    def sql_all_data(self, sql):
        # 得到游标对象
        with self.con as cur:
            cur.execute(sql)
            res = cur.fetchall()
            cur.close()
            return res

    # 获取查询数据的一条
    def sql_one_data(self, sql):
        # 得到游标对象
        with self.con as cur:
            cur.execute(sql)
            res = cur.fetchone()
            #关闭游标对象
            cur.close()
            return res

    def __del__(self):
        '''
        :return:  这是一个魔术方法,自动调用,作用:关闭连接对象
        '''
        self.con.close()

扩展with:

with是启动上下文管理器的关键字

上下文管理器协议:如果一个类中有如下两个方法,那么该类就实现了上下文管理器协议(可以通过with去操作)

__enter__: "with xxx as con  " 表示 con 接收的就是xxx方法的返回值

__exit__:with中的代码执行完毕后,就会执行这个退出方法

关于Decimal类型--也是python中表示浮点精度的一种类型

1.是一个双精度类型,使用时需要导包,精度比单精度(float)要高

2.使用时必须是一个字符串,如果出传入的不是一个字符串,也会出现精度丢失问题

from decimal import Decimal

print(Decimal("3.3")-Decimal('1.1'))  #  2.2
print(3.3-1.1)                        # 2.1999999999999997
print(Decimal(3.3)-Decimal(1.1))#2.199999999999999733546474090

正则表达式

0.使用官方模块 -- re

import  re

方法:

        findall('表达式',字符串):匹配到的内容列表,如果正则表达式中有组则只能匹配到组对应的内容

        finditer:匹配全部内容,返回迭代器

info = "战三          李四      王五"
result = re.finditer("\w+",info)
for item in result:# 每个item 都是一个match对象
    # 依次取出; 战三  李四  王五
    print(item.group())

        search(): 返回第一个符合匹配规则的匹配对象,没有匹配到就返回None

                匹配对象.group(): 提取匹配对象中的内容

               匹配对象.group(1): 通过参数获取指定组的匹配内容,1就表示第一组

                获取匹配内容对应的位置:span()

        

res = re.search('#(.+?)#',str)
print(res)
print(res.group()) #name#
print(res.group(1)) name

1.语法

        1.单字符:表示单独一个字符

                 []:列举匹配的字符串

                . :匹配任意一个字符

                \d:匹配数字0-9

                \D:匹配非数字,即不是数字

                \s:匹配空白,即空格,tab键

                \S:匹配非空白

                \w:匹配单词字符,即a-z,A-Z,0-9,下划线,汉字

                \W:匹配非单词字符           

import re
str = '1223sh—__csd,m2 323&    (*……¥'
# \d :匹配一个数字
res1 = re.findall('\d',str)
# print(res) ['1', '2', '2', '3', '2', '3', '2', '3']
# \D :匹配一个非数字
res2 = re.findall('\D',str)
# print(res2) ['s', 'h', 'c', 's', 'd', ',', 'm', ' ', '&', '(', '*', '…', '…', '¥']
# \s :匹配一个空白
res3 = re.findall('\s',str)
#print(res3) [' ', ' ', ' ', ' ', ' ']
# \S :匹配一个非空白
res4 = re.findall('\S',str)
# print(res4) ['1', '2', '2', '3', 's', 'h', 'c', 's', 'd', ',', 'm', '2', '3', '2', '3', '&', '(', '*', '…', '…', '¥']
# \w :匹配一个单词字符  --数字 字母 下划线
res5 = re.findall('\w',str)
# print(res5) ['1', '2', '2', '3', 's', 'h', '_', '_', 'c', 's', 'd', 'm', '2', '3', '2', '3']
# \W :匹配一个非单词字符
res6 = re.findall('\W',str)
# print(res6) ['—', ',', ' ', '&', ' ', ' ', ' ', ' ', '(', '*', '…', '…', '¥']

str = 'dscshc88^^__--'
res = re.findall('.',str)
# print(res)# 匹配任意字符
res2 = re.findall('[0-9]',str)
print(res2) #['8', '8']

            

        2.数量

                *:匹配一个字符出现0次或无限次,即可有可无

                +:匹配一个字符出现1次或无限次,即最少1次

                ?:匹配一个字符出现1次或0次,即要么出现1次,要么没有

                {m}:出现了m次

                {m,}:最少出现了m次

                {m,n}:出现了m-n次

        3,边界

               ^:匹配字符串开头

                $:字符串结尾

str2 = '123scksskjhs890789'

# ^: 表示开头
# $:表示结尾
res = re.findall('^1',str2)
#  print(res)  # ['1']
ter  = re.findall('89$',str2)
print(ter) # ['89']

                \b:匹配单词的边界   --单词边界 :数字字母下划线与其他字符的交界位置

                \B:匹配非单词的边界 

str3 ='jpython and c# andpythonjava not javaScript'
res = re.findall(r'\bpython',str3)
print(res) []
res2 = re.findall(r'python\b',str3)
print(res2) ['python']
res3 = re.findall(r'\Bpython',str3)
print(res3) ['python', 'python']

注意: \b 在python中表示退格键  --> backspace 这个键,所以使用的时候需要增加 r  防止转义

        4.匹配分组

                | :匹配左右任意一个表达式

           (ab):将括号中字符作为一个分组

# 分组:()  只显示()中的内容
str ='{"name":"#name#","id":"#id#","python":"#good#"}'

res =re.findall('#(\w+)#',str)
print(res,len(res),type(res)) ['name', 'id', 'good'] 3 <class 'list'>

        5.贪婪模式:python里数量吃默认是贪婪的 ,总是想尽可能多的匹配字符串

在表示数量范围的后面加上?==》关闭 贪婪模式

import re

str = '{ "aaaa": 123, "bbb": 345,"ccc":8888,"ddd":"9999999"}'

#res = re.findall('\d{3,4}',str)
# print(res,len(res)) ['123', '345', '8888', '9999', '999']
res = re.findall('\d{3,4}?',str)
print(res) ['123', '345', '888', '999', '999']
'''
?  表示关不贪婪模式,按照最少的来匹配,如果不关闭,则按照最多的来匹配

'''

 

 

info = "战三          李四      王五"

print(re.split("\s+",info))# ['战三', '李四', '王五']
print(re.sub("\s+","/",info))# 战三/李四/王五

最终封装一个方法:

import  re
def replace_data(data,cls):
    '''
    作用:传入一个类和字符换,能够将类属性替换到字符串中,注意类属性名要和字符串中的名称一致
    类:cls
    数据:s  (字符串)
    :return:
    '''
    while re.search('#(.+?)#', data):
        res = re.search('#(.+?)#', data)
        item = res.group()
        attr = res.group(1)
        value = getattr(cls, attr)
        data = data.replace(item, str(value[0]))
    return data
import re
import random
import time
# 替换字符串的方法
def data_replace(cls, data):
    '''

    :param cls: 类名
    :param data: 字符串
    :return: 返回替换后的字符串
    '''
    while re.search('#(.+?)#', data):
        # 返回一个符合规则的查询对象
        res = re.search('#(.+?)#', data)
        # 通过group方法得到内容
        item = res.group()
        value = res.group(1)
        # 查询类属性的值
        res = getattr(cls, value)
        data = data.replace(item, str(res))
    return data



# 随机生成手机号的方法
def create_a_phone():
    '''
    随机生成一个手机号
    :return:
    '''
    # 第二位数字
    second = [3, 4, 5, 7, 8][random.randint(0, 4)]

    # 第三位数字
    third = {3: random.randint(0, 9),
             4: [5, 7, 9][random.randint(0, 2)],
             5: [i for i in range(10) if i not in [4, 5]][random.randint(0, 7)],
             7: [i for i in range(10) if i not in [1,2,3,4,9]][random.randint(0, 4)],
             8: random.randint(0, 9), }[second]

    # 最后八位数字
    suffix = random.randint(9999999, 100000000)

    # 拼接手机号
    return "1{}{}{}".format(second, third, suffix)
# 随机生成一个时间戳
def time_date():
    t = round(time.time() * 1000)
    t_str = time.strftime('%Y-%m-%d:%H:%M:%S', time.localtime(t / 1000))
    return t_str
if __name__ == '__main__':
    item = create_a_phone()
    print(item)

unittestreport的测试结果推送

1.推送到邮箱--需要借助邮箱的SMTP服务

 授权码:toelbdydhgcbiaaj

QQ邮箱的SMTP服务器地址:

接收邮件服务器:pop.qq.com,使用SSL,端口号995

发送邮件服务器:smtp.qq.com,使用SSL,端口号465或587

 如果需要自定义发送邮件的类容,可以使用unittestreport中的方

from unittestreport.core.sendEmail import SendEmail

 钉钉发送测试结果

1.建立群聊会议室,点击设置

2.点击智能群助手

 

 3.添加机器人

 

 

 

接口扩展

1.webservice接口---返回的参数和请求的参数都是xml 文档

        环境安装: pip install suds-py3 

 注意:一个服务地址下可能会包含多个接口地址

QQ在线查询接口:http://ws.webxml.com.cn/webservices/qqOnlinewebService.asmx?wsdl

        参数:qq号码

        返回:Y/N/E  (Y代表在线,N代表离线,E代表参数有误)

from suds import client

url = "http://ws.webxml.com.cn/webservices/qqOnlinewebService.asmx?wsdl"
web_s = client.Client(url)

# 打印客户端信息
print(web_s)

# 发送请求--填写参数
res = web_s.service.qqCheckOnline(qqCode="1170368928")
print(res)

如果需要工具测试webservice接口可以 SoapUl  来测试

2.websocket---基于TCP的长链接

多个浏览器之间数据会同步

注意:不能通过抓包获取接口

pip install websocket-client

持续集成-jenkins

目的:

        1.让产品进行迭代,保持高质量

好处:

        1.快速发现错误

        2.防止分支偏离主干

        3.快速的发布

jenkins:开源的持续集成工具,提供软件版本发布,启动测试等一系列流程

1.Git和Git'代码仓库平台

2.jenkins介绍

3.持续集成之Postman

4.持续集成之代码

jenkins主从模式)

        如果公司服务上的jenkins不能直接运行代码,可以使用主从模式来执行

pytest的讲解

1.pytest和unittest的区别

1.unittest是python的官方库,兼容性更好,更稳定,pytest安装可能会出现python版本兼容性问题

2.测试类必须要继承unittest.TestCase

3.unittest必须加载测试套件,再去执行,pytest更加智能,会自动查找

4.pytho有打标签的功能,根据标签执行用例

5.python有用例失败重新运行的机制,unittest没有

6.pytest的插件更多,扩展性强

7.python的前后置处理比unittest灵活

。。。。。。

中文文快速入门 — learning-pytest 1.0 文档 

默认用例编写规则:

1.用例文件名以test开头

2.以test开头的函数会被当成一条测试用例

3.以Test开头的类,会被当成测试用例类

        测试类中,以test开头的函数会被当成一条测试用例

用例可以以类的形式去写,也可以以函数的形式去写

 如果业务需要,也可以自动定义用例规则


在同一个用例文件中,pytest执行用例的顺序是按代码的前后顺序

不同文件则根据ACSII码的顺序加载执行

测试类的前后置方法:

方式一:

        1.用例级别的:setup 和teardown

        2.用例类级别的:setup_class 和teardow_class

方式二:前后置在一个函数中

        @pytest_fixtrue

        在任意一个函数前增加@pytest_fixtrue装饰器,那么这个函数就变成一个前后置方法

scope="function" ==> 当前方法是一个用例级别的前后置方法

scope = "class"==> 当前方法是一个类级别的前后置方法

scope 还有模块(文件前后置)、包(文件夹前后置)、会话(程序的前后置)等前后置

如果一个用例函数想使用前后置方法,则把前后置方法名,添加发哦用例函数的参数中(如下)

import pytest


@pytest.fixture(scope="function")
def cast_setip():
    print('这是一个用例前置方法----------')

    yield

    print('这是一个用例后置方法')


class Test_login:
   

    def test_01(self,cast_setip):
        assert 1 ==100

方式二的更新: 

2.用例打标签:pytest.mark 

1.注册标签:创建pytest.ini文件

[pytest]
markers=
    musen   # 标签名
    serial  #标签名

2.给用例添加标签

1.在用例前添加@pytest.mark.标签名

 @pytest.mark.musen
    def test_01(self):
        assert 100 ==100

2.把某个类所有用例都打上标签

class Test_lersion:
    pytestmark =[pytest.mark.musen,pytest.mark.zq]

3.运行的时候指定标签用例

执行单个标签

在命令行使用:pytest -m 标签名

在main方法中:pytest.main([参数列表])

pytest.main(["-m","musen"]

执行多个用例,可以使用 and or not

注意需要使用 双引号

pytest -m "标签1" and "标签2"

pytest -m "not musen" # 执行不带musen标签的用例,注意:需要使用双引号

pytest -m "musen and serial"   # 执行带musen标签和serial标签的用例,(必须带有两个标签)

pytest -m  "musen or serial"   # 执行带musen或serial 标签的用例




pytest.main(["-m","musen"])

pytest.main(["-m","musen and zq or zq"])

4.内置标签:

skip:跳过用例

skipif:通过条件判断是否跳过用例

class Test_lersion:
    a=100
    @pytest.mark.skip
    def test_04(self):
        assert 99 ==100
    @pytest.mark.skipif(a==99,reason="只有这个(a==99)条件成立才跳过")
    def test_05(self):
        assert 10 ==100

5.筛选执行的 【用例文件】或【测试类】或【用例方法】

        1.执行用例文件:pytest  文件名.py

        2. 执行测试类: pytest 文件名.py::类名       pytest test_demo3.py::Test_lersion

        3. 执行用例方法: pytest 文件名.py::类名::用例方法名     

                 pytest test_demo3.py::Test_lersion::test_04

6 参数化--》pytest.mark.parametrize

import pytest

# case = [1, 654, 67, 78, 89, 45, 89]
case = [
    {"id": 1, "name": "张三", "age": 13},
    {"id": 2, "name": "李四", "age": 19},
    {"id": 3, "name": "王五", "age": 56}
]


class Test_Method:
    @pytest.mark.parametrize('item', case)
    def test_bm(self, item):
       print(item['id'],type(item['id']),item['name'],type(item['name']))

.1 <class 'int'> 张三 <class 'str'>
.2 <class 'int'> 李四 <class 'str'>
.3 <class 'int'> 王五 <class 'str'>

7.生成报告的几种方式

1.html格式的报告(基本不用)

pip install pytest-html

	第一种演示--基本不用
import pytest
case = [1, 654, 67, 78, 89, 45, 89]
class Test_Method:
    @pytest.mark.parametrize('item', case)
    def test_bm(self, item):
        assert item >20

pytest.main(['test_demo_05参数化.py','--html=reports/report.html'])

2. Allure

在这里插入图片描述

1.1:下载地址:Releases · allure-framework/allure2 · GitHub

 1.2:将文件夹路径放到pat环境变量中(从bin目录开始)

 1.3:安装allure依赖包 :pip install allure-pytest,执行pytest.main()文件

import pytest
case = [1, 654, 67, 78, 89, 45, 89]
class Test_Method:
    @pytest.mark.parametrize('item', case)
    def test_bm(self, item):
        assert item >20
       
pytest.main(['test_demo_05参数化.py','--alluredir=allure-reports'])
# allure-reports 代表文件夹,不需要给具体文件名

会生一个allure-reports的文件夹,里面都是json文件

在这里插入图片描述

开启allure服务命令: allure serve   文件夹名

在命令行,使用 allure serve  allure-reports ,会自动打开浏览器,生成报告

 

allure报告中添加错误截图

allure.attach(文件内容,名字,图片类型)
 def err_one_image(self, desc=None):
        '''
        错误截图
        :param desc: 说明
        :return:
        '''
        try:
            path = self.random_time() + f"{desc}" + '.png'
            # 生成图片名+路径
            filename = os.path.join(ERR_IMAGES, path)
            time.sleep(1)
            self.driver.save_screenshot(filename=filename)
        except Exception as err:
            my_log.error(f'错误截图--【{desc}】---失败')
            my_log.exception(err)
            raise err
        else:
            with open(filename,'rb') as f:
                content = f.read()
            allure.attach(content,'错误截图',allure.attachment_type.PNG)
            my_log.info(f'错误截图--【{desc}】---成功')

allure报告中添加标题

 

 

 

8. 用例重运行机制

1.安装插件

pip install pytest-rerunfailures

2.重运行

pytest --rerun  3   --reruns-delay 5

--rerun 3 :表示用例失败重运行的次数

--reruns-delay:表示每次重运行的间隔时间

pytest.main([
    'test_demo4.py',
    '--rerun','3',
    '--reruns-delay','5'
    '--alluredir=allure-reports'
])

提醒:main([])zhong 可以增加

                 -s  参数 ,防止数据被吞,

                 -v 参数,显示详细的信息

                可以放在【】的任意位置,一般调试时候使用

前端基础

window.open(‘网站地址’):打开新的网页
window.scrollBy(x坐标,y坐标):相对于的当前位置进行滑动,可以多次执行
window.scrollTo(x坐标,y坐标):滑动到页面指定位置,只能执行一次
target="_blank":这个属性表示 连接重新打开新窗口

selenium环境安装和入门

1.环境安装

1.安装: pip install selenium

        如下载失败可使用 国内源,百度搜索“python 国内源”

        pip install selenium -i  https://pypi.tuna.tsinghua.edu.cn/simple

        

pip包管理工具可以下载第三方库到本地使用,第三方库的来源地址称之为镜像源,镜像源中存放了大量的开源库供我们下载使用。pip的默认镜像源地址在国外,下载很慢,可以指定下面国内的镜像源地址:

清华:https://pypi.tuna.tsinghua.edu.cn/simple

阿里云:http://mirrors.aliyun.com/pypi/simple/

中国科技大学 https://pypi.mirrors.ustc.edu.cn/simple/

华中理工大学:http://pypi.hustunique.com/

山东理工大学:http://pypi.sdutlinux.org/ 

豆瓣:http://pypi.douban.com/simple/
————————————————
版权声明:本文为CSDN博主「smart_liu8」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/smart_liu8/article/details/121162788

2.浏览器对应的驱动下载

        1.chromedriver的下载:http://chromedriver.storage.googleapis.com/index.html

              注意:浏览器版本一定要大于下载驱动的版本

 下载成功,解压文件得到一个 "chromedriver.exe",如果是window系统,将这个文件放在python安装目录中,这样就不用再配置环境,直接使用

注意:如果是mac 电脑,解压成功后,将解压文件放在/usr/local/bin 目录中

 以上步骤执行后,测试安装是否成功,如成功打开浏览器,则表示成功

from selenium import webdriver

driver = webdriver.Chrome()
driver.get('https://www.baidu.com')

关闭driver对象
driver.quit()

 在这里插入图片描述 

2.selenium的基本使用

1selenium操作服务器的原理

       写的代码  ---》通过http协议 ----》创建webdriver服务器-----》 通过chrome驱动来操作浏览器

 浏览器
driver: 一般我们都会下载driver
client: 也就是我们写的代码
client其实并不知道浏览器是怎么工作的,但是driver知道,在selenium启动以后,driver其实充当了服务器的角色,跟client和浏览器通信,client根据webdriver协议发送请求给driver,driver解析请求,并在浏览器上执行相应的操作,并把执行结果返回给client。这就是selenium工作的大致原理
 

通过requests请求来操作浏览器

import requests
import os

os.popen("chromedriver")

data = {
    "capabilities": {
        "browserName": "chrome"
    },
    "desiredcapabilities": {}
}
resposne = requests.post(url="http://localhost:9515/session", json=data)
sessionid = resposne.json()['value']['sessionId']
print(sessionid)
data1 = {"url": "https://www.jd.com"}
url2 = "http://localhost:9515/session/{}/url".format(sessionid)
rsp = requests.post(url=url2, json=data1)

 案例

**属性:**

1. `capabilities`: 返回当前会话的浏览器的配置。
2. `desired_capabilities`: 返回用于启动浏览器的配置。
3. `session_id`: 返回当前会话的唯一标识符。
4. `title`: 返回当前页面的标题。
5. `current_url`: 返回当前页面的 URL。
6. `page_source`: 返回当前页面的 HTML 源代码。
7. `current_window_handle`: 返回当前窗口的句柄。
8. `window_handles`: 返回所有窗口的句柄列表。
————————————————

**方法:**

1. `get(url)`: 打开指定 URL 的页面。
2. `back()`: 在浏览器历史记录中后退一步。
3. `forward()`: 在浏览器历史记录中前进一步。
4. `refresh()`: 刷新当前页面。
5. `close()`: 关闭当前窗口。
6. `quit()`: 退出 WebDriver 并关闭所有窗口。
7. `execute_script(script, *args)`: 在当前页面执行 JavaScript 脚本。
8. `execute_async_script(script, *args)`: 在当前页面异步执行 JavaScript 脚本。
9. `switch_to`: 切换到其他上下文,如 iframe 或窗口。
10. `implicitly_wait(time_to_wait)`: 设置隐式等待时间。
11. `set_page_load_timeout(time_to_wait)`: 设置页面加载超时时间。
12. `set_script_timeout(time_to_wait)`: 设置执行 JavaScript 脚本的超时时间。
13. `add_cookie(cookie_dict)`: 添加 cookie 到当前会话。
14. `delete_cookie(name)`: 删除当前会话中的指定 cookie。
15. `delete_all_cookies()`: 删除当前会话中的所有 cookie。
16. `get_cookie(name)`: 获取当前会话中指定名称的 cookie。
17. `get_cookies()`: 获取当前会话中的所有 cookie。
18. `maximize_window()`: 最大化当前窗口。
19. `minimize_window()`: 最小化当前窗口。
20. `fullscreen_window()`: 将当前窗口设置为全屏。
21. `back()`: 返回上一个页面。
22. `forward()`: 前进到下一个页面。
23. `find_element(by, value)`: 根据定位策略和值查找单个元素。
24. `find_elements(by, value)`: 根据定位策略和值查找多个元素。

# 导入包
from selenium import webdriver
import  time
# 创建服务器,启动浏览器
driver = webdriver.Chrome()
#------------------------driver的方法------------------------------
# 发送请求
driver.get('https://www.baidu.com')
driver.get('https://taobao.com')
# 窗口最大化
# driver.maximize_window()
# 窗口最小化
# driver.minimize_window()
# time.sleep(3) 强制等待

# 返回上一个页面
driver.back()
time.sleep(1)
# 进入下一个页面
driver.forward()
# 刷新页面
driver.refresh()
driver.back()
time.sleep(1)


# 保存当前页面为图片--截图,保存在当前路径下
# driver.save_screenshot('图片的名字')
driver.save_screenshot('page.png')


#---------------------driver的属性-------------------------
#获取页面的title
title = driver.title
print(title)

#获取当前的url地址
url = driver.current_url


#获取页面的源代码
html = driver.page_source
#获取当前窗口的句柄(可以理解为编号,多个窗口的时候,每个窗口都有一个编号)
win = driver.current_window_handle

# 获取浏览器打开的所有窗口的句柄(使用窗口句柄可以自由的切换窗口)
wins = driver.window_handles
# 关闭请求
driver.quit()

# 批量删除chromedriver的进程
# taskkill /F /im chromedriver.exe

#输入
元素对象.send_keys('百度一下')
#点击
元素对象.click()

3.八大定位和xpath语法

1.定位解析

# 八大定位方法
# 1.通过id定位
driver.find_element_by_id()
# 2.通过class属性名定位
driver.find_element_by_class_name()
# 3.通过标签名
driver.find_element_by_tag_name()
# 4.通过链接(a标签)标签的文本定位
driver.find_element_by_link_text('新闻') # 查找
# 5.通过链接(a标签)标签的文本定位
driver.find_element_by_partial_link_text('新') # 部分匹配
# 6.通过元素的name属性定位
driver.find_element_by_name()
# 7.通过xpth定位表达式查找
driver.find_element_by_xpath()
# 8.通过css定位表达式查找
driver.find_element_by_css_selector()

在这里插入图片描述

 chrpath插件:按照选中的代码自动生成xpath css表达式

2.xpath语法

xpath是XML路径语言,它是一种确认XML文档中某部分位置的语言

1.作用:

        一次性查找,不用一层一层查找

2.基本语法

        nodename:选取此节点的所有子节点

        /                :从根节点选取,绝对路径

        //               :从匹配的当前节点选择文档中的节点,而不考虑他们的位置,相对路径

        .                :选取当前节点

        ..                :选取当前节点的父节点

        @               :选取属性

       []                :谓语判断条件   

                1.索引    例: div[2] 选取第二个div    注意索引取值从1开始

                注意:索引取值的优先级高于//  如果需要索引取值,一定要使用括号包起来:(//div)[2]

                2.属性筛选条件   [@属性名='属性值']

                        例://*[@nullmsg="请输入手机号"]  

                        如果需要多个属性进行筛选,则可以使用逻辑运算符  and 进行操作

                         例:  //*[@name='zhangsan' and @age ='seven']

                3.通过元素的文本进行定位  [text()='文本值']

                        text():表示文本内容

                       例://a[text()='忘记密码?']

                4.部分匹配,有两个函数可以使用

                        4.1        starts-with(str1,str2)  : str1是否以str2开头

                                例://a[starts-with(text(),'hao')]   匹配文本是否以 hao 开头

                                例://a[starts-with(@href,'http://news')]   匹配href 属性是否以http://news开头

                        4.2        contains(str1,str2)  :str1是否包含str2                                     

                                例://a[contains(text(),'hao')]   匹配文本是否包含 hao 

                                例://a[contains(@href,'http://news')]   匹配href 属性是否包含http://news

        

   3.xpath轴定位--》实际使用较少

优势:传统的定位方法只能从上自下来定位,但是轴定位可以依靠轴关系从上自下,从下自上来进行以及兄弟节点的定位

在这里插入图片描述

#查找当前节点下的所有子节点
//div[@class="clearfix"]/child::div

#查找当前节点的父节点
//div[@class="clearfix"]/parent::div

查找当前节点的所有子孙元素
//div[@class="clearfix"]/descendant::*

查找当前节点的父节点
//div[@class="clearfix"]/ancestor::*

查找当前节点的父级节点
//span[@class="nav-txt" and text()='京东']/ancestor::div[@class="nav-item"]
//span[@class="nav-txt" and text()='京东']/../..


查找当前节点的父级节点的兄弟节点
//span[@class="nav-txt" and text()='京东']/ancestor::div[@class="nav-item"]/following-sibling::*

//span[@class="nav-txt" and text()='京东']/../../following-sibling::div

        

 4.css定位语法      ---》不支持文本定位

1. 层级关系

        .info                :选择class= "info" 的所有元素

        #info                :选择id = "#info" 的所有元素

        *                       :所有元素

        p                        :所有<p>元素

        div,p                   :选择所有<div> 和<p>的元素

        div p                    :选择<div>元素内部所有<p>元素

        div>p                  :选择父元素为<div>元素的所有<p> 元素

        div+p                :选择紧接在<div>元素之后的所有<p>元素

2.谓语语法

        [info]                :选择带有info属性的所有元素

        a[title=淘宝]        :选择title=淘宝的a标签

        a[title*=淘宝]        :选择title包含淘宝的a标签

        a[title^=淘宝]         :选择title以淘宝开头的a标签

        a[title$=淘宝]          :选择title以淘宝结尾的a标签

3.索引

nth-of-type:正向索引

获取第五个类属性为info的div
div.info:nth-of-type(5)

nth-last-of-type:反向索引

获取倒数第2个类属性为info的div

div.info:nth-last-of-type(2)

xpath和css选择器的比较

1.css和html绑定的,是直接操作html

2.Xpath是和DOM绑定在一起的,需要通过html生成dom对象

3.只要有DOM就能使用Xpath来定位元素,xpath用途更广

4.xpath在IE中效率高,其他浏览器都会比css 定位慢

5.xpath可以使用文本定位,css则不行

6.xpath的轴定位可以上下左右定位,css只能向下或横向定位

4.selenium的工作原理

5.页面定位和等待机制

 1.定位表达式在浏览器上可以定位,但是在代码上不能定位,是为什么?

        答:网页未加载完成,代码就去寻找元素,添加等待就能解决这个问题

2.三种等待机制

        2.1: 强制等待---》一般不建议使用,大大增加代码执行的等待时间

                time.sleep('2'):强制等待2秒

        2.2:隐式等待----》设置一次对driverd对象整个生命周期(quit()前)生效

               driver.implicitly.wait(等待时间):  driver.implicitly.wait(15)

执行逻辑:
    当代码执行元素定位操作的时候,若找得到元素,则继续执行
    若找不到元素,则等待0.5秒,然后再找一次,若还是找不到,则又等0.5秒再找
    如此循环,直到找到元素,若找到元素,则继续往下执行代码
    若达到最大超时时间,还是不能找到元素,则抛出 timeout 异常(等待超时的错误)

   

        2.3:显示等待---》优势在于可以等待元素处于某个状态

        1.导三个包    

from selenium.webdriver.support.wait import WebDriverWait (等待对象的类)
from selenium.webdriver.support import expected_conditions as  EC   (等待条件模块)
from selenium.webdriver.common.by import By    (定位表达式的方法)

        2.使用By 创建定位表达式

                loc = (By.XAPTH,'//a[@title="百度一下"]')

        2.使用WebDriverWait创建一个等待对象,需要三个参数

                参数1:driver对象

                参数2:等待的最大时间

                参数3:轮询时间(间隔多长时间去查询一次元素是否出现)

                wt = WebDriverWait(driver,15,0.5)

        3.创建一个等待条件,传入定位表达式

                ec = EC.presence_of_element_located(loc)

        4.使用等待对象的until方法,传入等待条件

                wt.until(ec)

  3.页面切换机制    

        3.1 窗口切换:点击后获取当前所有出口句柄,使用switch_to.window('句柄'),出入切换的窗口句柄   

为了确保代码的稳定性,建议在切换窗口前使用一下显式等待(防止窗口为出现而出现代码报错)

# 增加切换窗口的显式等待
WebDriverWait(driver,10,0.5).until(

   EC.new_window_is_opened(driver.window_handles) # 切换窗口的方法,参数:当前窗口的所有句柄
)

wins = driver.window_handles  # 获取当前所有的窗口句柄
driver.switch_to.window(wins[-1]) # 传入句柄来切换
在新窗口进行后续操作

        3.2 iframe 切换及退出

                方式一:根据iframe的name属性来切换

                方式二:先定位iframe,在根据定位来切换

方式一:
driver.switch_to.frame('name的值')


方式二:
loc_fra =(By.ID,'logn_frame')  # 定位表达式

iframe_ele = driver.find_elemenet(*loc_fra)  # 进行定位

driver.switch_to_frame(iframe_ele ) # 传入定位,进行切换



#切换到某一个iframe
driver.switch_to.frame(reference)
#切换到父级iframe
driver.switch_to.parent_frame()
#切换到主iframe
driver.switch_to.default_content()


        3.3  alert弹框切换 

获取到alert弹框

alert = driver.switch_to.alert


# 点击确认
alert.accpet()

#点击取消
alert.dismiss()

#获取弹框的提示文本
content = alert.text

6.鼠标操作

 move_to_element_with_offset(‘元素,x轴,y轴’):移动一个元素到指定位置

# 创建鼠标对象,传入服务器对象
action = ActionChains(driver)
# 鼠标单击,传入一个定位元素,只是把点击动作添加到动作列表中,不会执行
action.click(loc_click)
#  执行列表中的动作
action.perform()


# 建议每个鼠标动作前都创建一个鼠标对象,来进行当次的鼠标操作,
# 因为鼠标动作列表中的动作使用完后不会清空,如果使用同一个鼠标对象执行,则每次都会执行之前的操作


#鼠标右击
action.context_click(loc_click)
action.perform()


# 鼠标双击
action.double_click(loc_click)
action.perform()
time.sleep(5)

# 按下鼠标--传入一个定位元素
action.click_and_hold(loc_click)
# 移动鼠标
action.move_by_offset(xoffset=170,yoffset=0)
# 释放鼠标
action.release()

7.Select下拉选择框选择

1.原生的html,k使用selenium中的 Select类

'''
原生select 操作
'''
import time

from selenium.webdriver import Chrome
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.by import By
'''
定位
'''
# 点击商家登录
loc_login =(By.XPATH,'//span[text()="商家登录"]')
# 账号
loc_user = (By.XPATH,'//input[@id="username"]')
# 密码
loc_pass = (By.XPATH,'//input[@id="password"]')
# 点击登录按钮
loc_clcik =(By.XPATH,'//div[@id="J-login-btn"]')
# 选择商家
loc_select=(By.XPATH,'//select[@id="textadd"]')


driver = Chrome()
driver.implicitly_wait(10)

driver.get('http://youkecloud.com.cn')
driver.maximize_window()

driver.find_element(*loc_login).click()
driver.find_element(*loc_user).send_keys('13764654039')
driver.find_element(*loc_pass).send_keys('zq111111')
driver.find_element(*loc_clcik).click()
# 创建Select对象
ele = driver.find_element(*loc_select)
select = Select(ele)
select.select_by_visible_text('进阶版体验店')
time.sleep(2)
print('网页的句柄:',driver.window_handles)
print('当前网页的title:',driver.title)
print('当前网页的url',driver.current_url)


time.sleep(10)
driver,quit()

 2.非原生的下拉,一般使用框架组件,只能直接点击

8.键盘操作

import time

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
driver = webdriver.Chrome()
driver.implicitly_wait(5)
driver.get('https://www.baidu.com/')

driver.find_element_by_id('kw').send_keys('python is goods')
'''
复制粘贴3次
'''
driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'a')
driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'c')
driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'v')
driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'v')
driver.find_element_by_id('kw').send_keys(Keys.CONTROL,'v')

'''
复制完成后,点击enter 
'''
driver.find_element_by_id('su').send_keys(Keys.ENTER)


time.sleep(6)
driver.quit()

9.js修改元素

import time

from selenium import webdriver
from selenium.webdriver.common.by import By

driver = webdriver.Chrome()
driver.implicitly_wait(4)
driver.get('https://www.12306.cn/index/')
driver.maximize_window()
loc_u = (By.XPATH,'//input[@id="fromStationText"]')
loc_p=(By.XPATH,'//input[@id="toStationText"]')
loc_data=(By.XPATH,'//input[@id="train_date"]')
ele = driver.find_element(*loc_u)
els = driver.find_element(*loc_p)
ele_id = driver.find_element(*loc_data)
js="""
    console.log(arguments);
    arguments[0].value = '长沙';
    arguments[1].value = '苏州';
    arguments[2].value  = '2022-06-15'
"""
driver.execute_script(js,ele,els,ele_id)


driver.quit()

10.页面滑动

import time

from selenium import webdriver
from selenium.webdriver.common.by import By
driver = webdriver.Chrome()
driver.implicitly_wait(3)
driver.get('https://www.layui.site/demo/form.html')
driver.maximize_window()

# 定位元素
loc_sele = (By.XPATH,'(//div[@class="layui-form-item"]//button[@type="submit" and text()="立即提交"])[1]')
# 得到定位元素
ele = driver.find_element(*loc_sele)
# 方式一:
# 滑动到元素可见状态,并返回元素坐标,使用的时候不用加(),当成只读属性上
# locs = ele.location_once_scrolled_into_view
# print(locs)

# 方式二:滑动窗口 直接给滑动距离,不要定位
# js = "document.documentElement.scrollTop=540"
# driver.execute_script(js)

# 方式三:使用scrollIntoView()方法,来滑动元素
js="arguments[0].scrollIntoView()"
driver.execute_script(js,ele)


#方式四   window.scrollby(x坐标,y坐标)
方式五    window.scrollTo(x坐标,y坐标)



time.sleep(5)
driver.quit()

  11.窗口管理      

        通过js打开一个新窗口

js="window.open()"
driver.execute_script(js)

12.文件上传

方式一:

1.找到对应按钮下的input标签,直接使用send_keys方法输入文件路径

方式二:如果找不到input标签,则使用PC端自动化的库

2.1  pywinauto(只能在window使用,并支持中文路径,python3.7.3以下版本可以使用,以上版本不能使用,上传路径前需要强制等待2秒)

2.2  pyautogui (window,mac ,liunx通用,但不支持中文路径)

selenium设置user-agent

selenium总结 

 

 web自动化如何保证稳定性:

        加等待

        pytest有用例失败从运行机制

        优化用例表达式       用相对定位

        采用PO模式,拆分成小模块

 web自动化如何提高运行效率:

        少用强制等待

        driver创建的次数

        

13.PO模型

     page_object_model

      每个页面都是一个类,页面的操作封装为方法,使用的时候实例化类,来调用方法,使页面逻辑和测试分离

po模式的优势:

        可读性

        可维护性

        扩展性

        页面逻辑和测试逻辑分离

        可复用性

        等待操作封装可以避免各种切换

14.框架分层

        1.PO页面对象分层

        2.测试用例层

        3.测试数据层

        4元素定位层

        5.日志层

        6.报告层

        7.公共模块层

定位表达式的分层:

1.抽取一个页面的所有表达式,当做操作类的类属性,使用时直接使用类来调用

当前是把登录页面的所需要的定位当成类属性

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By


class login_page_index:
    login_mobile=(By.XPATH,'//input[@placeholder="手机号"]')
    login_pwd=(By.XPATH,'//input[@placeholder="密码"]')
    login_button = (By.XPATH,'//button[@class="btn btn-special"]')
    login_error_tips_text=(By.XPATH,'//div[@class="form-error-info"]')
    loc_con = (By.XPATH, '//div[@class="layui-layer-content"]')
    '''
    登录页面类
    '''
    def __init__(self, driver):
        self.driver = driver

    def login(self, mobile, pwd):
        '''
        首页登录方法
        :return:
        '''
        self.driver.find_element(*self.login_mobile).send_keys(mobile)
        self.driver.find_element(*self.login_pwd).send_keys(pwd)

        self.driver.find_element(*self.login_button).click()

    def error_info(self):
        '''
        返回登录页面错误提示内容
        :return:
        '''
        res = self.driver.find_element(*self.login_error_tips_text).text
        return res

    def pop_info(self):
        '''
        返回登录页面的弹框提示内容
        :return:
        '''

        WebDriverWait(self.driver, 10, 0.5).until(
            EC.visibility_of_element_located(self.loc_con)
        )
        res = self.driver.find_element(*self.loc_con).text
        return res

2.专门创建一个文件,每个页面的定位表达式当做一个类,使用的时候导入到需要的操作类中使用

 

无头浏览器执行模式

优点:

        1.节约时间,不启动浏览器

        2.如果放到服务器上执行,只能以无头模式执行

缺点:无头模式下,使用第三方插件上传图片不能执行

       (当代码放在Linux服务器上执行,因为没有浏览器,所以使用无头浏览器模式执行代码)

        Chrome启动参数大全:Chrome浏览器启动参数大全(命令行参数)

 在前置方法中设置无头浏览器模式

def crete_driver(is_headers = True):
    '''
    是否创建无头浏览器模式
    :param is_headers: 默认启动浏览器窗口,如果为FALSE 则为无头浏览器模式执行
    :return:
    '''
    if is_headers:
        '''不创建无头浏览器模式'''
        driver =Chrome()
    else:
        # 实例化启动参数对象
        option = ChromeOptions()
        # 设置无头模式
        option.add_argument('--headless')
        # 专门应对无头模式下浏览器不能最大化屏幕
        option.add_argument('--window-size=1920,1050')
        # option.add_argument('--start-maximized')
        driver = Chrome(options=option)
    return driver

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值