Python3语法重点难点——装饰器,*args和**kwargs

递归函数/尾递归优化

如果一个函数在内部调用自身本身,这个函数就是递归函数
例如 n! 阶乘:

def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)  # 此处return语句是一个表达式,即乘法表达式

如果我们计算fact(5),可以根据函数定义看到计算过程如下:

===> fact(5)
===> 5 * fact(4)
===> 5 * (4 * fact(3))
===> 5 * (4 * (3 * fact(2)))
===> 5 * (4 * (3 * (2 * fact(1))))
===> 5 * (4 * (3 * (2 * 1)))
===> 5 * (4 * (3 * 2))
===> 5 * (4 * 6)
===> 5 * 24
===> 120

递归函数的优点是定义简单,逻辑清晰;缺点是过深的调用会导致栈溢出
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

尾递归优化

尾递归是指在函数返回的时候,调用自身本身,并且return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

def fact(n):
    return fact_iter(n, 1)    # 初始product = 1,即乘以任何数都是其本身

def fact_iter(num, product):
    if num == 1:
        return product
    return fact_iter(num - 1, num * product)

可以看到,return fact_iter(num - 1, num * product)仅返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。
fact(5)对应的fact_iter(5, 1)的调用如下:

===> fact_iter(5, 1)
===> fact_iter(4, 5)
===> fact_iter(3, 20)
===> fact_iter(2, 60)
===> fact_iter(1, 120)
===> 120

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。但是,Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题

【练习汉诺塔移动】

有三个立柱A、B、C。A柱上穿有大小不等的圆盘N个,较大的圆盘在下,较小的圆盘在上。要求把A柱上的圆盘全部移到C柱上,保持大盘在下、小盘在上的规律(可借助B柱)。每次移动只能把一个柱子最上面的圆盘移到另一个柱子的最上面。请输出移动过程。

# 利用递归函数移动汉诺塔
def Hanoi(n, a, b, c):
    if n == 1:
        print('move', a, '-->', c)
    else:
        Hanoi(n-1, a, c, b)
        Hanoi(1, a, b, c)
        Hanoi(n-1, b, a, c)
# 执行函数
Hanoi(3, 'A', 'B', 'C')

输出结果为:

move A --> C
move A --> B
move C --> B
move A --> C
move B --> A
move B --> C
move A --> C

【解答原理】汉诺塔问题,可以通过三步实现:

  1. 将A柱最上面的n-1个圆盘移动到B柱(借助C柱)
  2. 将A柱上剩下的那1个圆盘直接移动到C柱
  3. 将B柱上的n-1个圆盘移动到C柱(借助A柱)

如下图所示(from柱即A柱, by柱即B柱, to柱即C柱):
在这里插入图片描述
参考文章:
递归函数
python汉诺塔

生成器

在Python中一边循环一边计算的机制称为生成器(generator)。
把一个列表生成式的[]改成(),就创建了一个generator,然后通过for循环来迭代它。
斐波拉契数列: 1, 1, 2, 3, 5, 8, 13, 21, 34, …

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b   # yield关键字,表示是一个generator
        a, b = b, a + b
        n = n + 1
    return 'done'
# 调用
for i in fib(6):
    print(i)

变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

g = fib(6)
while True:
    try:
        x = next(g)
        print('g:', x)
    except StopIteration as e:
        print('Generator return value:', e.value)
        break

输出结果

g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

装饰器

参考:Python 函数装饰器
Python装饰器
简单地说,装饰器是修改其他函数的功能的函数。
举例:
下面是没有装饰器的代码:

def a_new_decorator(a_func):
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction

def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()

a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
#        I am the function which needs some decoration to remove my foul smell
#        I am doing some boring work after executing a_func()

python装饰器,即封装一个函数并修改它的行为。下面是改装后的python解释器代码:

from functools import wraps

def a_new_decorator(a_func):
    @wraps(a_func)
    def wrapTheFunction():
        print("I am doing some boring work before executing a_func()")
        a_func()
        print("I am doing some boring work after executing a_func()")
    return wrapTheFunction

@a_new_decorator    # 装饰器
def a_function_requiring_decoration():
    print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration() 
# 作用等同于a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#outputs: I am doing some boring work before executing a_func()
#         I am the function which needs some decoration to remove my foul smell
#         I am doing some boring work after executing a_func()

再举一个案例:定义一个打印出方法名及其参数的装饰器。

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('call %s():' % func.__name__)
        print('args = {}'.format(*args))
        return func(*args, **kwargs)
    return wrapper

调用装饰器

@log
def test(p):
    print(test.__name__ + " param: " + p)
    
test("I'm a param")
# 输出
# call test():
# args = I'm a param
# test param: I'm a param

值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。

装饰器功能:

  1. 引入日志
  2. 函数执行时间统计
  3. 执行函数前预备处理
  4. 执行函数后清理功能
  5. 权限校验等场景
  6. 缓存

*args和**kwargs

参考:Python中的*args和**kwargs
*args 就是传递一个可变参数列表给函数实参,这个参数列表的数目未知,甚至长度可以为0。
例如:

def test_args(first, *args):    # 第一个参数是必须要传入的参数,所以使用了第一个形参,后面几个是可变参数列表
    print('Required argument: ', first)
    print(type(args))
    for v in args:
        print ('Optional argument: ', v)

test_args(1, 2, 3, 4)

输出:
Required argument: 1
<class ‘tuple’>
Optional argument: 2
Optional argument: 3
Optional argument: 4

**kwargs是将一个可变的关键字参数的字典传给函数实参,同样参数列表长度可以为0或为其他值。
例如:

def test_kwargs(first, *args, **kwargs):
    print('Required argument: ', first)
    print(type(kwargs))
    for v in args:
        print ('Optional argument (args): ', v)
    for k, v in kwargs.items():
        print ('Optional argument %s (kwargs): %s' % (k, v))

test_kwargs(1, 2, 3, 4, k1=5, k2=6)

输出:
Required argument: 1
<class ‘dict’>
Optional argument (args): 2
Optional argument (args): 3
Optional argument (args): 4
Optional argument k1 (kwargs): 5
Optional argument k2 (kwargs): 6

args类型是一个tuple,而kwargs则是一个字典dict,并且args只能位于kwargs的前面。

常用的一些语法功能

%load_ext autoreload

设置修改的模块自动重新载入

%load_ext autoreload
%autoreload 2

sys.path.append()用法

参考:关于sys.path.append()
当我们导入一个模块时:import xxx,默认情况下python解析器会搜索当前目录、已安装的内置模块和第三方模块,搜索路径存放在sys模块的path中,可通过sys.path 查看。
对于模块和自己写的脚本不在同一个目录下,在脚本开头加sys.path.append(‘xxx’):

import sys  
sys.path.append(’引用模块的地址')  

把路径添加到系统的环境变量,或把该路径的文件夹放进已经添加到系统环境变量的路径内。环境变量的内容会自动添加到模块搜索路径中。可以通过dir(sys)来查看他里面的方法和成员属性。

os.listdir() 方法

os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。

import os, sys
print(os.listdir('查看的文件夹路径'))

os.chdir()

os.chdir() 方法用于改变当前工作目录到指定的路径。

os.chdir(path) # path为要切换到的新路径
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值