zoukankan      html  css  js  c++  java
  • Python 闭包与装饰器

    前几篇的笔记都是一值在推导神经网络, BP算法, 反复推, 一直在求导, 多变量求偏导, 引入中间变量求偏导... 连续推导了 2遍, 感觉头都大了, 觉得, 缓一波, 看看编程的东西, 比如来整个 Python 的语言特性.

    近两年很流行 Python, 都说, 简单易懂, 上手快, 3个月就能精通.... 我也用了好几年了其实, 我感觉, 这些话语, 真的是在瞎扯, 主要是混淆了概念, 将 语法 与 语言 弄混淆了. 然后在工作中, 就看到了太多的烂代码, 当然, 我自己也还烂, 都是些, 一来就写, 随意命名变量, 不写关键注释, 函数互相调, 没有模块化, 基本语法不熟, 以为定义了个类, 就是面向对象... 实在是无力吐槽 ... 就很难看到一些, 简单易懂, 容易维护, 的高质量代码, 导致现在我都已经有 严重的代码洁癖和工作洁癖, 就是, 除非必须如此, 坚决不接别人的烂代码, 宁愿全部推倒重来或重构...

    感觉说偏题了, 怎么变成了吐槽, 哈哈. 就趁着放假吗, 看看编程基础, 语言特性这些东西, 还是蛮重要的, 感觉. 当然, 我自己感觉也很菜, 假如笔误了, 也没关系, 笔记, 毕竟, 发现了, 改过来就行, 重在学习, 认知的过程.

    变量的本质

    在 Python 中很流行一句话, 说 万物皆对象. 细聊就要要从 类 (Class) 讲起来, 本篇就算了, 结论是, Python 中的整数, 浮动数, 字符串, 列表, 元组, 字典 ...这些东西, 其实都是 某一个类的实例而已. 因此, 这也意味着, 可以重写 或自定义类. 来构造自己的一套法则.

    能自定义啥意思, 就真的可以为所欲为呀, 比如你可以重新定义 "+", 让 1 + 1 == 3 也是可以的哦.

    那作为语言最基本的元素 , Python 中的 变量, 到底是什么呢?

    是一个对象地址的引用, 即 Python 中的变量, 就是 C 语言 中的 指针, 变量不存储实值, 而是存一个地址. 当创建一个变量, 如 a = 123; a = [1,2,3] , 这其实说在说, 将 变量 a 指向 实例对象123 的地址. 其变量的类型, 完全取决于它 指向的 实例对象的类型. 这就是 Python 代码中不需要事先声明变量类型的原因 , 理解这一点很关键, 跟 Java, C, R, JavaScript, 这些语言是不一样的. Python 变量存储的就是指针地址而已, 根本就不存储实例对象的值. 只是一个指向关系而已.

    说明 Python 变量的本质是一个指针, 最直观的方式, 我用一个 单链表的, 头部插元素 的案例, 觉得非常形象

    case1 - Pointer

    class Node:
        """节点类, 包含数据区, 和指针区"""
    	def __init__(self, data):
            self.data = data
            self.next = None
            
    class LinkList:
        """单链表类, 由一个头部, 和很多节点组成"""
        def __init__(self)
        	self.head = None
            
        # head -> None
        # 现在要将节点 node1, node2, node3, 依次从链表的head 插入
        # head -> node3 -> node2 -> node1
        
        def add(self, item):
            """头插法"""
    		node = Node(item)
            # 实在不理解可以用纸笔画一下指向 和 断链的过程
            node.next = self.head
            self.head = node 
    

    如果能理解到 node.next = self.head ; self.head = node 这两句话, 则就真正理解, Python变量的本质是指针了.

    case2 - Variable:

    # Py 变量的本质是地址的引用呀
    
    a = [1,2,3]  # a -> id([1,2,3])
    b = a   	 # b -> a
    c = a        # c -> a
    
    print('a_id:', id(a))
    print('b_id:', id(b))
    print('c_id:', id(c))
    
    # output
    a_id: 2827009387784
    b_id: 2827009387784
    c_id: 2827009387784
    

    case3 - Function

    def foo1():
        return 'i am foo1'
    
    
    # foo2 去引用 foo1 地址
    foo2 = foo1
    
    print('call foo1:', foo1())
    print('foo1_id:', id(foo1))
    
    print('call foo2:', foo2())
    print('foo1_id:', id(foo2)))
    
    # output
    call foo1: i am foo1
    foo1_id: 1745719143960
    --------------------------------  
    call foo2: i am foo1
    foo1_id: 1745719143960
    

    case4 - Class

    class CalcSales:
        def __init__(self, price):
            self.price = price
    
        def __call__(self, number):
            return self.price * number
    
    
    calc_sales = CalcSales(6.6)
    
    # 对象()  --> 表示调用类的 __call__ 方法
    
    print(calc_sales(100))
    print(calc_sales(1000))
    
    # oupput
    660.0
    6600.0
    

    可以初步下个结论:

    • 函数名和变量名都是一样的, 都只是 代码(内存) 空间的引用, 机制上其实就是递归调用栈

    • 当函数名赋给另一变量, 或者说, 令另一个变量取去指向该函数时, 这就是引用的传递.

    • 类等...亦是如此

    闭包

    在理解了Python 变量的本质是引用后, 理解闭包就会很容易.

    闭包是一种函数嵌套函数 的写法, 其实工作中好像基本没用到过多, 主要是为了引出后面的装饰器而已. 闭包具有这样的特点:

    • 外函数 里面 嵌套着 内函数
    • 外部函数 返回 内部函数的 地址
    • 内部函数 能 用到 外部函数的 参数

    真正的工作核心是 内函数, 外函数的作用, 好似给器, 增加了一个保护套, 或者说延长了内函数的生命周期.

    case1

    def 外函数(a):
        
        def 内函数(b):
            print('a=', a, 'b=', b)
        
        return 内函数
    
    # 内函数是可以拿到外函数的参数的
    # 传参的话, 从外到里哦
    
    外函数(1)(2)
    
    # output
    a= 1 b= 2
    

    **case2 **

    # y = ax + b
    
    def 直线方程(斜率, 偏置):
        
        def 某点的预测值计算(位置):
            return 位置 * 斜率 + 偏置
        
        return 某点的预测值计算
    
    直线方程 = 直线方程(3, 5)
    print("当自变量为 20 时, 因变量为:", 直线方程(位置=20)) 
    
    # output
    当自变量为 20 时, 因变量为: 65
    

    Python3 以后是支持中文变量的哈, 也主要是为了理解这闭包的概念以及用法, 感觉上, 外函数, 就只是返回了内函数的地址而已, 而内函数开始真正工作, 内函数是可以拿到外函数的参数的, 就相当于将 内函数的 作用空间扩大了.

    装饰器

    装饰器是是闭包的一种特例, 特殊在于, 要求外函数, 只能传一个 "函数对象" 的参数

    case1

    def 装饰器_认证(被装饰函数):
        
        def 内函数():
      
            print("---颜值认证 通过---")
            被装饰函数()
        
        return 内函数
    
    @装饰器_认证
    def check():
        print("小陈同学, 肩上责任很大呀")
        
    # 效果是, 每次调用 check 函数, 则会在其上面打印 内函数里的话
    check()
    
    # out
    ---颜值认证 通过---
    小陈同学, 肩上责任很大呀
    

    不难看出, 装饰器的 实质 是, 在不改变原函数的前提下, 额外扩展原函数的功能

    可能有小伙伴会很疑惑, 为啥要这么麻烦, 不能直接改源代码嘛? 是的 不能改. 如果有做个web程序的小伙伴就很明白, 一个地方改动, 会影响一大块改动的, 直接改源码, 我感觉会直接被同时拉黑, 那这种场景, 就可以用 写个装饰器了, 然后在改的地方, 给装饰一下, 带个套就好了. 就有点像打补丁.

    更多的应用场景, 如 日志管理, 函数执行统计, 函数执行前, 后 的处理, 权限验证, 缓存处理等, 还是蛮多的, 也比较装逼, 写出来话, 当然不推荐为了炫技而写, 代码简洁优雅 和 能让别人看懂 这样的高效沟通才是最重要的.

    case2 - 带参数

    def check_nCov(func):
        """内函数接收参数"""
        def inner(param):
            print("... check...")
            func(param)
        return inner
    
    @check_nCov
    def check_2019_nCoV(user_name):
        print(user_name, "is very healthy.")
    
    check_2019_nCoV("youge")
    
    # output
    ...check...
    youge is very healthy.
    

    case3 - args - kwargs

    def out(func):
        def inner(*args, **kwargs):
            print("--- be decorated with the out function---")
            func(*args, **kwargs)  # 拆包 unpack
    
        return inner
    
    
    @out
    def check_2019_nCoV(*args, **kwargs):
        print(args)
        print(kwargs)
        print("ok, working now ...")
    
    # output
    
    --- be decorated with the out function---
    (1, 2, 3)
    {'user_name': 'youge', 'password': '123'}
    ok, working now ...
    
    

    这里的 * args 与 ** args 的参数组包 (一个 * 元组, 两个 * 是 字典) , 和 后面的 拆包 unpack 应用还是很多的, 我有的时候, 会用来做一个 异常处理 , 即有时不论接收的什么, 都接收, 但不处理 , 这样就能一定程度上保持程序的稳健性, 不至于突然崩溃.

    其次, 调用函数, 参数的 组包, 拆包 也是蛮常见的, 个人很喜欢这种设计哦.

    case4 - 参数unpack

    def my_func(a, b=0, *hello, **world):
        
        print(a, b)
        print(hello)
        print(world)
        
    my_func(1,2,3,3,4,5, name='youge')
    
    # 参数传递顺序: 位置-> 默认值-> *arg, **args
    # 通常可用来作为参数传递的异常处理哦
    
    # output
    1 2
    (3, 3, 4, 5)
    {'name': 'youge'}
    
    
    def my_func(*args):
        """参数组包拆包"""
        
        for i, v in enumerate(args):
            print(i, v)
    
    
    lst = [1, 2, 3, "youge"]
    my_func(*lst)
        
    # out
    0 1
    1 2
    2 3
    3 youge
    

    case5 - 返回值

    def out(func):
        
        def inner(*args, **kwargs):
    
            print(args, kwargs)
            
            # 被装饰的函数有返回值
            return func(*args, **kwargs)
    
        return inner
    
    
    @out
    def check_2019_nCoV(user_name):
    
        return f"{user_name} is very healthy."
    
    
    tmp = check_2019_nCov("youge")
    print(tmp)
    
    # output
    ('youge',) {}
    youge is very healthy.
    

    case6 - 类

    # 应用 __call__ 方法, 当类被调用, 就会自动执行
    # 即把 装饰的代码放 __call__ 里面就可以了.
    
    class Out:
        def __init__(self, func):
            self.func = func
    
        def __call__(self, *args, **kwargs):
            
            print("--- be decorated with the out function---")
            return self.func(*args, **kwargs)
    
    @Out
    def check_2019_nCoV(user_name):
        return f"{user_name} is very healthy."
    
    
    print(check_2019_nCoV('youge'))
    
    # out
    
    --- be decorated with the out function---
    youge is very healthy.
    
    

    case7 -multiple

    def out1(func):
        print('i am the decorator 01')
        def inner(*args, **kwargs):
            print("...check_01...")
            return func(*args, **kwargs)
    
        return inner
    
    
    def out2(func):
        print("i am the decorator 02")
        def inner(*args, **kwargs):
            print("...check_02...")
            return func(*args, **kwargs)
    
        return inner
    
    @out1
    @out2
    def check_2019_nCoV(user_name):
        print("okay~")
        return f"{user_name} is very healthy"
    
    # out
    i am the decorator 02
    i am the decorator 01
    ...check_01...
    ...check_02...
    okay~
    youge is very healthy
    

    发现, 如果函数被 多个装饰器, 装饰时, 装饰的顺序跟咱期望一样, 但装饰器的函数, 执行的顺序却是逆序的哦

    闭包与装饰器就到这吧, 差不多了. 尽可能多代码演示, 少文字叙述. 嗯, 装饰器感觉还是蛮有意思的, 感觉也算是Python的一个突出的语言特性了吧.

  • 相关阅读:
    ASP.NET Web API(二):安全验证之使用HTTP基本认证
    对象映射工具AutoMapper介绍
    分享一个基于Bootstrap的 ACE框架 入门(MVC+EF)
    C/C++资料网站
    C++基础:二维数组动态的申请内存和释放内存
    基于dijkstra算法求地铁站最短路径以及打印出所有的路径
    最短路径算法
    Android学习_ContentProvider
    Android_ListActivity使用注意
    Android_Adapter与ListView
  • 原文地址:https://www.cnblogs.com/chenjieyouge/p/12236618.html
Copyright © 2011-2022 走看看