函数回顾
1.函数可以当做一个参数赋值给另一个函数:
def func(): print("in the func") def foo(x): x()foo(func)
输出:
in the func
2.函数可以设置为变量形式,函数可以被当做数据来传递:
def func(): print("in the func")f1 = funcf1()
输出:
in the func
3.返回值可以是函数
def func(): print("in the func")def foo(): return funcres = foo() #res = funcres() #res() = func()
输出:
in the func
4.可以做容器类型的元素(列表,元组,字典)
def func(): print("in the func")def abc(): print("in the abc")func_dic={ "func":func, "abc":abc,}func_dic["func"]() # func() 通过字典取值的形式执行函数func_dic["abc"]() # abc()
输出:
in the funcin the abc
嵌套调用
在一个函数的内部调用另外一个函数;
例:比较四个值的大小:def my_max4(a, b, c, d): res1 = my_max2(a, b) res2 = my_max2(res1, c) res3 = my_max2(res2, d) return res3def my_max2(x, y): if x > y: return x else: return y print(my_max4(100, 5000, -1, 10))
输出:
5000
嵌套定义
在函数的内部,又定义一个函数:
x = 1def f1(): def f2(): print(x) return f2f2 = f1()f2()
名称空间于作用域
1.名称空间种类
- 内置名称空间:在python解释器启动时产生,存放python内置的名字
- 全局名称空间:在执行文件时产生,存放文件级别定义的名字(全局变量),程序结束时释放
- 局部名称空间:在执行文件的过程中,如果调用了函数,则会产生该函数的名称空间,用来存放该函数内部定义的名字,该函数在函数调用时生效,韩式调用结束后失效。
2.名称空间,加载顺序
内置-->全局-->局部
3.名称空间,取值加载顺序
局部-->全局-->内置
4.作用域即范围
- 全局范围:全局存活,全局有效
- 局部范围:临时存活,局部有效
- 作用域关系是在函数定义阶段以及固定的,与函数的调用位置无关
1.查看局部作用域的名字 locals()
def func(): print("in the func") a = 1 b = 2 c = 5 def foo(): pass print(locals())def foo(x): x()foo(func)
输出:
{'a': 1, 'c': 5, 'b': 2, 'foo':.foo at 0x00000254BFD698C8>}
2.查看全局作用域的名字 globals()
def func(): print("in the func") a = 1 b = 2 c = 5 def foo(): pass # print(locals())def foo(x): x()foo(func)print(globals())
输出:
{'__package__': None, '__spec__': None, '__doc__': None, 'func':, '__builtins__': , 'foo': , '__name__': '__main__', '__cached__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000254BFD07AC8>, '__file__': 'C:/Users/PycharmProjects/Py/da/笔记/作用域.py'}
5.局部内修改全局变量值global (尽量不要在局部中修改全局变量值)
x = 1def f1(): global x x =10f1()print(x)
输出:
10
6.作用域关系,在函数定义时就已经固定,于调用位置无关
1.在第一次打印x值时取值为定义时固定的 x = 1,第二次时全局的x已经被修改为500所以值为500;
x=1def f1(): def f2(): print(x) return f2func = f1()def foo(func): x = 1000 func()foo(f1())x = 500foo(f1())
输出
1500
闭包函数
在内部函数当中,有对外部函数名字的引用;(对外部作用域,不是全局作用域)
1.将x = 1 隐藏在了函数f2中,只有在调用执行时才会被引用;且不受全局变量的影响x = 1000 # 全局作用域def f1(): x = 1 # 外部作用域 (关键的闭包值) def f2(): print(x) return f2f = f1()f()
输出:
1
2.将baidu的网页爬下来,先存储起来使用的时候,直接调用baidu()即可:
import requests##导入模块页面爬取requestdef page(url): # url = http://www.baidu.com def get(): print(requests.get(url).text) return getbaidu = page("http://www.baidu.com")baidu()print(baidu.__closure__[0].cell_contents) # 查看闭包函数包过的值(http://www.baidu.com)
装饰器实现原则
1.一定不能修改源代码
2.不能修改调用方式 3.装饰器本身也是一个函数 4.遵循开放封闭原则装饰器的语法是:
在你需要装饰的函数正上方一行使用@符号写上装饰器的名字; 被装饰者"a",装饰器名字为 timer函数1.在index函数和home函数上面添加一个计算程序运行时间的计算,但是不改变index的调用方式
import timedef times(func): # 运行times(func)函数 def f1(): start = time.time() func() stop = time.time() print(stop-start) return f1 # 将times得到的值 f1() 返回给 index@timesdef home(): time.sleep(2) print("from in home!")@times # index = times(index) 运行times()函数,并把 index()函数值传入进去def index(): time.sleep(3) print("from in index")home()index()
输出:
from in home!2.000453233718872from in index3.0009102821350098
2.有参装饰器当index函数有调用方式的时候,@timer函数需要使用不定长参数来表示(*args, **kwargs);
def timer(index): def abc(*args, **kwargs): print("in the abc") index(*args, **kwargs) return abc@timer # index = timer(index)def index(user, age): print("in the index %s %s" % (user, age))index("tom", age="19")
3.返回值装饰器当home函数有返回值时,@times函数内部同时需要返回home的返回值
import timedef times(func): def f1(*args,**kwargs): # 因为会将值传到 f1函数来接收所以使用 *args,**kwargs 接收 start = time.time() res = func(*args,**kwargs) # 并将接收到的参数,全部转发给 func() 函数 stop = time.time() print(stop-start) return res # 将的到的返回值返回给 home函数 return f1@timesdef home(name): # home()函数有返回值 time.sleep(2) print("from %s in home!" % name)@timesdef index(): time.sleep(3) print("from in index")home("lin") # 在这里在执行home("lin") 函数是,就是在执行 f1(*args,**kwargs)index()
输出:
from lin in home!2.0006303787231445from in index3.0005226135253906
4.让用户输入用户密码,如果用户密码正确,方可执行调用函数:
user_l = {"user":None} # 定义一个空字典,用来存放已登录的用户,如果有值,无需在次登录def auth(func): def f1(*args, **kwargs): if user_l["user"]: # 判断user_l字典中是否有值 res = func(*args,**kwargs) return res name = input("输入你的名字:") pwd = input("输入你的密码:") with open("db.txt","r",encoding="utf-8") as f: # 从文件db.txt 读取用户信息 date_dic = eval(f.read()) # 将用户信息转为字典格式 if name in date_dic and pwd == date_dic[name]: # 判断用户输入内容和字典取值是否相同 res = func(*args,**kwargs) user_l["user"]=name return res else: print("用户或密码ERROR") return f1@authdef home(name): print("from %s in home" % name)@authdef index(): print("from in index!")index()home("lin")
db.txt内容
{"zhangsan":"123","wangwu":"456","zhaoliu":"789"}
输出:
输入你的名字:zhangsan输入你的密码:123from in index!from lin in home
有参装饰器
1.当用户输入指定登录时,使用什么方式验证;不同的验证参数使用不同的验证方法;
判断用户如果选择“file”方式登录,需要让用户输入用户密码,如果选择“ldap”方式登录直接登录!user_l = {"user":None}def canshu(auth_type="file"): # 在最外层包过一个用户传来的验证方式,默认为“file” def auth(func): # func = index def f1(*args,**kwargs): if auth_type == "file": # 获取用户传入的auth_type 参数 判断是否为 file if user_l["user"]: res = func(*args,**kwargs) return res name = input("输入你的名字:") pwd = input("输入你的秘密:") with open("db.txt","r",encoding="utf-8") as f: data_dic = eval(f.read()) if name in data_dic and pwd == data_dic[name]: res = func(*args,**kwargs) user_l["user"] = name return res else: print("用户名或密码ERROR!") elif auth_type == "mysql": print("auth mysql!") elif auth_type == "ldap": print("auth ldap") else: print("auth ERROR") return f1 return auth@canshu(auth_type="file") # canshu(file) --->@auth -- > f1()def home(name): print("from %s in home" % name)home("lin") # 最终执行 f1()
db.txt内容
{"zhangsan":"123","wangwu":"456","zhaoliu":"789"}
输出:
输入你的名字:zhangsan输入你的秘密:123from lin in homepython
使用装饰器时,显示源函数调用方式的帮助信息
import timeimport functools # 用于使用装饰器时,展示源函数的 帮助信息def times(func): @functools.wraps(func) # 在最装饰器最内层的函数 上面加上 @functools.wraps(func) 并把 func 传进来 def f1(*args,**kwargs): start = time.time() func(*args,**kwargs) stop = time.time() print(stop-start) return f1@timesdef home(name): ''' 这是home的帮助信息! :return: ''' time.sleep(3) print("from %s in home" % name)print(home.__doc__) # 打印函数的注释信息home("lin")
输出:
这是home的帮助信息! :return: from lin in home3.0001637935638428
装饰器上面在套用装饰器,达到多次装饰的目的
1.在计算程序运行时间的装饰器上,在加一个验证装饰器
import timeimport functools # 用于使用装饰器时,展示源函数的 帮助信息user_l = {"user":None}def times(func): @functools.wraps(func) # 在每个最装饰器最内层的函数 上面加上 @functools.wraps(func) 并把 func 传进来 def f1(*args,**kwargs): start = time.time() func(*args,**kwargs) stop = time.time() print(stop-start) return f1def canshu(auth_type="file"): def auth(func): @functools.wraps(func) # 在每个最装饰器最内层的函数 上面加上 @functools.wraps(func) 并把 func 传进来 def f1(*args, **kwargs): if auth_type == "file": if user_l["user"]: res = func(*args,**kwargs) return res name = input("输入你的名字:") pwd = input("输入你的密码:") with open("db.txt","r",encoding="utf-8") as f: date_dic = eval(f.read()) if name in date_dic and pwd == date_dic[name]: res = func(*args,**kwargs) user_l["user"]=name return res else: print("用户或密码ERROR") elif auth_type == "mysql": print("mysql auth") elif auth_type == "ldap": print("ldap auth") else: print("auth ERROR") return f1 return auth@canshu() # 哪个装饰器写在上面先运行哪个装饰器 先运行验证装饰器@times # 后运行计时装饰器def home(name): ''' 这是home的帮助信息! :return: ''' time.sleep(3) print("from %s in home" % name)print(home.__doc__) # 打印函数的注释信息home("lin")
输出:
这是home的帮助信息! :return: 输入你的名字:zhangsan输入你的密码:123from lin in home3.000636339187622
装饰器练习(巩固加深)
1.编写日志装饰器,实现功能如:一旦函数index执行,则将消息2017-07-24 22:11:17 index run写入到日志文件中,日志文件路径可以指定(装饰器2017-07-24.log)
import os, timedef logger(logfile): def deco(func): if not os.path.exists(logfile): # 判断 日志文件 “装饰器2017-07-24.log” 是否存在 with open(logfile, "w"): pass # 不存在创建 文件 def f1(*args, **kwargs): res = func(*args, **kwargs) with open(logfile, "a", encoding="utf-8") as f: f.write("%s %s run\n" % (time.strftime("%Y-%m-%d %X"), func.__name__)) # 将当前时间 文件名记录到文件中 return res return f1 return decotime_date = time.strftime('%Y-%m-%d')@logger(logfile="装饰器" + time_date + ".log") # 传入日志文件名 日期格式 “装饰器2017-07-24.log”def index(): print("index") index()
2.将爬取的站点,存入依照URL的md5值命名的文件中用于缓存,下次用户在访问时,可以从文件中直接读取数据返回。
import requests, os, hashlibsettings = { # 定义字典格式的 缓存 存放格式 选择 "file": {"dirname": "./db", # db 目录需要提前创建 }, "mysql": { "host": "127.0.0.1", "port": 3306, "user": "root", "password": "123", }, "redis": { "host": "127.0.0.1", "port": 6379, "user": "root", "password": "123", }}def make_cache(cache_file="file"): # 接收用户选择 存储缓存的方式 if cache_file not in settings: # 如果选择缓存的方式不再 settings 字典中 raise TypeError("cache_file not valid!") # raise 自定义一个异常抛给用户 def deco(func): def f1(url): if cache_file == "file": m = hashlib.md5(url.encode("utf-8")) # 获取一个 URL 转换的 hash对象 "" cache_filename = m.hexdigest() # 取出这个md5的hash值 "e1d84f4301e444a3db82c908f29947b1" cache_filepath = r"%s/%s" % (settings["file"]["dirname"], cache_filename) # 拼接一个文件路径 ./db/e1d84f4301e444a3db82c908f29947b1 if os.path.exists(cache_filepath) and os.path.getsize(cache_filepath): # 如果md5后的url文件存在并且里面有值 return open(cache_filepath, encoding="utf-8").read() # 直接将结果返回给用户 res = func(url) with open(cache_filepath, "w", encoding="utf-8") as f: # 否则依照 url 的md5值为文件名创建文件 f.write(res) # 将爬取的 页面内容存入 url 的md5值为文件名中 return res # 并返回 爬取的数据 elif settings == "mysql": pass elif settings == "redis": pass else: pass return f1 return deco@make_cache(cache_file="file")def get(url): return requests.get(url).textprint(get("https://www.python.org"))print(get("https://www.jd.com"))