函数式编程(三元运算、文件操作、函数、装饰器)
一、三元运算
简介:
三元运算又称三目运算,是对简单的条件语句简写,如:
#简单条件语句
if 1 < 2:
var =1
else:
var = 2
print(var)
#改成三元运算
var_1 = 1 if 1 < 2 else 2
print(var_1)
#输出
1
1
二、文件处理
文件操作分为读、写、修改。
读:
#注意这个路径如果不写绝对路径就是与python程序处于同一路径,建议使用绝对路径
#这里一定要注意编码的问题,以什么编码写的要以什么编码打开,否则就是乱码了
#mode ='r' r参数代表只读
f = open("E:\python学习\函数式编程\学生名称联系方式.bak.txt",mode='r',encoding='gbk')
#f.read()代表读取所有内容,内容是已经转换完毕的字符串
data = f.read()
print(data)
#f.close()关闭文件
f.close()
#输出
小明 13832408885
小九 13903249995
大琳 13703241110
张三 17888026957
李四 17600789963
rb模式
#为什么这里在打开文件的时候没有指定 encoding,是因为直接以rb模式打开了该文件。
# rb模式是指二进制模式,数据读到内存中都是bytes格式,如果想查看内容需要手动decode,
#因此在打开文件阶段不需要指定编码
f = open("E:\python学习\函数式编程\学生名称联系方式.bak.txt",mode='rb')
data = f.read()
print(data)
#print(data.decode('gbk'))#手动decode
f.close()
#输出(因二进制太多只列举一部分)
b'\xd0\xa1\xc3\xf7 13832408885\r\n\xd0\xa1\xbe\xc5 13903249995\r\n\xb4\xf3\xc1\xd5 13703241110
但是如果不知道该程序的编码怎么办?需要借用第三方的工具箱 chardet,这个模块作用就是用于检测文本的编码,因为这个工具python3 本身没有则需要安装.。以下为Windows的安装方式:
C:\Users\小九>pip install chardet
Collecting chardet
Downloading chardet-3.0.4-py2.py3-none-any.whl (133kB)
100% |████████████████████████████████| 143kB 130kB/s
Installing collected packages: chardet
Successfully installed chardet-3.0.4
You are using pip version 9.0.1, however version 9.0.2 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
.
chardet 自动检测文本编码模块
import chardet
#导入工具库
#这里的读取模式也可以不用加前面的mode,因为默认第二个参数就是读取模式
f = open('log.txt','rb')
data = f.read()
f.close()
#使用detect模块以rb的模式打开文件,并检测编码
result = chardet.detect(open('log.txt','rb').read())
print(result)
#输出
#分别为 encoding 编码格式 ,confidence 对比读越高越可信,language 语言
{'encoding': 'utf-8', 'confidence': 0.7525, 'language': ''}
循环文件
f = open('学生名称联系方式.txt','r',encoding='utf-8')
for i in f:
print(i)
f.close()
#输出
小明 13832408885
小九 13903249995
大琳 13703241110
张三 17888026957
#为什么每行之间都有空行,因为print默认输出就是每行之结尾有一个\n的参数
写文件
#模式为w 表示只写(w 就是创建新的文件,如果有文件同名会覆盖掉),
#f.write()要写的内容,写入的内容是unicode字符,内部会根据encoding进行转换为指定的编码。
f = open('中学生联系方式','w',encoding='utf-8')
f.write("李四 17899662323")
f.close()
#无输出,会创建名为 '中学生联系方式' 的文件内容为:
李四 17899662323
二进制模式写是wb
#注意无论是 rb 还是 wb ,二进制一般用于数据传输。
追加
#文件操作时,以'a'或'ab'模式打开,则只能追加,即在原来内容尾部追加内容
#ab 写入时需要直接传入以某种编码的 0101011即:字节类型
#a 和encoding ,写入时需要传入unicode字符串,内部会根据encoding制定的编码进行转换。
f = open('中学生联系方式','a',encoding='utf-8')
f.write("张三 17508322673")
f.close()
#无输出,文件内容为:
李四 17899662323张三 17508322673
#如果不加别的参数默认就追加到目标文件最后面。
读写混合模式(r+,w+)
#之前说过了,文件能以只读,只写的模式打开,如果想读又想写,就要以 读写的模式打开
#r+读写模式,先读后写
f = open('中学生联系方式','r+',encoding='utf-8')
data = f.read()
print(data)
f.write("我是后面写入的")
f.close()
print(data)
#输出
李四 17899662323张三 17508322673
李四 17899662323张三 17508322673
#文件内容
李四 17899662323张三 17508322673我是后面写入的
#这里肯定会疑惑,为什么第二次输出的时候没有输出写入字符呢,那是因为文件只能读一次,光标一次就读到了的文件的末尾,所以第一次读可以读到文件内容,第二次读光标还在文件末尾所以无法读到新写入的值。
大家也可以对读写模式理解为可追加的读模式
写读模式(慎用)
f = open('中学生联系方式','w+',encoding='utf-8')
data = f.read()
print(data)
f.write('\n nwe 1 哇哇')
f.write('\n nwe 2 哇哇')
f.write('\n nwe 3 哇哇')
print('content',f.read())
f.close()
#输出
content
#为什么只输出 content ,我文件里面的内容,文件内容如下:
#文件内容
nwe 1 哇哇
nwe 2 哇哇
nwe 3 哇哇
#可以看到文件里之前的文件被覆盖,这就是之前说过的w是写,也就是先创建一个新文件把之前的文件覆盖掉了,再写入新的数据,所以之前的数据没了,这个一般不会用到!
f = open("中学生联系方式",'r+',encoding='utf-8')
data = f.read()
#fileno() 方法返回一个整型的文件描述符(file descriptor FD 整型),可用于底层操作系统的 I/O 操作。
#一般用不到
print(f.fileno())
#输出
3
f = open("中学生联系方式",'r+',encoding='utf-8')
f.write("\n 哈哈哈")
#flush() 方法是用来刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区,不需要是被动的等待输出缓冲区写入。
#一般情况下,文件关闭后会自动刷新缓冲区,但有时你需要在关闭前刷新它,这时就可以使用 flush() 方法。
f.flush()
f.close()
f = open("中学生联系方式",'r+',encoding='utf-8')
readline() 方法用于从文件读取整行,包括 "\n" 字符。如果指定了一个非负数的参数,则返回指定大小的字节数,包括 "\n" 字符。
print(f.readline())
#输出
nwe 1 哇哇
f = open("中学生联系方式",'r+',encoding='utf-8')
print(f.readline())
#tell() 方法返回文件的当前位置,即文件指针当前位置。
print(f.tell())
#输出
nwe 1 哇哇
14
f = open("中学生联系方式",'r+',encoding='utf-8')
print(f.tell())
#seek() 方法用于移动文件读取指针到指定位置。
#注意seek的长度是按字节计算的,字符编码每个字符所占的长度不一样。
print(f.seek(6))
#这里将光标移到第几个字节的位置,然后打印
print(f.readline())
#输出
0
6
哇哇
#f.seekable() 判断文件是否可以被seek,,因为Linux里一切皆文件有些文件是不可以被seek的
f = open("中学生联系方式",'r+',encoding='utf-8')
#truncate() 方法用于从文件的首行首字符开始截断,截断文件为 size 个字符,无 size 表示从当前位置截断;截断之后 V 后面的所有字符被删除,其中 Widnows 系统下的换行代表2个字符大小。 。
f.truncate(6)
#因为这里只截取了6个字节,后面的全部被擦除
print(f.readline())
#输出
nwe 1
#f.wirtable 判断文件是否可写
修改文件
#占用硬盘修改文件版本
f_name = '学生名称联系方式.txt'
f_new_name = '%s.new' %f_name
ord_str = '张三'
new_str = '大头'
f = open(f_name,'r',encoding='utf-8')
f_new = open(f_new_name,'w',encoding='utf-8')
#逐行读进内存进行判断
for line in f:
if ord_str in line:
#如果旧字符串存在就替换为新字符串
line = line.replace(ord_str,new_str)
f_new.write(line)
f.close()
f_new.close()
#会产生一个新文件 学生名称联系方式.txt.new
如何直接将原文件覆盖掉呢?
#将新文件名替换成旧文件名,实现覆盖更新,这样就实现了对源文件进行更新
os.replace(f_new_name,f_name)
#占内存文件修改方法
import os
f_name = '学生名称联系方式.txt'
f_new_name = '%s.new'%f_name
old_str = '大头'
new_str = '张三'
f = open(f_name,'r',encoding='utf-8')
f_new = open(f_new_name,'w',encoding='utf-8')
#与占硬盘的方式不同的就是这里讲文件内容一次性读到内存里
date = f.read()
if old_str in date:
date = date.replace(old_str,new_str)
f_new.write(date)
f.close()
f_new.close()
os.replace(f_new_name,f_name)
练习题
练习题一 全局替换程序:
- 写一个脚本,允许用户按一下方式执行时,即可对指定文件进行内容的全局替换
- python your_script.py old_str new_str filename
- 替换完毕之后打印替换了多少处内容
import sys
import os
#导入 sys os 工具箱
#sys.argv 传参
old_str = sys.argv[1]
new_str = sys.argv[2]
f_name = sys.argv[3]
count = 0
f_name_new = '%s.new'%f_name
f = open(f_name,'r',encoding='utf-8')
f_new = open(f_name_new,'w',encoding='utf-8')
for line in f:
if old_str in line:
count+=1
line = line.replace(old_str,new_str)
f_new.write(line)
f.close()
f_new.close()
os.replace(f_name_new,f_name)
print('一共替换了',count)
输出:
练习题二 模拟登陆:
- 用户输入账号密码进行登陆
- 用户信息保存在文件内
- 用户密码输入错误三次后锁定用户,下次再登陆,检测到不允许登陆
import os
f_name = 'user_info'
f_name_new = '%s.key' %f_name
f = open(f_name,'r',encoding='utf-8')
f_new = open(f_name_new,'w',encoding='utf-8')
data =f.read()
data = eval(data)
while True:
f_new.flush()
user_input = input("用户:")
password_input =input("密码:")
if data[user_input][2] == 3:
print('用户被锁定')
break
elif data[user_input][0] == user_input and data[user_input][1] == password_input :
print('登陆成功')
else:
print('失败')
count = data[user_input][2]
count = int(count)
count+=1
data[user_input][2] = count
data = str(data)
f_new.truncate(0)
f_new.write(data)
data = eval(data)
continue
f.close()
f_new.close()
BUG版本有待更新
新版本
count = 0#循环初始值
while count <3 :#循环条件
user_name =input('用户名')#获取用户输入
f_lock =open('lock_file','r+',encoding='utf-8')#已读写模式打开已锁定用户文件
lock_list = f_lock.readlines()#使用readlines 一次读取全部文件,将每一行字符串保存在一个列表中。
for lock_line in lock_list:#使用循环取每一行的值
if user_name in lock_line:#判断输入的用户是否在锁文件中
print('锁定')
exit()
with open('user_info','r',encoding='utf-8') as f:#以只读的模式打开用户文件
user_list = f.readlines()
for user_line in user_list :
(user,passwd) = user_line.strip('\n').split()#使用strip去掉换行符,使用split以空格进行切片,对user,passwd赋值
if user_name == user:
n = 0#用户密码输错的初始值
while n <3:
password = input('输入密码')
if password == passwd:
print('登录成功')
exit(0)
else:
if n != 2 :#判断用户输入错误的次数给出提醒
print('%s 密码错误,请重新输入,你还有 %d 次机会'%(user_name,2 - n))
n += 1#累加
else:
f_lock.write(user_name + '\n')#如果输入错误三次跳出循环并将错误人员名单加入 lock文件
exit('用户已锁定')
else:
if count != 2:
print("输入错误请重新输入")
count +=1
else:
exit("用户不存在")
f_lock.close()#关闭lock文件
用户信息文件格式
小九 123
张三 456
三、函数
定义:
函数是指将一组语句的集合通过一个名字(函数)封装起来,想要执行这个函数,只需要调用其函数名即可
特性:
- 减少重复代码
- 使程序变的可扩展
- 使程序变的易维护
def sayhi(): #函数名
print('hello word') #这里可以是任意程序
sayhi()#调用函数
#输出
hello word
函数参数:
分为形参和实参
- 形参变量:只有在被调用的时候才分配内存单元,调用结束时,即可释放所分配的内存单元。因此形参只在函数内部有效。函数调用结束后返回主调用函数后则不能再使用形参变量
- 实参:可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,他们必须有确定的值,以便把这些值传给形参。因此应预先赋值,输入等办法使参数获得确定值
def sayhi(a,b): #形参
print(a*b) #程序里面的值是通过实参传给形参的
sayhi(2,5)#实参
#输出
10
默认参数:
通过默认参数,在传参的时候可以不给默认参数传参。如果传参会覆盖默认值
#不使用默认参数
def sayhi(name,country,age,):
print('My name is',name,'Come from',country,age,'year')
sayhi('xiaojiu','CN','18')#需要一一进行赋值
#输出
My name is xiaojiu Come from CN 18 year
#默认参数
def sayhi(name,age,country ='CN'):
print('My name is',name,'Come from',country,age,'year')
sayhi('xiaojiu','18')
#输出
My name is xiaojiu Come from CN 18 year
#对默认参数赋值
def sayhi(name,age,country ='CN'):
print('My name is',name,'Come from',country,age,'year')
sayhi('xiaojiu','18','US')
#输出
My name is xiaojiu Come from US 18 year
关键参数:
关键参数就是指定了参数名的参数就叫关键参数,关键参数必须在位置参数后面
def sayhi(name,age,country ='CN'):
print('My name is',name,'Come from',country,age,'year')
sayhi('xiaojiu',age='18',country='JB')
#输出
My name is xiaojiu Come from JB 18 year
非固定参数:
如果你的函数在定义的时候不确定用户传入多少参数就可以是用固定参数
#方法一 参数传给args,通过args打包为元组
#*args 所有参数打包传给args
def sayhi(name,age,*args):
print('My name is',name,age,'year',args)
sayhi('xiaojiu','18','a','b','c')
#输出
My name is xiaojiu 18 year ('a', 'b', 'c')
#方法二 手动上传元祖或者列表
#*args 所有参数打包成元祖传给args
def sayhi(name,age,*args):
print('My name is',name,age,'year',args)
sayhi('xiaojiu','18',*['a','b','c'])#切记这里参数前面一定要加*否则就会当成一个元素被转换为元祖
#输出
My name is xiaojiu 18 year ('a', 'b', 'c')
#错误示范 参数前面没有加* 就会把所有参数当成一个元素转换为元祖
#*args 所有参数打包成元祖传给args
def sayhi(name,age,*args):
print('My name is',name,age,'year',args)
sayhi('xiaojiu','18',['a','b','c'])#切记这里参数前面一定要加*否则就会当成一个元素被转换为元祖
#输出
My name is xiaojiu 18 year (['a', 'b', 'c'],)
**kwargs 将传输的值打包成字典
#*args 所有参数打包成元祖传给args
def sayhi(name,age,*args,**kwargs):#将参数转换为字典
print('My name is',name,age,'year',args,kwargs)
sayhi('xiaojiu','18',*['a','b','c'],a='1',b='2')#这里将变量名和值,转换成字典的key value
#输出
My name is xiaojiu 18 year ('a', 'b', 'c') {'a': '1', 'b': '2'}
函数返回值:
函数外部的代码要想获取函数的执行结果,就可以在函数里用return语句把结果返回
#注意
- 函数在执行过程中只要遇到return语句,就会体征执行并返回结果,也可以理解为return语句代表函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
def sayhi(name,age,):
print('My name is',name,age,'year')
if age > 20 :#在这里对参数进行判断,根据判断结果返回相应的值
return False#这里可以返回 list dict 都可以
elif age < 20 :
return True
status = sayhi('xiaojiu',18)
print(status) #根据返回值进行判断
if status :
print("真")
else:
print("假")
#输出
My name is xiaojiu 18 year
True
真
局部变量与全局变量:
- 在函数中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
- 全局变量作用域是整个程序,局部变量作用域是定义该变量的函数。
- 当全局变量与局部变量同名时,在定义局部变量的函数内,局部变量起作用;在其它地方全局变量起作用。
name = 'smallnine'
def change_name (name):#这里如果不传参,也可以正在程序里也可以直接调用全局变量
print('内部变量',name)
name = 'xiaojiu' #局部变量,也就是在函数内部定义的变量
print('修改后的变量',name)
change_name(name)
print(name) #这里面输出的还是之前定义的 smallnine,也就是全局变量
#输出
内部变量 smallnine
修改后的变量 xiaojiu
xiaojiu
如何将局部变量修改为全局变量:
global name
的作用就是要在函数里声明全局变量name ,意味着最上面的name = "snallnine"
即使不写,程序最后面的print也可以打印name
name = 'smallnine'
def change_name ():
global name #在函数里面声明该变量为全局变量
print('内部变量',name)#可以直接调用全局变量,及时函数内部没有定义该变量
name = 'xiaojiu'
print('修改后的变量',name)
change_name()
print(name)#在函数外输出的也是全局变量的值
#输出
内部变量 smallnine
修改后的变量 xiaojiu
xiaojiu
传入可变类型参数:
#如果传入的是可变类型,如 list dict ,在函数里修改也会影响函数外的值
name = [1,2,3]
def change_name (name):
name.append([10,20,30])
print('修改后的变量',name)
change_name(name)
print(name)
#输出
修改后的变量 [1, 2, 3, [10, 20, 30]]
[1, 2, 3, [10, 20, 30]]
匿名函数:
python 使用 lambda 来创建匿名函数。
所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数。
- lambda 只是一个表达式,函数体比 def 简单很多。
- lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
- lambda 函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
- 虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
lambda 函数的语法只包含一个语句,如下:
lambda [arg1 [,arg2,.....argn]]:expression
sum = lambda arg1,agr2:arg1*agr2 #创建匿名函数 print(sum(1,2))#调用sum函数 #输出 2
嵌套函数:
name = 'Small'
#函数里嵌套函数,函数里的函数就相当于局部变量,所以在函数外无法直接调用函数里面的函数
def change_name ():
name = 'Small2'
def change_name2 ():
name ='Smll3'
print('第三层',name)
change_name2()#调下层函数
print('第二层',name)
change_name()#调用函数
print('第一层打印',name)
#输出
第三层 Smll3
第二层 Small2
第一层打印 Small
高阶函数:
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
只需满足以下任意一个条件,即是高阶函数
- 接受一个或多个函数作为输入
- return 返回另外一个函数
def name():
print('hello word!')
def hello(f):#将name函数的值作为参数传给f
f()
hello(name)#接收name函数作为参数
#输出
hello word!
递归:
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
递归特性:
- 必须有一个明确的结束条件
- 每次进入更深一层递归时,问题规模相比上次递归都应有所减少
- 递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
def fact(n):
print(n)
if int(n/2) >0: #必须给递归一个结束的条件不然就会导致栈溢出
return fact(int(n/2))
fact(10)
#输出
10
5
2
1
递归小练习
menus = [
{
'text':'北京',
'children':[
{'text':'朝阳','children':[]},
{'text':'昌平','children':[
{'text':'沙河','children':[]},
{'text':'回龙观','children':[]}
]}
]
},
{
'text': '上海',
'children':[
{'text':'宝山','children':[]},
{'text':'金山','children':[]},
]
}
]
#1打印所有节点
#2输入节点名称遍历查找如果有返回True否则返回False
def func(menus):
for i in menus:
print(i['text'])
func(i['children'])
func(menus)
def func1(dict,name):
for i in dict:
func1(i['children'],name)
if i['text'] == name:
# print(i['text'])
print('true')
else:
print('false')
exit()
text = input(">>:")
func1(menus,text)
# def func1(dict,name):
# for i in dict:
# func1(i['children'],name)
# if i['text'] == name:
# print(i['text'])
# return True
# user = input(">>")
# s = func1(menus,user)
# print(s)
四、装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
函数对象有一个__name__
属性,可以拿到函数的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
现在,假设我们要增强now()
函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()
函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
观察上面的log
,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()
函数,不仅会运行now()
函数本身,还会在运行now()
函数前打印一行日志:
>>> now()
call now():
2015-3-25
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。比如,要自定义log的文本:
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
这个3层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>> now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>> now = log('execute')(now)
我们来剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
以上两种decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有__name__
等属性,但你去看经过decorator装饰之后的函数,它们的__name__
已经从原来的'now'
变成了'wrapper'
:
>>> now.__name__
'wrapper'
因为返回的那个wrapper()
函数名字就是'wrapper'
,所以,需要把原始函数的__name__
等属性复制到wrapper()
函数中,否则,有些依赖函数签名的代码执行就会出错。
不需要编写wrapper.__name__ = func.__name__
这样的代码,Python内置的functools.wraps
就是干这个事的,所以,一个完整的decorator的写法如下:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
或者针对带参数的decorator:
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
import functools
是导入functools
模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()
的前面加上@functools.wraps(func)
即可。