函数
- 关键字参数:在函数调用时,参数是key=val的形式,被称作关键字参数
- 位置参数:在函数调用时,参数只有参数名的形式,被称作位置参数
>>> def func1(name, age):
... print('%s is %s years old' % (name, age))
...
>>> func1('tom', 20) # OK
tom is 20 years old
>>> func1(20, 'tom') # 语法没错,语义不对
20 is tom years old
>>> func1(age=20, name='tom') # OK
tom is 20 years old
>>> func1(age=20, 'tom') # Error,关键字参数必须在位置参数后
File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument
>>> func1(20, name='tom') # Error,name得到了多个值
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func1() got multiple values for argument 'name'
>>> func1('tom', age=20) # OK
tom is 20 years old
>>> func1() # Error,参数不够
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func1() missing 2 required positional arguments: 'name' and 'age'
>>> func1('tom', 20, 200) # Error,参数太多
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: func1() takes 2 positional arguments but 3 were given
- 声明函数时,在参数名前加上*,表示使用元组接收参数
def func1(*canshu):
print(canshu)
if __name__ == '__main__':
func1()
func1('tom')
func1('hao', 123, 'jerry', 456)
- 声明函数时,在参数名前加上**,表示使用字典接收参数
def func2(**canshu):
print(canshu)
if __name__ == '__main__':
func2()
func2(name='tom', age=20)
- 调用函数时,在数据类型前加*表示将序列对象拆开
- 调用函数时,在数据类型前加**表示将字典对象拆成key=val的形式
def func3(name, age):
print('%s is %s years old' % (name, age))
if __name__ == '__main__':
user = ['tom', 20]
func3(user[0], user[1])
func3(*user) #*的作用是将列表拆开并把值当作位置参数传进去
d1 = {'age': 20, 'name': 'jerry'}
func3(d1['name'], d1['age'])
func3(**d1) 想当于把字典拆为: # func3(name='jerry', age=20)
案例1
随机出100以内加减法
共有三次机会,三次都答错后输出正确答案
from random import randint,choice
def exam(): #用于出题让用户作答
#随机生成两个字符,并排序
nums = [randint(1,100) for i in range(2)] #从1-100之间随机取出2个数
#randint(1,100)就是从1-100里取值
#range(2)的值为0,1;从0开始,结束不包含
#range(2) 可用list(range(2))展开
nums.sort() #默认将值升序排列
nums.reverse() #将值反转,由大到小排列。
#随机生成加减法
op = choice('+-')
#计算标准答案
if op == '+':
result = nums[0] + nums[1]
else:
result = nums[0] - nums[1]
#判断用户答案是否正确
promat = '%s %s %s = ' % (nums[0],op,nums[1])
n = 0
while n < 3:
try :
answer = int(input(promat))
except (KeyboardInterrupt,EOFError):
print('\nBye-Bye')
exit()
except ValueError:
print('无效的值请重试')
return
if answer == result:
print('回答正确,真棒!')
break
print('不对哟,在想想~')
n += 1
else: #while循环不执行的时候执行print语句
print('%s %s %s的答案是 %s' %(nums[0],op,nums[1],result))
def main():
while 1:
exam()
try:
yn = input('Continue(y/n)?').strip()[0] #去两端空白字符后取出第一个字符
except IndexError:
continue #退出本次循环进入下次循环
except (KeyboardInterrupt,EOFError):
yn = 'n'
if yn in 'nN': #如果输入N/n就退出循环,其他字符则继续循环
print('\nBye-bye')
break
if __name__ == '__main__':
main()
测试运行
[root@python day7]# python3 lianxi.py
86 + 37 = 123
回答正确,真棒!
Continue(y/n)?y
73 - 5 = 66
不对哟,在想想~
73 - 5 = 55
不对哟,在想想~
73 - 5 = 44
不对哟,在想想~
73 - 5的答案是 68
Continue(y/n)?n
Bye-bye
匿名函数
- 当函数体代码只有一行时,可以使用匿名函数
- 语法:
lambda 参数...: 表达式
def func1(x,y):
return x + y
if __name__ == '__main__':
result = func1(20,30)
print(result)
改写为匿名函数:
result2 = lambda x,y : x + y
print(result2(30,40))
案例1代码优化1.0
将原先计算正确结果修改为函数类型,定义一个字典,通过匹配加号减号来实现调用函数
from random import randint,choice
def jiafa(x,y):
return x + y #将结果返回
def jianfa(x,y):
return x -y
def exam():
func1 = {'+':jiafa,'-':jianfa}
nums = [randint(1,100) for i in range(2)]
nums.sort()
nums.reverse()
op = choice('+-')
#通过字典调用函数计算答案
result = func1[op](nums[0],nums[1])
案例1代码优化2.0
from random import randint,choice
def exam():
func1 = {'+':lambda x,y : x+y,'-':lambda x,y: x - y } #匿名函数
nums = [randint(1,100) for i in range(2)] #从1-100之间随机取出2个数
nums.sort() #升序
nums.reverse() #反转
#随机生成加减法
op = choice('+-')
#使用声明函数
result = func1[op](*nums) *nums自动将列表拆分
filter函数
- 它接收一个函数和一个序列对象作为参数
- 函数接收一个参数,必须返回真或假
- 序列对象中的每个值作为函数的参数进行调用,返回真的值保留,假的过滤掉
from random import randint
def func1(x):
# x % 2只有1或0两种情况,1为真,0为假
return x % 2
if __name__ == '__main__':
nums = [randint(1, 100) for i in range(10)]
print(nums)
result = filter(func1, nums)
print(list(result))
result2 = filter(lambda x: x % 2, nums)
print(list(result2))
map函数
- 它接收一个函数和一个序列对象作为参数
- 函数接收一个参数,对其进行加工,返回加工的结果
- map函数将序列对象中的每个值分别交给函数加工,保留所有的加工结果
from random import randint
def func1(x):
return x * 2
if __name__ == '__main__':
nums = [randint(1, 100) for i in range(10)]
print(nums)
result = map(func1, nums)
print(list(result))
result2 = map(lambda x: x * 2, nums)
print(list(result2))
变量
- 定义在函数外面的变量,叫全局变量。全局变量在它定义开始一直到程序结束,一直可见可用。
>>> x = 10
>>> def func1():
... print(x)
...
>>> func1()
10
- 定义在函数内部的变量,以及函数的参数,都是局部变量。局部变量只能在函数内部使用
>>> def func2():
... a = 10
... print(a)
...
>>> func2()
10
>>> print(a)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'a' is not defined
- 如果局部和全局有相同的变量名,那么局部变量将会遮盖住全局变量。
>>> x = 10
>>> def func3():
... x = 'hello world'
... print(x)
...
>>> func3()
hello world
>>> print(x)
10
- 如果真的需要在局部改变全局变量,需要在函数内部先通过global进行声明
>>> x = 10
>>> def func4():
... global x
... x = 'hello world'
... print(x)
...
>>> func4()
hello world
>>> print(x)
hello world
偏函数
- 通过functools.partial()函数修改现有函数,将函数的某些参数固定下来,生成新函数
>>> def add(a, b, c, d, e):
... return a + b + c + d + e
...
>>> add(10, 20, 30, 40, 5)
105
>>> add(10, 20, 30, 40, 1)
101
>>> add(10, 20, 30, 40, 3)
103
>>> from functools import partial
>>> myadd = partial(add, 10, 20, 30, 40)
>>> myadd(5)
105
>>> myadd(1)
101
>>> myadd(3)
103
- 使用偏函数改造int函数,使之可以将2进制、8进制、16进制字符串数字转换为10进制
>>> int('1010')
1010
>>> int('1010', base=2)
10
>>> int('1010', base=8)
520
>>> int('1010', base=16)
4112
>>> int('10101001', base=2)
169
>>> int2 = partial(int, base=2)
>>> int2('10101001')
169
递归函数(了解)
- 函数的代码块又包含了对自己的调用
- 递归函数往往可以使用循环替代
5!=5x4x3x2x1
4!=4x3x2x1
5!=5x4!
5!=5x4x3!
4!=4x3x2!
5!=5x4x3x2x1!
1!=1
# 一个数如果是1,那么它的阶乘就是1;如果这个数不是1,那么它的阶乘等于它乘以它下一个数的阶乘
def func1(x):
if x == 1:
return 1
return x * func1(x - 1)
生成器
- 生成器表达式:与列表解析语法一样
>>> nums = (randint(1, 100) for i in range(10))
>>> nums
<generator object <genexpr> at 0x7fdac0f6c200>
>>> for i in nums:
... print(i)
- 生成器函数:本质上是函数。只不过生成器函数可以通过yield关键字返回很多中间结果。
>>> def mygen():
... yield 100
... a = 10 + 10
... yield a
... yield 200
...
>>> mg = mygen()
>>> mg
<generator object mygen at 0x7fdac0f6c258>
>>> for i in mg:
... print(i)
...
100
20
200
模块
- 模块导入时,python到sys.path指定的路径下搜索模块
- 可以设置PYTHONPATH环境变量指定自定义的模块搜索路径
[root@localhost day02]# mkdir /opt/mylibs
[root@localhost day02]# cp qsort.py /opt/mylibs
[root@localhost day02]# export PYTHONPATH=/opt/mylibs
[root@localhost day02]# cd /tmp/
[root@localhost tmp]# python3
>>> import qsort
>>> import sys
>>> sys.path
['', '/opt/mylibs', '/usr/local/lib/python36.zip', '/usr/local/lib/python3.6', '/usr/local/lib/python3.6/lib-dynload', '/usr/local/lib/python3.6/site-packages']
- 在python中,目录被称作包,可以当成是一个特殊的模块
[root@localhost day02]# ls mypkgs/
hi.py
[root@localhost day02]# cat mypkgs/hello.py
hi = "Hello World!"
>>> import hello # Error,当前目录没有hello.py
>>> mypkgs.hello.hi
'Hello World!'
hashlib模块
- 用于计算数据的hash值
>>> import hashlib
>>> m = hashlib.md5(b'123456')
>>> m.hexdigest()
'e10adc3949ba59abbe56e057f20f883e'
[root@localhost day02]# echo -n 123456 > /tmp/d.txt
[root@localhost day02]# md5sum /tmp/d.txt
e10adc3949ba59abbe56e057f20f883e /tmp/d.txt
# 如果数据量较大,可以分批计算
>>> m1 = hashlib.md5()
>>> m1.update(b'12')
>>> m1.update(b'34')
>>> m1.update(b'56')
>>> m1.hexdigest()
'e10adc3949ba59abbe56e057f20f883e'
案例2 校验文件的md5值
import sys
import hashlib
def check_md5(fname):
m = hashlib.md5() # 如果数据量较大,可以分批计算
with open(fname,'rb') as fobj:
while 1:
data = fobj.read(4096)
if not data:
break
m.update(data)
return m.hexdigest() #将数据返回
if __name__ == '__main__':
print(check_md5(sys.argv[1])) #生成值之后需要print出来
测试:
[root@python day7]# python3 myerror.py /etc/hosts
54fb6627dbaa37721048e4549db3224d
[root@python day7]# md5sum /etc/hosts
54fb6627dbaa37721048e4549db3224d /etc/hosts
可以看到利用python校验出的md5值与系统命令校验出的值是相同的。
tarfile模块
- 用于压缩、解压缩
# 压缩
>>> import tarfile
>>> tar = tarfile.open('/tmp/a.tar.gz', 'w:gz')
>>> tar.add('/etc/hosts')
>>> tar.add('/etc/security')
>>> tar.close()
[root@localhost day02]# ls /tmp/a.tar.gz
/tmp/a.tar.gz
[root@localhost day02]# tar tvzf /tmp/a.tar.gz
# 解压缩
>>> tar = tarfile.open('/tmp/a.tar.gz')
>>> tar.extractall(path='/var/tmp')
>>> tar.close()
[root@localhost day02]# ls /var/tmp/etc/
hosts security
案例3 备份文件
每周一做完全备份其他时间做差异备份
from time import strftime
import hashlib
import os
import tarfile
import pickle
def check_md5(fname):
m = hashlib.md5()
with open(fname,'rb') as fobj:
while 1:
data = fobj.read(4096)
if not data:
break
m.update(data)
return m.hexdigest()
def full_backup(src,dst,md5file):
#完全备份,将src目录打包放到dst中,并将md5值记录到md5file
#拼接出备份文件的绝对路径
fname = os.path.basename(src) #取出原目录下的文件名
fname = '%s_full_%s.tar.gz' % (fname,strftime('%Y%m%d')) #将文件赋上日期
fname = os.path.join(dst,fname) #将文件拼接为绝对路径
#开始打包备份
tar = tarfile.open(fname,'w:gz')
tar.add(src)
tar.close()
#完全备份后,生成所有备份文件的md5值
md5dict = {}
for lujing,mulu,wenjian in os.walk(src):
for file in wenjian:
k = os.path.join(lujing,file) #生成绝对路径
md5dict[k] = check_md5(k) #md5dict['/tmp/demo/test': '2131456789agsjq']
#k = '/etc/passwd'
#d1 = {}
#d1[k] = '123456'
#print(K) ------> {'/etc/passwd':'123456'}
#将md5存入文件
with open(md5file,'wb') as fobj:
pickle.dump(md5dict,fobj)
def incr_backup(src,dst,md5file):
fname = os.path.basename(src) #取出原目录下的文件名
fname = '%s_incr_%s.tar.gz' % (fname,strftime('%Y%m%d')) #将文件赋上日期
fname = os.path.join(dst,fname) #将文件拼接为绝对路径
# 计算文件md5值,保存到字典中
md5dict = {}
for path, folders, files in os.walk(src):
for file in files:
k = os.path.join(path, file)
md5dict[k] = check_md5(k)
# 取出前一天的md5值
with open(md5file, 'rb') as fobj:
old_md5 = pickle.load(fobj)
# 备份新增文件和改动的文件
tar = tarfile.open(fname, 'w:gz')
for k in md5dict: #循环字典的key
if old_md5.get(k) != md5dict[k]: #通过key取出md5值在于新的md5值对比
tar.add(k)
tar.close()
# 更新md5文件
with open(md5file, 'wb') as fobj:
pickle.dump(md5dict, fobj)
if __name__ == '__main__':
src = '/tmp/demo/security' #要备份的文件
dst = '/tmp/demo/backup' #存放备份文件的目录
md5file = '/tmp/demo/backup/md5.data'
if strftime('%a') == 'Mon':
full_backup(src,dst,md5file)
else:
incr_backup(src,dst,md5file)