大家好,本文将围绕Python编程案例教程 刘庆,姚丽娜,余美华课后答案展开说明,Python编程案例教程 刘庆,姚丽娜,余美华电子版是一个很多人都想弄明白的事情,想搞清楚Python编程案例教程课后答案需要先了解以下几个事情。
目录:
- 1.判断合法用户
- 2.编写判断成绩的程序
- 3.编写石头剪刀布小游戏
- 4.斐波那契数列
- 5.九九乘法表
- 6.用列表构建栈结构
- 7.模拟用户登陆信息系统
- 8.挑选文件相同的部分
- 9.记账程序
- 10.简单的加减法数学游戏
- 11.快速排序
- 12.备份程序
- 13.扫描全网段存活主机--黑客攻击手段方式之一
说明:每一个实例都用到了python中的一些特性,需要提前知道以下知识点:
【Python数据类型,列表、字典与集合】Python数据类型,列表、字典与集合_JJH的创世纪的博客-CSDN博客
【python字符串与format格式化】python字符串与format格式化_JJH的创世纪的博客-CSDN博客
【python模块入门】python模块入门_JJH的创世纪的博客-CSDN博客
【python函数入门,偏函数,匿名函数,递归函数】python函数入门,偏函数,匿名函数,递归函数_JJH的创世纪的博客-CSDN博客
【python系统管理模块,shutil,subprocess,os,pickle】python系统管理模块,shutil,subprocess,os,pickle_JJH的创世纪的博客-CSDN博客
【Python打开文件与读写文件】Python对Linux系统的操作,打开文件与读写文件,python模拟linux的cp操作_python读取linux文件_JJH的创世纪的博客-CSDN博客
【Python *号的含义】python *号的含义_python 中*号_JJH的创世纪的博客-CSDN博客
1.判断合法用户
使用到的技术点:if..else判断,隐藏密码(getpass.getpass("")),调用函数
java语言判断可简写为 a=x>y?x:y;
python的判断可以简写为a= x if x<y else y
要求
- 提示用户输入用户名和密码
- 将用户名和密码分别保存在变量中
- 如果用户名为bob并且密码为123456,则输出Login successful,否则输出Login inorrect
- [root@localhost day02]# vim login.py
- #!/usr/bin/env python3
- import getpass #调用该函数可以在命令行窗口里面无回显输入密码
- username = input('username: ')
- password = getpass.getpass('password: ')
- if username == 'bob' and password == '123456':
- print('\033[32;1mLogin successful!\033[0m') #绿色粗体显示
- else:
- print('\033[31;1mLogin incorrect!\033[0m') #红色粗体显示
2.编写判断成绩的程序
使用到的技术:#coding: utf8(支持中文)
python没有switch..case的语法,所以只能用if..else写
- [root@localhost day02]# vim grade.py
- #!/usr/bin/env python3
- #coding: utf8 #为了程序可以支持中文,指定UTF8编码
- score = int(input('成绩:'))
- if score >= 90:
- print('优秀')
- elif score >= 80:
- print('好')
- elif score >= 70:
- print('良')
- elif score >= 60:
- print('及格')
- else:
- print('你要努力了!')
3.编写石头剪刀布小游戏
引用random模块生成0-2的随机数,提示并获取用户的整数输入值,应用if扩展语句对随机数与输入值进行对比判断,满足指定条件,输出结果
为简化代码,玩家获胜条件中用and和or两个逻辑运算符进行多个条件内容的判断,用括号来区分运算优先级,所以用户获胜条件为以下3项中任意一项:
1.用户输入剪刀并且随机数是布
2.用户输入石头并且随机数是剪刀
3.用户输入布并且随机数是石头
版本1:输入数字版本
使用到的技术点: random.randint(n,m)(随机整数),and和or逻辑运算符
- #!/usr/bin/env python3
- import random
- #1. 提示并获取用户的输入
- player = int(input("请输入 0剪刀 1石头 2布:"))
- #2. 让电脑出一个随机数
- computer = random.randint(0,2)
- #3. 判断用户的输入,然后显示对应的结果
- #if 玩家获胜的条件:
- if (player==0 and computer==2) or (player==1 and computer==0) or (player==2 and computer==1):
- print("赢了,,,,可以去买奶粉了.....")
- #elif 玩家平局的条件:
- elif player==computer:
- print("平局了,,,洗洗手决战到天亮....")
- else:
- print("输了,,,回家拿钱 再来....")
版本2:输入字符串版本
使用到的技术点:random.choice([*])随机字符串,%s,%s %(x,y)占位符
- #!/usr/bin/env python3
- import random
- computer = random.choice(['石头', '剪刀', '布'])
- player = input('请出拳(石头/剪刀/布):')
- # print('您出了:', player, '计算机出的是:', computer)
- print('您出了: %s, 计算机出的是: %s' % (player, computer))
- if player == '石头':
- if computer == '石头':
- print('平局')
- elif computer == '剪刀':
- print('You WIN!!!')
- else:
- print('You LOSE!!!')
- elif player == '剪刀':
- if computer == '石头':
- print('You LOSE!!!')
- elif computer == '剪刀':
- print('平局')
- else:
- print('You WIN!!!')
- else:
- if computer == '石头':
- print('You WIN!!!')
- elif computer == '剪刀':
- print('You LOSE!!!')
- else:
- print('平局')
版本3:列表判断法
使用到的技术点: [a,b] in 元组
这里使用到了二维元组(二维数组),使用in判断一维元组是否在二维元组中
- import random
- all_choices = ['石头', '剪刀', '布']
- win_list = [['石头', '剪刀'], ['剪刀', '布'], ['布', '石头']]
- prompt = '''(0) 石头
- (1) 剪刀
- (2) 布
- 请选择(0/1/2):'''
- computer = random.choice(all_choices)
- ind = int(input(prompt))
- player = all_choices[ind]
- print('您出了: %s, 计算机出的是: %s' % (player, computer))
- if player == computer:
- print('\033[32;1m平局\033[0m')
- elif [player, computer] in win_list:
- print('\033[31;1mYou WIN!!!\033[0m')
- else:
- print('\033[31;1mYou LOSE!!!\033[0m')
4.斐波那契数列
使用到的技术点:函数,range,元组,len
函数与java的方法一样,但是不同的是python不需要定义类型,自己用一个字符或字符串即可
range(n) n为一个数字,比如10,range(10)代表从0数到9
元组,元组的[-1]代表最后一个数字,[-2]代表倒数第二个数字
len(元组名)得到元组的长度
- #!/usr/bin/env python3
- def gen_fib(x):
- fib = [0, 1]
- #使用x-数字长度
- for i in range(x - len(fib)):
- fib.append(fib[-1] + fib[-2])
- return fib # 返回列表,不返回变量fib
- a = gen_fib(10)
- print(a)
- print('-' * 50)
- n = int(input("length: "))
- print(gen_fib(n)) # 不会把变量n传入,是把n代表的值赋值给形参
5.九九乘法表
使用到的技术:for循环嵌套技术,for循环变量的输出
与c一样,python需要用到占位符,占位符的格式是 ("%d" % (i))
一定要是双引号,一定要加%,一定要打(),占位符要与后面的实际变量是同一种类型
- #!/usr/bin/env python3
- for i in range(1, 10): # [0, 1, 2]
- for j in range(1, i+1): # i->0:[0], i->1: [0, 1], i->2: [0, 1, 2]
- print('%sX%s=%s' % (j, i, i*j), end=' ')
- print()
- [root@localhost day03]# vim mtable.py
- #!/usr/bin/env python3
- i=1
- while i<=9:
- j=1
- while j<=i:
- print("%d*%d=%d" % (j,i,j*i),end=" ")
- j+=1
- print("")
- i+=1
6.用列表构建栈结构
使用到的技术:列表的内建函数,pop,append
要求:
- 栈是一个后进先出的结构
- 编写一个程序,用列表实现栈结构
- 需要支持压栈、出栈、查询功能
方案:
创建空列表存储数据,创建4个函数,分别实现压栈、出栈、查询以及判断函数调用的方法PYTHON库“IMITATION”。
此程序需要注意的是堆栈的结构特点,先进后出,后进先出:
- 1.调用show_menu()函数后,利用while循环交互端输出提示,请用户input0/1/2/3任意数值,如果输入的值不是0/1/2/3,打印输入值无效请重新输入并重新开始循环,如果输入的值是3,停止整个循环,如果输入的值是0/1/2通过字典键值对关联关系,调用相对应函数
- 2.如果输入的值是0,字典cmds中0键对应的值是push_it,push_it()调用压栈函数,压栈函数利用stack.append()方法将输入数据追加到列表结尾
- 3.如上,如果输入的值是1,调用出栈函数pop_it(),出栈函数如果stack列表中有数据,弹出列表最后一个元素(根据堆栈结构特点stack.pop()中参数为空),如果stack列表没有数据,输出空列表
- 4.如果输入的值是2,调用查询函数view_it(),显示当前列表
#!/usr/bin/env python3
stack=[]
def push_it():
item=input('item to push:')
stack.append(item)def pop_it():
if stack:
print("\033[31;1mPopped %s\033[0m" % stack.pop())
else:
print('\033[31;1mEmpty stack\033[0m')def view_it():
print("\033[32;1m%s\033[0m" % stack)def show_menu():
prompt="""(0) push_it
(1) pop_it
(2) view_it
(3) quit
Please input you choice(0/1/2/3):"""
cmds = {'0': push_it, '1': pop_it, '2': view_it}while True:
choice=input(prompt).strip()[0]
if choice not in '0123':
print('Invalid input.Try again')
continue
if choice=='3':
break
cmds[choice]()if __name__=="__main__":
show_menu()
提示:这里有几个地方要注意
cmds = {'0': push_it, '1': pop_it, '2': view_it} :这里是做了一个集合,根据用户输入的值选取方法
choice=input(prompt).strip()[0] :这里的意思是先对输入值剔除空格```*``然后取第一个值神码ai伪原创工具。因为如果输入12,会没有这个选项,但是这里取0位,系统就会判断选择1
7.模拟用户登陆信息系统
使用到的技术:列表与字典
要求:
- 支持新用户注册,新用户名和密码注册到字典中
- 支持老用户登陆,用户名和密码正确提示登陆成功
- 主程序通过循环询问进行何种操作,根据用户的选择,执行注册或是登陆操作
方案:
创建空字典存储用户名、密码,用三个函数分别实现用户注册、用户登录以及判断调用函数这三个方法,完成模拟用户登录:
- 1.调用show_menu()函数后,利用while循环交互端输出提示,请用户input0/1/2任意数值,如果输入的值不是0/1/2,打印选择无效请重新输入并重新开始循环,如果输入的值是2,停止整个循环,如果输入的值是0/1/2通过字典键值对关联关系,调用相对应函数
- 2.如果输入的值是0,字典cmds中0键对应的值是register,register ()调用注册函数,函数利用if方法判断输入用户名是否存在,如果用户名在字典中,输出用户名已存在,否则用户输入密码,并将用户名与密码以键值对形式放入字典中
- 3.如上,如果输入的值是1,调用登录函数login(),利用if方法判断输入的用户名的对应的密码是否和字典中存储用户对应密码相同,如果不同显示登录失败,否则登录成功,此函数中导入getpass模块使用方法,作用是输入密码不可见。
#!/usr/bin/env python3
userdb={}
#用户注册
def register():
username=input('username:')
if username in userdb:
print('\033[31;1m%s already exists.\033[0m' % username)
else:
password=input('password:')
userdb[username]=password#用户登录
def login():
username=input('username:')
password=input('password:')
if userdb.get(username)!=password:
print('\033[31;1mLogin incorrect\033[0m')
else:
print('\033[32;1mLogin successful\033[0m')#显示当前用户
def show_userinfo():
print(userdb)def show_menu():
prompt="""(0)regiter
(1)login
(2)userinfos
(3)quit
Please input you choice(0/1/2/3):"""
cmds={'0':register,'1':login,"2":show_userinfo}
while True:
choice=input(prompt).strip()[0]
if choice not in '0123':
print('Invalid choice.Try again')
continue
if choice=='3':
break
cmds[choice]()if __name__=='__main__':
show_menu()
提示:print('\033[31;1mLogin incorrect\033[0m'):这里的\033[31;1m是Linux加颜色操作
cmds={'0':register,'1':login,"2":show_userinfo}:这里是做了一个集合,根据用户输入的值选取方法
choice=input(prompt).strip()[0] :这里的意思是先对输入值剔除空格。然后取第一个值。因为如果输入12,会没有这个选项,但是这里取0位,系统就会判断选择1
8.挑选文件相同的部分
使用到的技术:集合
要求:
- 有两个文件:a.log和b.log
- 两个文件中有大量重复内容
- 找出其中重复的内容
方案:
- 打开两个文件并作为集合
- 使用&符号取集合中的交集
#!/usr/bin/env python3
with open('/mnt/123') as f1:
aset=set(f1)with open('/mnt/23') as f2:
bset=set(f2)with open('/mnt/3','w') as f3:
f3.writelines(aset&bset)
9.挑选文件不相同部分
要求:
- 有两个文件:a.log和b.log
- 两个文件中有大量重复内容,也有非重复部分
- 找出a.log中存在而b.log中不存在的内容
方案:
- 打开两个文件并作为集合
- 使用-符号将a的内容减去b的内容,剩下的就是a中有而b中没有的内容
#!/usr/bin/env python3
with open('/mnt/123') as f1:
aset=set(f1)with open('/mnt/23') as f2:
bset=set(f2)with open('/mnt/3','w') as f3:
f3.writelines(aset-bset)
9.记账程序
使用到的技术:python异常,pickle模块,文件创建与打开
要求:
- 假设在记账时,有一万元钱
- 无论是开销还是收入都要进行记账
- 记账内容包括时间、金额和说明等
- 记账数据要求永久存储
方案:
创建4个函数,分别实现记录开销、记录收入、查询收支、判断函数调用的四个方法,导入时间模块获取时间,导入os模块判断文件是否存在,导入pickle模块用来python特有类型与数据类型转换:
- 1.调用show_menu()函数后,先判断记录余额文件是否存在,如果不存在创建文件并写入余额,如果存在,利用while循环在交互端输出提示,请用户input0/1/2/3任意数值,如果输入的值不是0/1/2/3,打印输入值无效请重新输入并重新开始循环,如果输入的值是3,停止整个循环,如果输入的值是0/1/2通过字典键值对关联关系,调用相对应函数
- 2.如果输入的值是0,字典cmds中0键对应的值是spend_money,调用spend_money ()记录开销函数,让此函数实现获取当前系统日期、输入开销金额、输入开销备注信息、以二进制读方式打开记录余额文件计算本次开销后余额,以写方式打开记录余额文件将计算后开销余额写入文件,以追加方式打开记账文件,将日期、开销、备注、余额写入追加入记账文件最后
- 3.如果输入的值是1,字典cmds中0键对应的值是save_money,调用save_money ()记录收入函数,让此函数实现获取当前系统日期、输入收入金额、输入收入备注信息、以二进制读方式打开记录余额文件计算本次收入后余额,以写方式打开记录余额文件将计算后收入余额写入文件,以追加方式打开记账文件,将日期、开销、备注、余额写入追加入记账文件最后
- 4.如果输入的值是2,调用查询收支函数query (),以二进制读方式打开记账文件,利用for循环遍历文件中数据,打印出来,打开记录余额文件读取余额并打印。
需要注意的是:为确保代码可以正常执行,while循环利用try except语句处理异常,优先匹配特殊异常,让用户按下Ctrl+C或Ctrl+D可以退出程序,遇到索引错误可以结束当次循环,重新开始选择选项。
将记录余额文件以及记账文件作为参数传入函数中
#!/bin/usr/env python3
#encode=utf-8import time
import os
import pickle as p#支出
def spend_money(record,wallet):
date=time.strftime("%Y-%m-%d") #获取时间
amount=int(input('金额:')) #支出金额
comment=input('备注:')
with open(wallet,'rb') as f: #读取余额
balance=p.load(f)-amount #剩余金额=账户金额-支出金额
with open(wallet,'wb') as f:
p.dump(balance,f) #写入账户
with open(record,'a') as f: #将这笔交易记录写入文本
f.write(
"%-20s-%-20s%-10s%-20s\n"
% (date,amount,balance,comment)
)#流入
def save_money(record,wallet):
date=time.strftime('%Y-%m-%d')
amount=int(input('金额:'))
comment=input('备注: ')
with open(wallet,'rb') as f: #获取金额
balance=p.load(f)+amount #剩余金额=流入金额+账户金额
with open(wallet,'wb') as f:
p.dump(balance,f) #写入账户
with open(record,'a') as f: #将这笔交易记录写入文本
f.write(
"%-20s+%-20s%-10s%-20s\n" %
(date,amount,balance,comment)
)#查询金额
def query(record,wallet): #打开记录不能
with open(record) as f:
for line in f:
print(line,end="") #输出每行内容
with open(wallet,'rb') as f:
balance=p.load(f)
print('当前余额 %s' % balance)def show_menu():
prompt=""" (0) 记录开销
(1) 纪录收入
(2) 查询记录
(3) 退出
请选择(0/1/2/3):"""cmds={'0':spend_money,'1':save_money,'2':query}
record='record.txt'
wallet='wallet.data'
if not os.path.exists(wallet): #初始化创建账本
with open(wallet,'wb') as f:
p.dump(10000,f)
if not os.path.exists(record): #初始化创建记录本
with open(record,'a') as f:
f.write(
"%-20s%-15s%-10s%-20s\n" %
("时间","流入(+)/支出(-)","余额","备注")
)
while True:
try:
choice=input(prompt).strip()[0] #获取输入结果,必须为数字
except IndexError:
continue
except (KeyboardInterrupt,EOFError):
print('\nBye-bye')
choice='3'
if choice not in '0123':
print('无效输入,请重试')
continue
if choice == '3':
break
cmds[choice](record,wallet)
if __name__ == '__main__':
show_menu()
10.简单的加减法数学游戏
要求:
- 随机生成两个100以内的数字
- 随机选择加法或是减法
- 总是使用大的数字减去小的数字
- 如果用户答错三次,程序给出正确答案
方案:
创建4个函数,分别实现返回两数之和、返回两数之差、判断表达式正确性、是否继续计算四种方法:
- 1.首先调用main()函数(是否继续计算功能),main函数利用循环无限次调用exam()函数进行计算,计算结束,用户选择是否继续(此过程利用try语句捕获索引错误、ctrl+c(中断)错误、ctrl+d错误),如果选择n即结束循环,不再调用exam()函数,否则循环继续
- 2.调用exam()函数:
- a)输出运算公式:利用列表切片将随机生成的两个数打印(这两个数利用random模块及列表生成式随机生成,并利用sort()方法进行降序排序,确保相减时一直是大的数字减小的数字),利用random模块随机生成“+”“-”号,输出在两数之间
- b)用户输入值,利用for循环进行三次判断,如果运算公式结果与用户输入值相同,循环结束,系统输出“你赢了”,exam()函数执行结束,否则系统输出“你答错了”,循环继续,3次都回答错误,利用循环的else分支输出运算公式及结果
- c)上诉运算公式结果:利用random模块随机生成“+”“-”值对关系调用(其中“+”“-”号作为字典键,返回和函数add()及返回差函数sub()作为值,调用时将随机生成的两个数字作为参数传递给add()函数及sub()函数)
- [root@localhost day06]# vim math_game.py
- #!/usr/bin/env python3
- import random
- def add(x, y):
- return x + y
- def sub(x, y):
- return x - y
- def exam():
- cmds = {'+': add, '-': sub} # 将函数存入字典
- nums = [random.randint(1, 100) for i in range(2)] # 生成两个数
- nums.sort(reverse=True) # 降序排列
- op = random.choice('+-')
- result = cmds[op](*nums) # 调用存入字典的函数,把nums列表拆开,作为参数传入
- prompt = "%s %s %s = " % (nums[0], op, nums[1])
- for i in range(3):
- try:
- answer = int(input(prompt))
- except:
- continue
- if answer == result:
- print('你真棒,答对了!')
- break # 答对了就不要再回答了,结束循环
- else:
- print('答错了')
- else:
- print("%s%s" % (prompt, result)) # 只有循环不被break才执行
- def main():
- while True:
- exam()
- try:
- go_on = input('Continue(y/n)? ').strip()[0]
- except IndexError: #异常处理,[0]索引不存在抛出异常
- continue
- except (KeyboardInterrupt, EOFError):
- go_on = 'n'
- if go_on in 'nN':
- print('\nBye-bye.')
- break
- if __name__ == '__main__':
- main()
提示:
1、cmds = {'+': add, '-': sub} 这里采用了字典的方式存储函数,通过关键字来调用函数,是一种良好的编程模式。将实际的‘+’符号联系到函数上。
2、nums = [random.randint(1, 100) for i in range(2)] 这里采用了列表表达式,生成两个1-100的整数
3、nums.sort(reverse=True) 用到了sort内置函数排序,并且是降序排序
4、op = random.choice('+-')
result = cmds[op](*nums) 这两句应该是较难理解的地方, cmds[op]其实相当于add(),而(*nums)则相当于两个参数x,y
那么这一句可以等价为 result=add(*nums)
【Python *号的含义】python *号的含义_python 中*号_JJH的创世纪的博客-CSDN博客
11.快速排序
要求:
- 随机生成10个数字
- 利用递归,实现快速排序
方案:
将要排序的数据分割成独立的三部分,任意选取一个数据作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一趟快速排序,整个排序过程通过递归进行,以此达到整个数据变成有序序列。
一趟快速排序的算法是:
- 1.创建两个空列表分别用于存放比关键数小的数据和比关键数大的数据smaller和larger
- 2.For循环遍历将要排序的数据,将数据与关键数对比,比关键数小的放入smaller列表中,比关键数大的放入larger列表中
- 3.函数返回值为,以smaller列表为参数调用自身函数、关键数、以larger列表为参数调用自身函数:此时,函数每一次调用都会基于上一次的调用进行,会持续调用自身函数,参数列表数据会越来越少,我们规定,参数列表长度为0或1,递归结束,输出最终数据
- 4.注意:在调用qsort函数时,根据上传数据类型不同,一定要注意数据类型转化
- [root@localhost day05] # vim qsort.py
- #!/usr/bin/env python3
- from random import randint
- def qsort(data):
- data = list(data)
- if len(data) == 0 or len(data) == 1: # 长度为0或1,直接返回
- return data
- middle = data.pop() # 假设最后一项是中间值
- smaller = []
- larger = []
- for item in data:
- if item > middle: # 比middle大的放到larger,否则放到smaller
- larger.append(item)
- else:
- smaller.append(item)
- return qsort(smaller) + [middle] + qsort(larger)
- if __name__ == '__main__':
- nums = [randint(1, 100) for i in range(10)]
- print(nums)
- print(qsort(nums))
提示:快速排序有几种写法,这里只是一种,利用递归按一个中间值不断将列表分成两部分(大于或小于中间值),当一个部分的长度为1时返回,再将两部分合成一部分,并返回
12.备份程序
要求:
- 需要支持完全和增量备份
- 周一执行完全备份
- 其他时间执行增量备份
- 备份文件需要打包为tar文件并使用gzip格式压缩
方案:
整体框架创建3个函数,分别实现完全备份、增量备份、文件加密3种功能:
- 1.首先导入time模块,利用if进行判断,如果当地时间是星期一,执行完全备份函数,否则执行增量备份函数,其中,通配符%a代表时间星期几缩写,上传参数分别为要备份的原目录、目标目录、md5字典存放目录
- 2.调用完全备份函数:
- a)首先获取新文件名,将新文件名放入目标目录下,目的是定义备份文件的绝对路径,以写压缩方式打开目标目录下新文件,将原目录写入新文件中,完成完全备份,其中os.path.join作用是将目录名和文件的基名拼接成一个完整的路径
- b)了解os.walk()目录遍历器输出文件结构,利用for循环将要备份原目录中文件遍历出来作为字典键值对键, md5加密结果作为字典键值对的值(此时将原目录中文件作为上传参数调用文件加密函数),存入空字典中,字典中每个文件对应一个md5值,最后将字典写入到md5字典存放目录中
- 3.调用文件加密函数:将原目录文件循环读取逐一加密,返回加密结果
- 4.调用增量备份函数:
- a)增量备份函数代码与完全备份函数基本一致
- b)区别在于,备份前要先以二进制读方式打开md5字典存放目录,读取旧数据,判断旧数据中键对应的加密值与新加密值是否相同,如果不相同,则将新增内容写入到目标文件中(即只备份新数据)
- 5.注意:md5主要用于原文件与新文件判断
#!/usr/bin/env pythoy3
import time
import os
import tarfile
import hashlib
import pickle#用于判断两个文件是否相同,提取每个文件中的前4字节的内容然后输出md5码进行比较
def check_md5(fname):
m = hashlib.md5()
with open(fname, 'rb') as fobj:
while True:
data = fobj.read(4096)
if not data:
break
m.update(data)
return m.hexdigest()#完全备份
#src_dir(被备份的文件),dst_dir(将次文件备份到哪),md5file(摘要值记录文件)
def full_backup(src_dir,dst_dir,md5file):
fname='%s_full_%s.tar.gz' % (fname,time.strftime('%Y%m%d')) #获取当前时间
fname=os.path.join(dst_dir,fname) #将格式转换为 :/文件目录/%s_full_%s.tar.gz
md5dict={}#备份文件
tar=tarfile.open(fname,'w:gz')
tar.add(src_dir)
tar.close()#更新摘要值
for path,folders,files in os.walk(src_dir):
for each_file in files:
key=os.path.join(path,each_file)
md5dict[key]=check_md5(key)#将摘要值存入文件
with open(md5file,'wb') as f:
pickle.dump(md5dict,f)#增量备份
#src_dir(被备份的文件),dst_dir(将次文件备份到哪),md5file(老的摘要值)
def incr_backup(src_dir,dst_dir,md5file):
fname='%s_incr_%s.tar.gz' % (fname,time,strftime('%Y%m%d'))
fname=os.path.join(dst_dir,fname)
md5dict={}with open(md5file,'rb') as f: #打开老的MD5文件
oldmd5=pickle.load(f)for path, folders, files in os.walk(src_dir): #记录新的MD5摘要值
for each_file in files:
key = os.path.join(path, each_file)
md5dict[key] = check_md5(key)
with open(md5file, 'wb') as fobj:
pickle.dump(md5dict, fobj)
with open(md5file,'wb') as f: #将新的MD5文件写到md5dict集合中
pickle.dump(md5dict,f)tar=tarfile.open(fname,'w:gz')
for key in md5dict: #新的MD5值与老的MD5值对比
if oldmd5.get(key) != md5dict[key]
tar.add(key)
tar.close()
if __name__ == '__main__':
# mkdir /tmp/demo; cp -r /var/log /tmp/demo 将日志文件作为实验读写
src_dir = '/tmp/demo/log' #源文件
dst_dir = '/var/tmp/backup' # mkdir /var/tmp/backup 压缩包存放的位置
md5file = '/var/tmp/backup/md5.data' #MD5摘要信息存放文件
if time.strftime('%a') == 'Mon': #获取日期,当周一时触发完全备份
full_backup(src_dir, dst_dir, md5file)
else: #获取日期,非周一时触发增量备份
incr_backup(src_dir, dst_dir, md5file)
提示:本实验难点较大,主要有以下二点
1.解压模块tarfile,了解如何压缩文件与添加新的文件到老的压缩文件中
2.摘要信息模块hashlib,了解如何获取文件的摘要信息
13.小实验2--扫描存活主机
需求:
我们可以写一个python脚本,扫描局域网所有的存活主机,如果懂ip协议的话,可以知道局域网的ip有其专用地址,如下
IPv4专用地址如下:
Class A 10.0.0.0-10.255.255.255
默认子网掩码:255.0.0.0
Class B 172.16.0.0-172.31.255.255
默认子网掩码:255.240.0.0
Class C 192.168.0.0-192.168.255.255
默认子网掩码:255.255.0.0
这里只举例ping一个网段,读者可以尝试用多个循环去完成实验
要求:
创建mtping.py脚本,实现以下功能:
- 通过ping测试主机是否可达
- 如果ping不通,不管什么原因都认为主机不可用
- 通过多线程方式实现并发扫描
方案:
- subprocess.call ()方法可以调用系统命令,其返回值是系统命令退出码,也就是如果系统命令成功执行,返回0,如果没有成功执行,返回非零值。
- 调用Ping对象,可以调用系统的ping命令,通过退出码来判断是否ping通了该主机。如果顺序执行,每个ping操作需要消耗数秒钟,全部的254个地址需要10分钟以上。而采用多线程,可以实现对这254个地址同时执行ping操作,并发的结果就是将执行时间缩短到了10秒钟左右。
实现此案例需要按照如下步骤进行。
步骤一:编写脚本
- [root@localhost day09]# vim mtping.py
- #!/usr/bin/env python3
- import subprocess
- import threading
- def ping(host):
- rc = subprocess.call(
- 'ping -c2 %s &> /dev/null' % host,
- shell=True
- )
- if rc:
- print('%s: down' % host)
- else:
- print('%s: up' % host)
- if __name__ == '__main__':
- ips = ['172.40.58.%s' % i for i in range(1, 255)]
- for ip in ips:
- t = threading.Thread(target=ping, args=(ip,)) # 创建线程,ping是上面定义的函数, args是传给ping函数的参数
- t.start() # 执行ping(ip)
面向对象代码编写方式如下:
- 定义Ping类,该类可实现允许ping通任何主机功能:
- 1.利用__init__方法初始化参数,当调用Ping类实例时,该方法自动调用
- 2. 利用__call__()方法让Ping类实例变成一个可调用对象调用,调用t.start()时, 引用subprocess模块执行shell命令ping所有主机,将执行结果返回给rc变量,此时,如果ping不通返回结果为1,如果能ping通返回结果为0
- 3.如果rc变量值不为0,表示ping不通,输出down
- 4.否则,表示可以ping通,输出up
- 利用列表推导式生成整个网段的IP地址列表[172.40.58.1,172.40.58.2....]
- 循环遍历整个网段列表,直接利用 Thread 类来创建线程对象,执行Ping(ip)。
- [root@localhost day09]# vim mtping2.py
- #!/usr/bin/env python3
- import threading
- import subprocess
- class Ping:
- def __init__(self, host):
- self.host = host
- def __call__(self):
- rc = subprocess.call(
- 'ping -c2 %s &> /dev/null' % self.host,
- shell=True
- )
- if rc:
- print('%s: down' % self.host)
- else:
- print('%s: up' % self.host)
- if __name__ == '__main__':
- ips = ('172.40.58.%s' % i for i in range(1, 255)) # 创建生成器
- for ip in ips:
- # 创建线程,Ping是上面定义的函数
- t = threading.Thread(target=Ping(ip)) # 创建Ping的实例target=Ping(ip)实例化对象
- t.start() #执行Ping(ip)
步骤二:测试脚本执行
- [root@localhost day09]# python3 udp_time_serv.py
- 172.40.58.1: up
- 172.40.58.69: up
- 172.40.58.87: up
- 172.40.58.90: up
- 172.40.58.102: up
- 172.40.58.101: up
- 172.40.58.105: up
- 172.40.58.106: up
- 172.40.58.108: up
- 172.40.58.110: up
- 172.40.58.109: up
- ...
- ...
- ...
- ...
- 172.40.58.241: down
- 172.40.58.242: down
- 172.40.58.243: down
- 172.40.58.245: down
- 172.40.58.246: down
- 172.40.58.248: down
- 172.40.58.247: down
- 172.40.58.250: down
- 172.40.58.249: down
- 172.40.58.251: down
- 172.40.58.252: down
- 172.40.58.253: down
- 172.40.58.254: down
总结:
以上提供了两种编程思路,一个是传递可调用类给Thread类,第二个是传递可调用函数给Thead类,推荐使用第一种方法。