文件操作
上次学习到文件的读写,为了高效的读写文件,我们可以用循环的方式,一行一行的进行读写操作,打开文件的方法是open的方法,打开文件执行完后还要进行关闭操作。
一般的文件流操作都包含缓冲机制,write方法并不直接将数据写入文件,而是先写入内存中特定的缓冲区。
正常情况下缓冲区满时,操作系统会自动将缓冲数据写入到文件中。
至于close方法,原理是内部先调用flush方法来刷新缓冲区,再执行关闭操作,这样即使缓冲区数据未满也能保证数据的完整性。
如果进程意外退出或正常退出时而未执行文件的close方法,缓冲区中的内容将会丢失。
所以我们通常用flush方法刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区。如下:
f=open('www','a+',encoding='utf-8')
f.
f.write('hahah
')
f.flush()
f.close()
还有一种打开文件的方式,可以自动关闭文件,防止open方式忘记手动关闭文件。
with open('aaa','a+',encoding='utf-8') as f:
f.seek(0)
f.write('haha
')
f.flush()
在 “with” 打开的代码块内,文件是打开的,而且可以自由读取。 然而,一旦Python代码从 “with” 负责的代码段退出,文件会自动关闭。
用with打开文件的时候可以打开多个,用逗号分开就好:
with open('aaa','a+',encoding='utf-8') as f,open('www','w',encoding='utf-8')as w:
f.seek(0)
f.write('haha
')
f.flush()
w.write('再见你好!')
打开多个文件时,我们就可以对多个文件同时进行操作了。
在文件操作时可能会遇到需要下载图片或视频的内容,如果我们要下载一个网站的图片,进行保存,由于图片地址时http协议的,所以我们需要用到request处理HTTP的功能。
import requests
url='https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1515432257731&di=feaf94121db96dc3e315f6c170e0d5a0&imgtype=0&src=http%3A%2F%2Fh.hiphotos.baidu.com%2Fzhidao%2Fpic%2Fitem%2F1e30e924b899a901934d50551d950a7b0208f55d.jpg'
img=requests.get(url).content
f=open('ooo.jpg','wb')
f.write(img)
wb模式时文件的一种操作模式,wb代表以二进制的格式写文件,还有以下几种方式:
"rb" 以二进制读方式打开,只能读文件 , 如果文件不存在,会发生异常
"wb" 以二进制写方式打开,只能写文件, 如果文件不存在,创建该文件
如果文件已存在,先清空,再打开文件
"rt" 以文本读方式打开,只能读文件 , 如果文件不存在,会发生异常
"wt" 以文本写方式打开,只能写文件, 如果文件不存在,创建该文件
如果文件已存在,先清空,再打开文件
"rb+" 以二进制读方式打开,可以读、写文件 , 如果文件不存在,会发生异常
"wb+" 以二进制写方式打开,可以读、写文件, 如果文件不存在,创建该文件
如果文件已存在,先清空,再打开文件
实战练习:
用学过的知识完成下面的小程序:
将文件中的内容个别文字进行批量替换。
with open('ttt','a+',encoding='utf-8')as f:
f.seek(0)
content=f.read()
new_content=content.replace('终于','还好')
f.seek(0)
f.truncate()
f.write(new_content)
f.flush()
上述方法我们直接取出所有内容,然后进行替换,清空源文件后再写入所有内容,这样的效率时不高的,文件小的时候还好,当文件有大量数据的时候,这种方法的效率就太低了。
我还可以用for循环的方式,进行逐行读取,逐行修改的方式,但在文件中我们没办法在原文件中取读完每一行就立马进行修改
所以可以分为以下几个步骤进行:
1、逐行高效读取文件,进行修改
2、将修改后的内容写入一个新的文件中
3、修改完成后删除原有文件
4、将新文件的名称修改为目标文件的名称
import os#导入文件操作模块
with open('ttt','r',encoding='utf-8')as f,open('new','a+',encoding='utf-8')as newf:#打开要修改的文件和一个新文件
for content in f:#遍历文件内容
new_content=content.replace('还好','终于')#替换文字
newf.write(new_content)#写入新文件
newf.flush()#立即刷新
os.remove('ttt')#删除原有文件
os.rename('new','ttt')#重命名文件
集合
在Python中集合set是基本数据类型的一种,它有可变集合(set)和不可变集合(frozenset)两种。创建集合set、集合set添加、集合删除、交集、并集、差集的操作都是非常实用的方法。
集合的一个特点是天生去重
创建集合:
s=set()#空集合
s2={'1','2','3','3','3'}
list=[1,2,3,4,5,5,5,5,5,5]
s3=set(list)
#list 去重的话,需要循环取出比较
print(list)
print(s2)
print(s3)
查看执行结果:
[1, 2, 3, 4, 5, 5, 5, 5, 5, 5]
{'2', '3', '1'}
{1, 2, 3, 4, 5}
取集合的数据:
s2={'1','2','3','3','3'}
#集合是无序的,没有办法通过下标来取数据
print(s2[2])
查看执行结果:
添加元素:
s2={'1','2','3','3','3'}
s2.add('5')#add方法可以添加一个元素
print(s2)
查看执行结果:
{'5', '2', '1', '3'}
如果想添加多个元素的话,可以用update
s2={'1','2','3','3','3'}
s2.update([7,8,9]) # 添加多个元素
print(s2)
执行查看结果:
{7, '2', 8, 9, '1', '3'}
删除元素:
删除元素可以用pop()随机删除一个元素:
s2={'1','2','3','4','8','9'}
print(s2.pop())
print(s2)
查看执行结果:
2
{'9', '1', '8', '4', '3'}
集合的运算操作:
交集、并集、差集、子集、包含
s2={'1','2','3','5'}
s3={'1','2','3','4','8','9'}
#交集
print(s2.intersection(s3))#intersection
print(s2&s3)#&f符号是取交集
查看执行结果:
{'1', '3', '2'}
{'1', '3', '2'}
#并集
print(s2.union(s3))#union取并集
print(s2|s3)#|取并集
查看执行结果:
{'8', '3', '5', '2', '1', '4', '9'}
{'8', '3', '5', '2', '1', '4', '9'}
# #差集
print(s2.difference(s3))#S2中有,但是S3中没有的
print(s3.difference(s2))#s3中有,但是s2中没有的
print(s2-s3)#-代表差集,S2中有,但是S3中没有的
查看执行结果:
{'5'}
{'4', '8', '9'}
{'5'}
子集、包含关系:
s2={'1','2','3'}
s3={'1','2','3','4','8','9'}
print(s2.issubset(s3)) #S2是S3的子集
print(s3.issuperset(s2))#S3包含S2
查看执行结果
True
True
函数
函数是把一堆代码合到一起,变成一个整体,是一个方法或者功能的代码段,可以重复使用。
定义一个函数的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
定义一个函数,并调用:hello world!
def hello():
print('hello world!')
hello() #函数的调用
查看执行结果:
hello world!
再来看一个实例:
def hello():
f=open('sss','a+')
f.seek(0)
f.write('www')
f.close()
hello()
查看执行结果:SSS文件中写入了www
参数传递
在 python 中,类型属于对象,变量是没有类型的
a=[1,2,3]
a="Runoob"
以上代码中,[1,2,3] 是 List 类型,"Runoob" 是 String 类型,而变量 a 是没有类型,她仅仅是一个对象的引用(一个指针),可以是 List 类型对象,也可以指向 String 类型对象。
实例:def hello(filename,content=''):#形参,形式参数
f = open(filename, 'a+',encoding='utf-8')
#return #函数中添加return时,结束后边的代码
if content:
f.seek(0)
f.write(content)
res=''
else:
f.seek(0)
res=f.read()
#return res return以后文件就不会被关闭了,所以要把return写到后边
f.close()
return res
print(hello('www','乒乒乓乓乒乒乓乓乒乒乓乓'))#实参,实际的参数
users=hello('aaa')
print(users)
上述代码我们可以看到,当content为空或不为空的时候会有一个判断,一个用来读文件,一个用来写文件
形参,实参
#形参,位置参数也叫必填参数
#默认参数,定义是有一个默认值
#默认值参数是不必填的
#函数里边的变量只能在函数里边用,出了函数里边就不能用了,如果想获取到函数的处理结果,必须return
#没有return的话,返回的是none,return是非必填的,需要返回值的时候再写
#函数中遇到return,函数运行结束
#所以return的两个作用:1、返回函数值,2、结束运行
可变参数,默认参数,扩展参数(不定长参数)
def test(a,b=1,*args):#b=1为默认参数,默认参数为非必填
print('a',a)
print('b',b)
print('args',args)
test('hhh')
test('aaa','222','22233','44444','5555')#位置调用,根据参数的位置指定
test(b=5,a=10)#关键字参数,关键字调用,与位置调用不能混用
查看执行结果:
a hhh
b 1
args ()
a aaa
b 222
args ('22233', '44444', '5555')
a 10
b 5
args ()
*args是可扩展参数,可扩展参数为非必填参数,会把多传的参数放到一个元祖中,可以自己定义名字。
参数为字典表的时候:
def test2(**kwargs):#keargs 字典
print(kwargs)
test2(name='222',sex='eee')#kwargs方式必须用关键字调用的方法
def test3(a,**kwargs):
print(a)
print(kwargs)
test3(a=10000,s='sss',d='ssss3')
查看执行结果:
{'sex': 'eee', 'name': '222'}
10000
{'d': 'ssss3', 's': 'sss'}
全局变量和局部变量
定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。
局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。
如下实例:
a=100 #全局变量
def test():
a=5 #局部变量
print('函数内部:',a)
def test2():
b=1
print(a)#获取的全局变量100
test()
test2()
print('函数外部',a)#直接打印的外部的全局变量
查看执行结果:
函数内部: 5
100
函数外部 100
如果函数内部要使用全局变量的话,可以单独进行声明:例如:
#
d=100
def test():
global d#声明一下这是全局变量
print('libian',d)
d=6
test()
print('waibian',d)
查看执行结果:
libian 100
waibian 6
我们看到函数内部因为生命了d为全局变量,所以执行的时候首先取全局变量d=100,然后对d进行了修改为d=6,所以全局变量变为6,再次打印的时候显示为6.
我们看一下下边这个例子:
money = 899
def test(consume):
return money-consume
def test1(money):
return test(money)+test(money)
money=test1(money)
print(money)
可以先预计一下执行结果,money=test1(money),先调用test1,money=899,执行test1又会调用test,传参都是money,所以test执行后是0,test1执行是0+0=0;
我们执行一下看预期结果:0 #执行结果是0,预期正确
再看一下下边的例子:
def test():
global f
f=5
print(f)
def test1():
c=f+5
return c
res=test1()
print(res)
看上边的例子,有的朋友会预期,调用test1,c=f+5,f=5,所以结果应该是10,我们执行看一下结果:
从执行结果看到报错了,因为没有f没有定义,那是因为我们调用的时候只调用了test1,没有调用test,所以系统是不知道f的值的,只有调用的时候才会进行运算。
正确的是:
def test():
global f
f=5
print(f)
def test1():
c=f+5
return c
test()#需要调用才会执行test,不调用不执行
res=test1()
print(res)
查看执行结果:10
实例演练:
写一个小程序,校验输入的字符串是否是一个合法的小数。
分析:1、小数分为正小数和负小数:例3.3,-2.33
2、小数有且只有一个小数点
3、校验字符串就要先把输入的内容强制类型转换成字符串类型
def check_float(number):
if number.count('.') == 1:#判断小数点的个数
num_left=number.split('.')[0]#以小数点为分割点
num_rigth=number.split('.')[1]
if num_left.isdigit() and num_rigth.isdigit():#小数点左边和右边都是数字,则为正小数
print('您输入的是正小数')
elif number.count('-')==1 and number.startswith('-'):#判断符号的个数,且以负号开头
if num_left.split('-')[1].isdigit() and num_rigth.isdigit():#小数点左边,负号右边都是数字,小数点右边都是数字
print('您输入的是负小数')
else:
print('您输入的不是小数')
else:
print('您输入的不是小数')
else:
print('您输入的不是小数!')
str=input('请输入要校验的字符串:')#input输入的就是字符串类型
check_float(str)#调用函数
大家可以自己试一下执行一下结果。
递归
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
下面看一个例子:
def test1():
num =int(input('请输入数字:'))
if num%2==0:
return True
else:
print('不是偶数请重新输入:')
return test1()
test1()
上述代码,表达了输入一个数字,如果是偶数,返回True ,如果是计数再次调用test1,知道输入的是偶数返回True为止。这种我们就叫它递归函数。
递归函数的优点是定义简单,逻辑清晰。
但是使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,
每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。递归最多调用999次;
比较两个字典的key和value:
########################################
#对比两个报文中不一样值
#1、循环第一个字典,取出key
#2、拿第一个的key去第二个的字典中去取值
d1={'a':'1','b':'2'}
d2={'a':'1','b':'3'}
def compare(d1,d2):
for key in d1:
v1=d1.get(key)
v2=d2.get(key)
if v2:
if v1==v2:
pass
else:
print('两个值不一样,不一样的key:%s,v1的值:%s,v2的值:%s'%(key,v1,v2))
compare(d1,d2)
查看执行结果:
两个值不一样,不一样的key:b,v1的值:2,v2的值:3
判断对象的数据类型方法,可以用type()方法,如:
def print_var_type(var):
if type(var)==str:#字符串类型
print('string')
elif type(var)==dict:#字典类型
print('dict')
elif type(var)==list:#列表类型
print('list')
s={'name':'pei'}
print_var_type(s)
执行查看结果:
dict
模块
在Python中用关键字import来引入某个模块,比如要引用模块math,就可以在文件最开始的地方用import math来引入。
如果需要调用模块中的函数的话,需要用 模块名.函数名 进行调用。
#一个python文件就是一个模块
#1、标准模块
# python自带的,不需要安装的
#2、第三方模块
# 别人写的,只要安装就能使用
#3、自己写的模块
# pip install radis #直接pip install 就可以 在 python安装目录下的scripts,加到环境变量中
#下载好安装包手动安装,解压,在命令行里边进入到解压后的目录,在执行python setup.py install
#或者进入到目录中,shift+右键,在当前窗口打开命令,输入python setup.py install
例如上面的校验是否是小数的函数,存放在check.py文件中,如果需要直接调用函数,则需要导入这个模块,如下:
import cheak
print(cheak.check_float('1.6'))
执行的话就会直接调用check中的check_float 函数。
#导入python文件的实质是从头到尾运行一次
#
#import play
#import 导入文件的时候是在当前目录下找文件,
#当前目录找不到的话,从环境变量里面找
#环境变量就是让一个命令在任何目录下都能执行
#查看当前系统的环境变量目录
import sys
print(sys.path)
实战演练:
需求:access.log日志
60S内同一个IP地址访问超过200次,IP加入黑名单
#60s读一次文件
#以空格切割,取第一个元素,获取到IP
#把IP地址存入list,如果大于200次,则加入黑名单
import time
point = 0#文件指针
while True:
ips=[]#空列表,用于存放所有IP
bip = set()#定义一个空集合,用于存放需要加入黑名单的
with open('access.log') as f:
f.seek(point)
for line in f:
ip=line.split()[0]#分割每一行,默认split是以空格分隔
ips.append(ip)#添加到list中
if ips.count(ip)>199:#判断
bip.add(ip)
for i in bip:#bip为集合,存入的去重的ip
print('已经把%s加入黑名单'%i)
point=f.tell()
time.sleep(60)
else:
print('没有被攻击')