Loading

三:函数+闭包+装饰器

一:函数

以功能为导向,一个函数就是一个功能,需要的时候拿来直接用,函数名指向一个函数对象。

增强了代码的可读性,方法名基本就可以猜出来是干什么的方法。

处理数据的逻辑封装,方便反复使用,数据和逻辑分离,数据由参数接收,函数内部封装了处理数据的逻辑,达到数据和逻辑分离的目的。
分为:函数定义,函数调用,函数嵌套
定义时:位置参数  默认参数 *args   命名关键字的默认参数    **kwargs,顺序不允许改变.最简单的就是*args  **kwargs不变应万变。
调用时:参数不是必须按上面的顺序,但是关键字参数后面的必须使用关键字传参,但最好按定义时传参.

3.8版本多了一个/符号,表示左边参数都是位参,必须传递,就像*一样,规定右边必须是关键字参数。
返回值默认是元组,所以可以return多个,不显示return就是return None,python中没有Null,只有None类型是NoneType

再次强调函数的返回值很重要,line.raplace("sb","SB")这样确实改变了line中的字符,但是字符串替换的本质是开辟了新的内存创建了新的字符串对象,因此

必须line=line.replace("sb","SB")将生成新的str用line重新指向。否则line总是指向原来的str。

1.1函数的参数


函数里面是比较封闭的写好了之后几乎是不会再去改动,尤其是无参函数,当然优化除外

带参函数是为了盘活这个函数,对于外界动态传入的数据进行动态的执行。

位置参数:1.必须传递参数,2.顺序如果不是关键字传参不允许改动顺序,如果是关键字传参,顺序随意,但是位置参数必须放在关键字传参前面。

缺点:当参数过多的时候,顺序是难以记住的。

关键字参数:当顺序记不住的时候,按关键字传参,test(age=18,name="zhangsan")

位置参数和关键字参数混合使用调用函数时,位置参数必须放在关键字参数的前面。

默认参数:函数定义阶段,key=value的形式表示默认值参数,位于*前面只要调用阶段不动它,就一直沿用默认值,但是不能为可变数据类型

想要动默认参数就必须使用关键字传参,放在位置参数之后调用

万能参数:*args   **kwargs

*args:形参角度,表示聚合所有的位置参数到一个元组中,用args变量名指向元组。

**kwargs:形参角度,表示将所有的关键字参数聚合到一个字典中,用kwargs变量名指向字典。

定义函数时test(*args,**kwargs)就是万能参数了。

形参角度的参数顺序:位置参数   默认参数   *args (默认参数) **kwargs 

默认参数和*arg谁放在前面,多余的参数就给谁

*args放前面,所有位置参数都给*args,想改默认参数只能关键字参数的形式修改

默认参数放*args前面,多余出来的一个参数默认给了默认参数,即使没有用关键字传参的方式,剩下的都给*args

def  test(a,b,c=2,*args):
        pass
test(1,2,3,4)
默认参数有2种方式传递,假设没有*args,只传递了三个参数,test(1,2,3)3会分配给参数c
或者test(1,2,c=3)两种方式更改默认参数
因此*args要放在默认参数之后,否则把所有的位置参数都放入元组了,第二种默认参数的修改方法失效。

二:闭包

本质:函数嵌套定义.以一种特殊的方式将对象创建出来返回到全局并接收,为的就是保存状态反复使用。

闭包:内部函数引用外部函数非全部变量,并且把内部函数的引用进行了return,全局必须有接收return引用的变量,否则闭包没有任何意义。

函数传参两种形式,一是参数形式传递,二是闭包。

 

全局用一个变量接收了return的内部函数,这样随着函数的调用,并不会立马结束掉,因为全局有一个引用指向,GC无法回收可以在内存驻留,全局引用了内部,内部引用了外部的局部变量,内部+外部函数都不会被回收,使得这种状态得以在内存保留,想要再用的时候直接调用就可以了,即特殊的创建对象的方式。
闭包打破的传统的规则:通过把内部函数引用return到全局完成规则的打破,外部无法访问函数内部的变量,可以这样做正是因为return的神奇,他可以把一切返回到上一个临近的作用域。

 

相当于变向的创建了一个对象,这个对象中外部函数的局部变量就是属性,内部函数就是方法,可以这么理解,那么你调用函数时传进来的过程相当于init初始化了属性,那么这个状态就得以在内存中保留,
还想复用的时候就直接用全局的引用加()就可以随时调用,不用像传统方法那样频繁的开栈帧弹栈,以及里面的变量也要随着函数频繁调用而频繁开内存,大大提升了效率.

而闭包的进阶装饰器,只是将变量指向的数据换成了函数的代码片段而已,他们在编码的时候用的不多,更多的使用在代码的可维护性。

面向对象编程:

为什么创建对象?

因为对象里面封装了解决一类问题需要的方法和变量,使得你创建一次后面就可以反复的使用.

而闭包或装饰器就是另一种方式而已,

没有面向对象完全可以开发,只是有了面向对象之后,让开发更加容易理解,容易实现.

def fn():
    a = 10
    # 函数内部再定义一个函数
    def inner():
        nonlocal a
        a += 1
        print('我是fn2' , a)
    return inner

r = fn()
r()
View Code

以前的函数定义和调用

import requests
def  get(url):
        response = requests.get(url)
        print(response)
get("https://www.baidu.com")
get("https://www.baidu.com")
get("https://www.baidu.com")
get("https://www.baidu.com")
get("https://www.baidu.com")
之前函数定义和调用

每次请求百度,都要写一遍"https://www.baidu.com",虽然可以用一格全局变量a="https://www.baidu.com"来不用写那么多次的https://www.baidu.com,但是每次调用都要重新的开新内存

import requests

a="https://www.baidu.com"
def  get(url):
    def inner():
        response = requests.get(url)
        print(response)
    return inner
f=get(a)
f()
f()
f()
闭包

这个引用一直在,不会随着函数的调用结束而消失,一直在内存中,反复使用,大大提升效率.

python的GC类似java中的回收机制gcroot开始看引用的一条线,只要不在都会被回收,函数的是function对象,这个对象是python解释器帮我们起的名字,调用完就会被解释器回收掉,而闭包这里我们主动拉了一条线到函数对象上,GC从gcroot开始找垃圾的时候就不会把这个函数对象回收掉.

可以把闭包视为OOP的变形:

变形一:类的畸形定义,封装数据靠外部函数来接收,内部函数就是方法

变形二:只有方法的类,外部函数接收的方法是不同的,但是内部函数的逻辑是固定死的,用来对外部函数进行装饰,但是外部函数的内部无法改造,只能在它的外部进行修饰.

另一个扩展:只有属性的类:namedtuple

无论是变形一还是变形二,他们的目的都是为了重用,变形一使得一个数据传入闭包,对象在内存长期驻留,反复使用

变形二使得一个装饰的过程即内部函数不变,通过外部函数把要进行装饰的函数的引用接收进来,

三:再进阶

没有类的封装,完全可以靠函数来完成变成工作,只不过用了类之后区分更明显,类把数据和方法封装到了一起

而函数的数据和逻辑是分离的,除了默认值,其他的数据都要靠形参接收到函数内部

四:装饰器:闭包的升级

常用在有切片的场景,例如逻辑代码中插入日志和性能测试代码,事务处理,缓存,权限校验等。

不改变原有逻辑和调用的方式为前提来扩展功能

开放封闭原则:对修改关闭,对扩展开放.

装饰器完美的解决了无感知扩展调用方式也不会改变,别人不会因为你修改代码而去大篇幅修改别人的代码

外部函数就是负责把数据接收到实例对象中,最后把内部函数的引用返回到全局,内部方法负责对传进来的函数进行修饰,

全局引用的调用实际就是内部函数的调用,妙在外部接收的变量名和要装饰的函数名是一致的,这就无感知的骗过了装饰器,名字虽然一样,但是通过内部函数修饰了原函数.

相同的装饰逻辑对象长驻内存,方便随时反复使用.

def outer(fn):
    def inner(*args,**kwargs):
        print("头部装饰")
        fn()
        print("尾部装饰")
    return inner
@outer
def hexin():
    print("main test")
View Code

一个坑就是,先把装饰器写出来,然后再加到要被装饰的函数头顶上

装饰的开关

如果开发时测试性能,加上装饰器,测试完了之后去掉装饰器,难道一个一个删?3000个函数,加3000遍,删3000遍?拒绝无用的重复劳作!

五:总结

编程里面非常重要的思想就是重复利用---------------->"偷懒".

falg = True
def outer(flag):
    def timer(func):
        def inner(*args, **kwargs):
            if flag:
                print('''执行函数之前要做的''')
            re = func(*args, **kwargs)
            if flag:
                print('''执行函数之后要做的''')
            return re

        return inner

    return timer

flag=True
@outer(flag)# 先看outer(flag)拿到装饰器,然后@装饰器开始装饰,走了2步
def func():
    print(111)

func()
View Code

当关闭装饰器,把全局的flag改为false就可以了

六:高阶函数

将函数作为参数传递,或者作为返回值返回的函数都是高阶函数

可见python是很灵活的。

posted @ 2019-09-20 10:42  浅忆尘  阅读(177)  评论(0编辑  收藏  举报