zoukankan      html  css  js  c++  java
  • 007-Python函数-装饰器

    函数回顾

    1.函数可以当做一个参数赋值给另一个函数:

    def func():
        print("in the func")
    
    	
    def foo(x):
        x()
    foo(func)
    
    

    输出:

    in the func
    

    2.函数可以设置为变量形式,函数可以被当做数据来传递:

    def func():
        print("in the func")
    
    f1 = func
    f1()
    

    输出:

    in the func
    

    3.返回值可以是函数

    def func():
        print("in the func")
    
    def foo():
        return func
    
    res = foo()		#res = func
    res()			#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 func
    in 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 res3
    
    
    def my_max2(x, y):
        if x > y:
            return x
        else:
            return y
            
    print(my_max4(100, 5000, -1, 10))
    

    输出:

    5000
    

    嵌套定义

    在函数的内部,又定义一个函数:

    x = 1
    
    def f1():
        def f2():
            print(x)
        return f2
    f2 = f1()
    f2()
    

    名称空间于作用域

    1.名称空间种类

    1. 内置名称空间:在python解释器启动时产生,存放python内置的名字
    2. 全局名称空间:在执行文件时产生,存放文件级别定义的名字(全局变量),程序结束时释放
    3. 局部名称空间:在执行文件的过程中,如果调用了函数,则会产生该函数的名称空间,用来存放该函数内部定义的名字,该函数在函数调用时生效,韩式调用结束后失效。

    2.名称空间,加载顺序

    内置-->全局-->局部

    3.名称空间,取值加载顺序

    局部-->全局-->内置

    4.作用域即范围

    1. 全局范围:全局存活,全局有效
    2. 局部范围:临时存活,局部有效
    3. 作用域关系是在函数定义阶段以及固定的,与函数的调用位置无关

    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': <function func.<locals>.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': <function func at 0x00000254BFD696A8>, '__builtins__': <module 'builtins' (built-in)>, 'foo': <function foo at 0x00000254BFD69840>, '__name__': '__main__', '__cached__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000254BFD07AC8>, '__file__': 'C:/Users/PycharmProjects/Py/da/笔记/作用域.py'}
    

    5.局部内修改全局变量值global (尽量不要在局部中修改全局变量值)

    x = 1
    def f1():
        global x
        x =10
    f1()
    print(x)
    

    输出:

    10
    

    6.作用域关系,在函数定义时就已经固定,于调用位置无关

    1.在第一次打印x值时取值为定义时固定的 x = 1,第二次时全局的x已经被修改为500所以值为500;

    x=1
    def f1():
        def f2():
            print(x)
        return f2
    
    func = f1()
    
    def foo(func):
        x = 1000
        func()
    
    foo(f1())
    x = 500
    foo(f1())
    

    输出

    1
    500 
    

    闭包函数

    在内部函数当中,有对外部函数名字的引用;(对外部作用域,不是全局作用域)
    1.将x = 1 隐藏在了函数f2中,只有在调用执行时才会被引用;且不受全局变量的影响

    x = 1000             # 全局作用域
    def f1():
        x = 1            # 外部作用域 (关键的闭包值)
        def f2():
            print(x)
        return f2
    f = f1()
    f()
    

    输出:

    1
    

    2.将baidu的网页爬下来,先存储起来使用的时候,直接调用baidu()即可:

    import requests
    ##导入模块页面爬取request
    
    def page(url):
        # url = http://www.baidu.com
        def get():
            print(requests.get(url).text)
        return get
    
    baidu = 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 time
    def times(func):                 # 运行times(func)函数
        def f1():
            start = time.time()
            func()
            stop = time.time()
            print(stop-start)
        return f1                    # 将times得到的值 f1() 返回给 index
    
    @times
    def 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.000453233718872
    from in index
    3.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 time
    
    def 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
    
    @times
    def home(name):                            # home()函数有返回值
        time.sleep(2)
        print("from %s in home!" % name)
    
    @times
    def index():
        time.sleep(3)
        print("from in index")
    
    home("lin")                                # 在这里在执行home("lin") 函数是,就是在执行 f1(*args,**kwargs)
    index()
    

    输出:

    from lin in home!
    2.0006303787231445
    from in index
    3.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
    
    @auth
    def home(name):
        print("from %s in home" % name)
    
    @auth
    def index():
        print("from in index!")
    
    index()
    home("lin")
    

    db.txt内容

    {"zhangsan":"123","wangwu":"456","zhaoliu":"789"}
    

    输出:

    输入你的名字:zhangsan
    输入你的密码:123
    from 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
    输入你的秘密:123
    from lin in homepython
    

    使用装饰器时,显示源函数调用方式的帮助信息

    import time
    import 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
    
    
    @times
    def home(name):
        '''
        这是home的帮助信息!
        :return:
        '''
        time.sleep(3)
        print("from %s in home" % name)
    
    print(home.__doc__)         # 打印函数的注释信息
    home("lin")
    

    输出:

        这是home的帮助信息!
        :return:
        
    from lin in home
    3.0001637935638428
    

    装饰器上面在套用装饰器,达到多次装饰的目的

    1.在计算程序运行时间的装饰器上,在加一个验证装饰器

    import time
    import 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 f1
    
    
    def 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
    输入你的密码:123
    from lin in home
    3.000636339187622
    

    装饰器练习(巩固加深)

    1.编写日志装饰器,实现功能如:一旦函数index执行,则将消息2017-07-24 22:11:17 index run写入到日志文件中,日志文件路径可以指定(装饰器2017-07-24.log)

    import os, time
    
    def 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
    " % (time.strftime("%Y-%m-%d %X"), func.__name__))     # 将当前时间 文件名记录到文件中
                return res
            return f1
        return deco
    
    
    time_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, hashlib
    
    settings = {                                # 定义字典格式的 缓存 存放格式 选择
        "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对象 "<md5 HASH object @ 0x0000025C7ED35300>"
                    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).text
    
    
    print(get("https://www.python.org"))
    print(get("https://www.jd.com"))
    
  • 相关阅读:
    python3+spark2.1+kafka0.8+sparkStreaming
    python3.5读取kafka中的数据
    kafka启动及查看topic命令【已用过的,待更新】
    filebeat+kafka失败
    情侣浪漫网页制作
    侃侃静态代理和动态代理
    MyBatis增删改查模板
    虚拟机和Docker的异同
    <context:annotation-config/>,<mvc:annotation-driven/>和<context:component-scan>之间的关系
    异步上传图片到另外一台服务器
  • 原文地址:https://www.cnblogs.com/baolin2200/p/6406349.html
Copyright © 2011-2022 走看看