【Python3_基础系列_015】Python3-闭包与装饰器函数

 一、闭包

闭包(closure)是函数式编程的重要的语法结构,python中的闭包是必须要理解的概念,否则在后面讲解到装饰器的时候会一脸懵逼。这里我不打算对这个概念进行基础的分析。可以参考这个文章查看基本的概念:https://www.cnblogs.com/JohnABC/p/4076855.html

我们在这里简单提一下闭包的特性和如何创建闭包:

首先是维基百科中关于闭包的概念:
在一些语言中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则可能产生闭包。闭包可以用来在一个函数与一组“私有”变量之间创建关联关系。在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。

根据这句话,其实我们自己就可以总结出在python语言中形成闭包的三个条件,缺一不可:
1)必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
2)内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
3)外部函数必须返回内嵌函数——必须返回那个内部函数

在创建一个闭包函数之前,我们先简单的理解一下函数的概念:

#如何定义函数

def f1(**args):
    f1 body
    return value

我们先这样理解一个函数:函数就是f1()返回了一个结果值。
参考函数的定义,我们可以思考如果函数返回的不是一个值(python的基本数据类型),而是另外一个函数呢,那么函数的定义可以如下:
#函数f1()返回一个函数f2

def f1():
    f1 body
    return f2

#由于f2是一个函数,那么必须进行定义,而且必须要在f1()函数的内部定义f2()。那么此时的函数结构:

def f1():
    def f2()
        f2 body
        return value
    return f2   

#更一般的情况,f1是需要参数和变量的。由函数变量的作用域我们可以知道,函数内部可以访问函数外部的变量。那么可以定义这个函数如下:下面就是闭包函数的一般形式。
def f1(x):
    def f2()
        x
        f1 body
        return value
    return f2   

用法参考:

def greeting(time):
    def greet(name):
        print("good {0}, {1}!".format(time, name))
    return greet


# func = greeting("afternoon")
# func("big cat")

greeting("afternoon")("big cat")

通过上面的例子,对比闭包函数的一般形式,可以很方便的理解闭包的概念。

二、装饰器函数

由闭包我们可以知道了,闭包的特点:
1.f1()内嵌f2()
2.f1()返回f2()
3.f2()引用f1()的变量或者参数

def f1(x):
    def f2()
        x
        f2 body
        return value
    return f2

继续思考,f1(x)的参数我们使用的是变量(基本数据类型),但是如果f1()的参数是一个函数呢?形式如下:

def f1(f3):
    def f2()

    return f2 

闭包的时候f2()要去引用f1()的参数或者变量,那么在f1的参数为函数f3的情况下为了实现闭包,f2()中就必须引用或者返回f3.那么形式就是如下:

def f1(f3):
    def f2()
        f3调用或者返回
    return f2

其实这种形式就是函数装饰器的构成,为了理解方便,我们可以把函数的装饰器函数的一般形式定义如下:

###装饰器函数的一般形式####
def f1(f2)
    def f3()
           f2调用或者参数或者直接返回
    return f3

特点:
1.f1(f2)内嵌f3
2.f1(f2)返回f3
3.f3中调用或者返回f2

装饰器的作用:

装饰器的作用就是为已经存在的对象添加额外的功能。
常用场景:插入日志、性能测试、事务处理、缓存、权限校验等

 

装饰器函数的例子:
定义四则运算器函数,并且对参数进行验证

#4则运算计算器,装饰器函数是valid函数。valid(func)==f1(f2()),f3=cacl
def valid(func):
    """
    :param a:
    :param b:
    :return:
    """
    def calc(a, b):
        try:
            _a = float(a)
        except ValueError as error:
            print(error)
            _a = 0
        try:
            _b = float(b)
        except ValueError as error:
            print(error)
            _b = 0
        print(func(_a, _b))
    return calc

    # def add(a, b):
    #     return a + b
    # return add(_a, _b)

@valid
def add(a, b):
    return a + b


@valid
def sub(a, b):
    return a - b


@valid
def mul(a, b):
    return a * b


@valid
def div(a, b):
    try:
        return a / b
    except ZeroDivisionError as error:
        print(error)
        return 0

三、面试题

1.简单介绍下闭包和装饰器

1.闭包的定义:
闭包函数必须有内嵌函数 内嵌函数需要引用嵌套函数的变量 闭包函数必须返回内嵌函数

2.装饰器函数:

装饰器(deco):装饰函数的参数是被装饰的函数对象,返回原函数对象
装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。
(函数装饰器)
@fu1 #装饰器 #语法糖 deco = fu1(deco)
def deco(m):
print('这是deco')
return m*m #这里通过装饰器的作用,实现啦给deco的返回值加 1 的功能


def test(): #@fu1对这个函数没有影响
pass


闭包中的变量换成函数即可
###装饰器函数的一般形式####
def f1(f2)
    def f3()
           f2调用或者参数或者直接返回
    return f3

特点:
1.f1(f2)内嵌f3
2.f1(f2)返回f3
3.f3中调用或者返回f2

2.写一个装饰器用于统计任意函数的运行时间

import time  #自带的时间模块
def run_time(func):
    def new_fun(*args,**kwargs):
        t0 = time.time()
        print('star time: %s'%(time.strftime('%x',time.localtime())) )
        back = func(*args,**kwargs)
        print('end time: %s'%(time.strftime('%x',time.localtime())) )
        print('run time: %s'%(time.time() - t0))
        return back
    return new_fun

@run_time
def test():
    for z in range(10):
        for i in range(1,10):
            for j in range(1,i+1):
                print('%dx%d=%2s'%(j,i,i*j),end = ' ')
            print ()

test()###打印10遍99乘法表

输出:
****
end time: 07/15/18

run time: 0.0020055770874023438
 

下面几个面试题仅用于记录作用,非装饰器面试题

3.任意输入日期,输出是当年的第几天

try:
    year = int(input('请输入年:'))
except ValueError as error:
    print(error)
    print('亲输入正确的日期')
try:
    month = int(input('请输入月:'))
except ValueError as error:
    print(error)
    print('亲输入正确的日期')
try:
    day = int(input('请输入日:'))
except ValueError as error:
    print(error)
    print('亲输入正确的日期')

is_run_year = year % 400 == 0 or (year % 4 == 0 and year % 100 != 0)

day_in_month =[0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
if day > day_in_month[month]:
    print("本月没有这么多天")
if is_run_year:
    day_in_month[2] = 29

print(sum(day_in_month[:month]) + day)

输出:

请输入年:2018
请输入月:7
请输入日:15
196

4.解密字符串

明文

密文

abc

bcd

wxy

xyz

请问这里明文是什么

cfmjfwf!zpvstfmg"

 

 

 

 

 

 

miwen = 'cfmjfwf!zpvstfmg"'
y=''
for x in miwen:
    y += chr(ord(x)-1)
print(y)

输出:believe yourself!

有几个函数需要注意:
len(li) #长度
min(li) #求最小
max(li) #求最大
sorted(li,key=len,reverse)  #排序.key 安装是什么方式来排序  int len str float lambda
sorted(la)
list(reversed(li))  #反向
sum(li)  #求和
sum(li,2) 
#进制转换函数
bin(12)  #转成二进制
oct(8) #转成八进制
hex(16) #转成十六进制
ord('a') #转成ASCII
chr(97) #ascii转成字符

5.福彩3D彩票中奖程序

已知福彩3D的规则如下,编写简单的程序实现:

import random

# for _ in range(10):
#     print("{0:03d}".format(random.randint(0, 999)))

def zhixuan(ticket, number):
    if ticket == number:
        print("恭喜您中将,奖金为1000元人民币!")
    else:
        print("一次多买几张,增加中将概率!")

def zuxuan3(ticket, number):
    ticket, number  = set(ticket), set(number)
    if ticket == number  and len(ticket) == 2 \
    and ticket.count(ticket.pop()) == number.count(number.pop()) \
    and ticket.count(ticket.pop()) == number.count(number.pop()):
        print("恭喜您中将,奖金为320元人民币!")
    else:
        print("一次多买几张,增加中将概率!")

def zuxuan6(ticket, number):
    if set(ticket) == set(number):
        print("恭喜您中将,奖金为160元人民币!")
    else:
        print("一次多买几张,增加中将概率!")

while True:
    play = int(input("请输入您的玩法:"))
    ticket = input("请输入您要购买的号码:")
    number = "{0:03d}".format(random.randint(0, 999))
    if play == 1:
        zhixuan(ticket, number)
    elif play == 2:
        zuxuan3(ticket, number)
    elif play == 3:
        zuxuan6(ticket, number)
    else:
        print("请选择正确的玩法!")
    print("本期中奖号码是{0}".format(number))

6.地铁金额计算程序

已知地铁的计费规则和各站点的距离,编写程序求出任意站点乘坐时的金额。

distance = [2839, 1206, 1829, 4866, 2538, 3623, 1423, 2110, 4785, 2272, 6720, 2152, 1110, 1135, 1769]

station = {
    "xizhimen": 0,
    "dazhongsi": 1,
    "zhicuhnlu": 2,
    "wudaokou": 3,
    "shangdi": 4,
    "xierqi": 5,
    "longze": 6,
    "huilongguan": 7,
    "huoying": 8,
    "lishuiqiao": 9,
    "beiyuan": 10,
    "wangjingxi": 11,
    "shaoyaoju": 12,
    "guangximen": 13,
    "liufang": 14,
    "dongzhimen": 15
}
start = input("请输入起始站:")
end = input("请输入目的地:")

if station[start] > station[end]:
    start, end = end, start

print(distance[station[start]: station[end]])
juli = sum(distance[station[start]: station[end]]) / 1000

if juli <= 6:
    print("3元")
elif juli <= 12:
    print("4元")
elif juli <= 22:
    print("5元")
elif juli <= 32:
    print("6元")
else:
    price = int((juli - 32) / 20) + 6
    print("{0}元".format(price))

输出:
请输入起始站:beiyuan
请输入目的地:liufang
[6720, 2152, 1110, 1135]
4元

7.青蛙跳台的问题全解

青蛙跳台问题的问题在之前的文章中已经写了,就是递归函数实现费布那齐数列,但是假设题目就是:一只青蛙1次可以跳1节台阶也可以一次跳2节台阶,那么这只青蛙跳上一个N级台阶的可能跳法有多少种?

虽然这个费布那齐数列的实现很简单,但是假设这个题目是10分的话,仅仅写出基本的递归函数,可能只得3分。下面解释一下这个面试题的全部步骤:

1.解释跳台的思路以及如何求出一般N的公式---2分
>如果N=1,那么f(1)=1.

>如果N=2,那么可以跳1阶,也可以跳2阶,f(2)=1+1=2

...

>如果N,那么青蛙可以从N-1跳+N-2跳2种可能,所以分f(n)=f(n-1)+f(n-2)

2.递归函数的书写---3分

def fibnacci(n):
    if n<=0:
        return 0;
    elif n==1 or n==2:
        return 1;
    else:
        return fibnacci(n-1)+fibnacci(n-2)

3.递归函数中异常处理----3分

不是简单的实现递归函数即可,实际的开发中对于程序的异常处理尤为重要,考虑和处理异常情况是作为高级程序员的必备素养。因此在上面的递归函数中,一般要加入大量的异常处理,try except等

4.递归与非递归的区别--2分

递归是解决这个问题最容易想到的解决方案但是递归由于每次都要创建大量的对象,因此对于内存的使用(栈)很高,递归的效率很低。如果是非递归该如何实现以及效率分析。

非递归的实现:

a=0
b=1
while b < 1000:
    print(b)
    a, b = b, a+b

 

因此对于这个问题的满分10分的简单需求是需要全面考虑原理,代码实现,异常处理,效率以及替代解决方案的。希望每一个python人都具备这种解决问题的思路。

 

posted @ 2018-07-15 15:03  爱寂寞撒的谎言  阅读(188)  评论(0编辑  收藏  举报