函数式编程

面向过程:

  • 根据业务逻辑从上到下写垒代码

函数式:

  • 将某功能代码封装到函数中,日后便无需重复编写,仅调用这个函数即可
  • 函数作用是你的程序有良好的扩展性、复用性。
  • 同样的功能要是用3次以上的话就建议使用函数。

函数可以理解为:一个一个的 功能块,你把一个大的功能拆分成一块一块的,用某项功能的时候就去调用某个函数,

函数可以理解为:乐高积木,给你一块一块的,你可以用这些积木块组成你最终想要实现的东西。

函数可以调用函数!主函数(main)的作用就是把函数进行串联、调用!函数本身是不能自己执行的如果你不调用就永不执行!

背景

在学习函数之前,一直遵循:面向过程编程,即:根据业务逻辑从上到下实现功能,其往往用一长段代码来实现指定功能,开发过程中最常见的操作就是粘贴复制,也就是将之前实现的代码块复制到现需功能处,如下:

In [ ]:
while True
    if cpu利用率 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接
  
    if 硬盘使用空间 > 90%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接
  
    if 内存占用 > 80%:
        #发送邮件提醒
        连接邮箱服务器
        发送邮件
        关闭连接

定眼一看上述代码,if条件语句下的内容可以被提取出来公用,如下:

In [ ]:
def 发送邮件(内容)
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接

while True
  
    if cpu利用率 > 90%:
        发送邮件('CPU报警')
  
    if 硬盘使用空间 > 90%:
        发送邮件('硬盘报警')
  
    if 内存占用 > 80%:
        发送邮件('内存报警')

对于上述的两种实现方式,第二次必然比第一次的重(chong)用性和可读性要好,其实这就是 函数式编程面向过程编程 的区别:

  • 面向过程:根据业务逻辑从上到下写代码
  • 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可

函数的定义

In [ ]:
def 函数名(参数):
     
    ...
    函数体
    ...

函数的定义主要有如下要点:

  • def:表示函数的关键字
  • 函数名:函数的名称,日后根据函数名调用函数
  • 函数体:函数中进行一系列的逻辑计算,如:发送邮件、计算出 [11,22,38,888,2]中的最大数等...
  • 参数:为函数体提供数据
  • 返回值:当函数执行完毕后,可以给调用者返回数据。

以上要点中,比较重要有参数和返回值:

  1. 返回值

函数是一个功能块,该功能到底执行成功与否,需要通过返回值来告知调用者。

In [ ]:
def 发送短信():

    #  发送短信的代码...
    
    if 发送成功:
        return True
    else:
        return False
 
 
while True:
     
    # 每次执行发送短信函数,都会将返回值自动赋值给result
    # 之后,可以根据result来写日志,或重发等操作
 
    result = 发送短信()
    if result == False:
        记录日志短信发送失败...
  1. 参数

为什么要有参数?

不使用参数:

In [ ]:
def CPU报警邮件()
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接

def 硬盘报警邮件()
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接

def 内存报警邮件()
    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接

while True
 
    if cpu利用率 > 90%:
        CPU报警邮件()
 
    if 硬盘使用空间 > 90%:
        硬盘报警邮件()
 
    if 内存占用 > 80%:
        内存报警邮件()

使用参数:

In [ ]:
def 发送邮件(邮件内容)

    #发送邮件提醒
    连接邮箱服务器
    发送邮件
    关闭连接


while True
 
    if cpu利用率 > 90%:
        发送邮件("CPU报警了。")
 
    if 硬盘使用空间 > 90%:
        发送邮件("硬盘报警了。")
 
    if 内存占用 > 80%:
        发送邮件("内存报警了。")

函数的参数分为以下几种:

  • 普通参数
  • 默认参数
  • 指定参数
  • 非关键字可变长参数
  • 关键字可变长参数
  • 万能参数

普通参数传递

In [7]:
######### 定义函数 ######### 
# name 叫做函数send的形式参数,简称:形参
def send(name):
    print(name)

######### 调用函数 #########
#  "Kevin" 叫做函数send的实际参数,简称:实参
send("Kevin") 
Kevin

指定参数传递

In [8]:
# 指定参数传递
def send(A,B,C):
    print(A,B,C)
    
# 指定参数必须所有参数都要指定
send(B="Lina", A="Kevin", C="Gary")  
Kevin Lina Gary

默认参数

In [9]:
# 默认参数,没有传递参数,使用默认参数,传递了则使用传递的参数
def send(A, B, C="stupid"):
    print(A, B, C)

send("Gary", "you're")
Gary you're stupid

关键字可变长参数

In [11]:
# 关键字可变长参数,也可接收任意个参数,参数传递形式:key-value
def send(**kwargs):  # 注意这里,定义函数这里的参数书写格式需要写成这样
    print(kwargs)

f1_dict = {
    'a': 'aaa',
    'b': 'bbb',
    'c': 'ccc'
}

send(a='aaa', b='bbb', c='ccc')
# 达到如上一样的效果
send(**f1_dict)
{'a': 'aaa', 'b': 'bbb', 'c': 'ccc'}
{'a': 'aaa', 'b': 'bbb', 'c': 'ccc'}

非关键字可变长参数

In [12]:
# 非关键字可变长参数,可接收任意个参数
def send(*args):
    print(args)

send("panwenbin", "lianglian", "zhaohongfei")
send(*["panwenbin", "lianglian", "zhaohongfei"])   # 使用"*"可遍历序列,递归传參
('panwenbin', 'lianglian', 'zhaohongfei')
('panwenbin', 'lianglian', 'zhaohongfei')

万能参数

In [35]:
# 万能参数,可接收任意个参数,关键字可变长参数和非关键字可变长参数同时使用,接收任意类型任意个数参数
def send(*args, **kwargs):
    print(*args)
    print(kwargs)

send(1, 11, 21, **{"k1": "v1", "k2": "v2"})
1 11 21
{'k1': 'v1', 'k2': 'v2'}

python中使用到万能参数的内置函数:

In [36]:
# string的format()方法用到万能参数的例子
s1 = "i am {0}, age {1}".format("lianglian", 12)
print(s1)

s2 = "i am {0}, age {1}".format(*["lianglian", 12])
print(s2)

s3 = "i am {name}, age {age}".format(name="lianglian", age=12)
print(s3)

dict={'name': 'liangian', 'age':12}
s4 = "i am {name}, age {age}".format(**dict)
print(s4)
i am lianglian, age 12
i am lianglian, age 12
i am lianglian, age 12
i am liangian, age 12

注意事项

python 函数多次定义被覆盖:

In [37]:
# python 函数多次定义被覆盖
def f1(a1, a2):
    return a1 + a2

def f1(a1, a2):
    return a1 * a2

ret = f1(8,8)
print(ret)
64

python 参数传递是引用:

In [38]:
# python 参数传递是引用
def f1(a1):
    a1.append(999)

li = [11, 22, 33, 44]
f1(li)

print(li)
[11, 22, 33, 44, 999]

函数内定义的变量只在该函数内有效:

In [39]:
# 函数内定义的变量只在该函数内有效(注意作用域)
# global 修改全局变量
NAME = 'lianglian'

def f1():
    age = 18
    global NAME      # 使用global修改过全局生效,作用于全局
    NAME = 'Kevin'  
    print(age, NAME)

def f2():
    age = 19
    print(age, NAME)

f1()
f2()
18 Kevin
19 Kevin

Python 没有常量这个数据类型,但是在行首定义的常量(变量),遵循不要重新赋值

In [43]:
# 序列(list,tuple...) 行首定义的序列(全局)可以修改 但是不能重新赋值
group = [11,22,33,44]

def f1():
    age = 18
    group.append(55)
    print(age, group)

def f2():
    age = 19
    print(age, group)

f1()
f2()
18 [11, 22, 33, 44, 55]
19 [11, 22, 33, 44, 55]

函数实參和形參之间是引用,内部定义的变量和只在内部生效

In [45]:
# 函数实參和形參之间是引用
lists = [1, 2, 3, "q"]
print(id(lists))     # 创建变量这个变量得时候的地址空间

def name(li):
    print(id(li))    # 引用实际参数,在这里内存地址空间还是一样

    li = "c"
    print(li)        # 只在这个函数内生效,就像是在这个函数内重新定义了一个"li"变量,和外面那个没关系
    print(id(li))    # 这里是重新赋值了形参,所以地址空间变了

name(lists)
print(lists)
4367712648
4367712648
c
4331025664
[1, 2, 3, 'q']
In [47]:
# 函数内重新赋值作用域只限于函数内,函数内变了,外面没变
name = "Kevin"
def  show():
    name='liang'
    print(name)
show()
print(name)
liang
Kevin

lambda表达式

In [48]:
# lambda 表达式会自动retrun结果
# lambda表达式  (简单的函数表达式,只能写一行)
def f1(a1):
    return a1 + 100

ret = f1(10)
print(ret)

f2 = lambda a1: a1 + 100
r2 = f2(10)
print(r2)
110
110

装饰器

装饰器也是个函数,只不过该函数可以具有特殊的含义,装饰器用来装饰函数或类,使用装饰器可以在函数执行前和执行后添加相应操作(装饰:在不改变原函数本身情况下,给他添加额外得功能,就像在一个普通士兵给她装备各种牛逼装备只有他能变得异常强大,而装饰器就是哪些装在它身上的牛逼装备)。

一、初创公司有N个业务部门,1个基础平台部门,基础平台负责提供底层的功能,如:数据库操作、redis调用、监控API等功能。业务部门使用基础功能时,只需调用基础平台提供的功能即可。如下:

In [ ]:
############### 基础平台提供的功能如下 ###############
  
def f1():
    print 'f1'

def f2():
    print 'f2'

def f3():
    print 'f3'

def f4():
    print 'f4'
In [ ]:
############### 业务部门A 调用基础平台提供的功能 ###############
  
f1()
f2()
f3()
f4()
  
############### 业务部门B 调用基础平台提供的功能 ###############
  
f1()
f2()
f3()
f4()

目前公司有条不紊的进行着,但是,以前基础平台的开发人员在写代码时候没有关注验证相关的问题,即:基础平台的提供的功能可以被任何人使用。现在需要对基础平台的所有功能进行重构,为平台提供的所有功能添加验证机制,即:执行功能前,先进行验证。

老大把工作交给 worker A,他是这么做的:

跟每个业务部门交涉,每个业务部门自己写代码,调用基础平台的功能之前先验证。诶,这样一来基础平台就不需要做任何修改了。

当天worker A 被开除了...

老大把工作交给 worker B,他是这么做的:

只对基础平台的代码进行重构,让N业务部门无需做任何修改

In [ ]:
############### 基础平台提供的功能如下 ############### 

def f1():
    # 验证1
    # 验证2
    # 验证3
    print 'f1'

def f2():
    # 验证1
    # 验证2
    # 验证3
    print 'f2'

def f3():
    # 验证1
    # 验证2
    # 验证3
    print 'f3'

def f4():
    # 验证1
    # 验证2
    # 验证3
    print 'f4'

过了一周 worker B 被开除了...

老大把工作交给 worker C,他是这么做的:

只对基础平台的代码进行重构,其他业务部门无需做任何修改

In [ ]:
############### 基础平台提供的功能如下 ############### 

def check_login():
    # 验证1
    # 验证2
    # 验证3
    pass

def f1():
    check_login()
    print 'f1'

def f2():
    check_login()
    print 'f2'

def f3():
    check_login()
    print 'f3'

def f4():
    check_login()
    print 'f4'

老大看了下Low BBB 的实现,嘴角漏出了一丝的欣慰的笑,语重心长的跟Low BBB聊了个天: 老大说:

写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:

  • 封闭:已实现的功能代码块
  • 开放:为已经实现的功能代码库做扩展开发

如果将开放封闭原则应用在上述需求中,那么就不允许在函数 f1 、f2、f3、f4的内部进行修改代码,老板就给了Low BBB一个实现方案:

In [ ]:
def w1(func):
    def inner():
        # 验证1
        # 验证2
        # 验证3
        return func()
    return inner
  
@w1
def f1():
    print 'f1'
@w1
def f2():
    print 'f2'
@w1
def f3():
    print 'f3'
@w1
def f4():
    print 'f4'

对于上述代码,也是仅仅对基础平台的代码进行修改,就可以实现在其他人调用函数 f1 f2 f3 f4 之前都进行【验证】操作,并且其他业务部门无需做任何操作。

worker C心惊胆战的问了下,这段代码的内部执行原理是什么呢?

单独以 f1 为例:

In [52]:
# @ + 函数名
# 功能: 1.自动执行这个函数,并且将下面的函数名f1当做参数传递
#      2.将outer函数的返回值,重新赋值给放f1
def outer(func):
    def inner():
        print('run inner')
        return func()
    return inner

@outer
def f1():
    print("F1")

f1()
run inner
F1

当执行的时候,python是由上到下执行的,首先执行到定义函数:def outer(func) 这里把def outer(func)加载到内存

当执行到 @outer 的时候@outer是python的魔法!它会把它下面的函数进行封装。

把f1这个函数作为def outer(func)的参数传进去!就是:f1()=outer(f1)

然后def outer(func): == outer(f1)就会执行:

In [ ]:
def outer(func):
    def inner():
        print('run inner')
        return func()  # func() == 原函数f1
    return inner    # 然后把封装后的函数return的结果给原函数 (= f1)

@outer就相当于做了一个重新打了个包,包含原数据和新增数据,然后变成新的包给"f1"

def f1() <==> def inner()

In [53]:
@outer              #  outer(f1)
def f1():           #      def inner():
    print("F1")     #          print('run inner')
                    #          return f1()
                    #      return inner()

用装饰器写一个计时器来统计函数的执行时间:

In [59]:
import time

def timer(func):
    def inner():
        start_time = time.time()
        result = func()
        end_time = time.time()
        time_used = end_time - start_time
        print("time_used: %.2fs" % time_used)
        return result
    return inner

@timer
def f1():
    time.sleep(10)
    print(111)

f1()
111
time_used: 10.00s

被装饰的函数如果有参数呢?

In [63]:
#  装饰函数使用参数
def outer(func):
    def inner(name):      # func对应f1,f1有参数,这里也要接收参数好传递给内部funce
        print("before")
        r = func(name)
        print("after")
        return r  # 不要丢弃原函数返回值
    return inner


@outer
def f1(arge):
    print(arge)
    return "F1"

ret = f1('test')
print(ret)
before
test
after
F1

装饰器装饰动态参数函数:

In [65]:
#  装饰器装饰动态参数函数
def outer(func):
    def inner(*arge, **kwargs):   # 装饰器使用动态参数,来应对装饰不同得函数参数不确定情况
        print("before")
        r = func(*arge, **kwargs)  # 将原原函数的参数传递进来
        print("after")
        return r  # 不要丢弃原函数返回值
    return inner


@outer
def f1(a1):     # 一个参数
    print(a1) 
    return "F1"

@outer
def f2(a1, b1):   # 两个参数
    print(a1, b1)   
    return "F2"

f1('test1')
f2('test1', 'test2')
before
test1
after
Out[65]:
'F1'
before
test1 test2
after
Out[65]:
'F2'

多装饰器的使用:

In [ ]:
USER_INFO = {'is_login': None, 'user_type': 1}
# USER_INFO['is_login']
# USER_INFO.get('is_login', None)


def check_login(func):
    def inner(*args, **kwargs):
        if USER_INFO.get('is_login', None):
            ret = func(*args, **kwargs)
            return ret
        else:
            print("请登录!")
    return inner


def check_admin(func):
    def inner(*args, **kwargs):
        if USER_INFO.get('user_type', None) == 2:
            ret = func(*args, **kwargs)
            return ret
        else:
            print('无权查看')
    return inner


@check_login
@check_admin
def index():
    """
    管理员的功能
    :return:
    """
    print('Index')
In [ ]:
------------------------华丽的分割线-----------------------------


# 装饰器装饰也是有顺序的,代码是从上到下加载到内存中,
# 我们知道装饰器会把被 [@装饰器] 下面的函数装饰,并将它当作参数传递到装饰器里面.
# 装饰顺序结构(0),(1),(2);  (0)代表被装饰函数本体
@check_login
@check_admin
def index():
    """
    管理员的功能
    :return:
    """
    print('Index')



if USER_INFO.get('is_login', None):   # (2) check_login函数体
    if USER_INFO.get('user_type', None) == 2:  # (1) check_admin函数体
        ret = print('Index')  # (0) index函数本体
        return ret            # (0) index函数返回值
    else:
        print('无权查看')  # (1) check_admin函数体
else:
    print("请登录!")   # (2) check_login函数体

因此由上可看出,多个装饰器装饰一个函数,其实就是将这个函数层层嵌套到装饰器得函数体里面.

多装饰器的顺序:

  代码由上到下执行,加载到内存中,所有当一个函数上面有多个装饰器的时候,从上往下的顺序依次装饰函数,被装饰的函数则从被装饰的函数由下往上层层嵌套到装饰器中。

  1、最上面的装饰器是最外一层嵌套

  2、嵌套需要注意的是,最外层包裹内层,执行时是从上往下,考虑嵌套后代码执行顺序。

迭代器和生成器

迭代器 是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件

特点:

  • 访问者不需要关心迭代器内部的结构,仅需通过 next() 方法不断去取下一个内容
  • 不能随机访问集合中的某个值 ,只能从头到尾依次访问
  • 访问到一半时不能往回退
  • 便于循环比较大的数据集合,节省内存
In [68]:
a = iter([1,2,3,4,5])
a
a.__next__()
a.__next__()
a.__next__()
a.__next__()
a.__next__()

a.__next__() # 没有了,再迭代会报错
Out[68]:
<list_iterator at 0x10461de48>
Out[68]:
1
Out[68]:
2
Out[68]:
3
Out[68]:
4
Out[68]:
5
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-68-5d36a6bfb415> in <module>()
      7 a.__next__()
      8 
----> 9 a.__next__()

StopIteration: 

生成器

一个函数调用时返回一个迭代器,那这个函数就叫做 生成器(generator);如果函数中包含 yield 语法,那这个函数就会变成生成器;

In [70]:
def func():
    yield 1
    yield 2
    yield 3
    yield 4

上述代码中:func是函数称为生成器,当执行此函数func()时会得到一个迭代器。

In [72]:
temp = func()
temp.__next__()
temp.__next__()
temp.__next__()
temp.__next__()
Out[72]:
1
Out[72]:
2
Out[72]:
3
Out[72]:
4

Example:

In [74]:
def myrange(arg):
    start = 0
    while True:
        if start > arg:
            return
        yield start
        start += 1

ret = myrange(3)
ret.__next__()
ret.__next__()
ret.__next__()
ret.__next__()
Out[74]:
0
Out[74]:
1
Out[74]:
2
Out[74]:
3
In [75]:
# for 是一个自动迭代器,内部也是调的对象的 “__next__()” 方法
ret = myrange(3)
for item in ret:
    print(item)
0
1
2
3

递归

在函数体内重复调用自己本身,做到反复运行一个算法达到最终目的然后返回。

In [76]:
def func(n):
    n += 1
    if n >= 4:
        print(n)
        return "end"
    return func(n)

r = func(1)  # 我传递进去个1,经过递归最后给我返回4
print(r)
4
end
In [77]:
# 1*2*3*4*5*6*7
def func(num):
    if num == 1:
        return 1
    return num * func(num-1)   # 7 * 7-1 返回递归

x = func(7)
print(x)
5040