python ix函数_Python函数知识大杂烩(二)函数高级进阶

上一篇文章谈妖:Python函数知识大杂烩(一)定义及参数​zhuanlan.zhihu.com15721ac973bd3d4e70a6f1a9176a231a.png

回顾了Python函数的基础部分,本篇将提供进阶部分的总结。

返回值和作用域

另一个非常重要的概念是作用域。一般来讲,作用域是指一个变量起作用的范围。我们可以通过一个简单的案例来了解这个概念:

def func1():

x = 5

print(x)

def func2():

print(x)

>>>func1()

5

>>>func2()

Traceback (most recent call last):

File "", line 1, in

File "", line 2, in func2

NameError: name 'x' is not defined

在上个案例中,func1 函数可以正常返回值,而 func2 却返回错误信息。

原因就在于,变量x在函数func1内定义,因此它的作用域仅限于func1内部,func2调用时无法使用。

当我们在函数或类的内部定义变量时,变量只能在其内部使用,这就是所谓的局部作用域。当我们在整个程序的运行环境中定义变量,则在整个程序中都可以使用,这就是所谓的全局作用域。

在上例中,我们可以将func1中定义的变量x放到全局中进行定义,func2就不会出错。

接下来我们看另外一种情况:

def func3():

x=10

def func4():

x=15

print('func4 {}'.format(x))

func4()

print('func3 {}'.format(x))

>>> func3()

func4 15

func3 10

当一个函数内部还有另外一个函数时,我们将其称为嵌套函数。可以发现在嵌套函数内部,变量同样会受到作用域的限制,我们在func4中的定义的变量x并没有覆盖外层的x。

接下来我们再看一个案例:

x = 5

def func5():

x += 1

print(x)

>>> func5()

Traceback (most recent call last):

File "", line 1, in

File "", line 2, in func5

UnboundLocalError: local variable 'x' referenced before assignment

错误提示显示,本地变量 x 在被分配前即被引用。我们不是已经在全局定义了一个 x 吗?为什么此时显示还没有定义呢?

原因在于,在Python中,赋值即定义。我们在函数内部使用“=”时,相当于在本地作用域中定义了一个x,语句“x+=1”等价于“x=x+1”,此时等式右边的x在本地作用域还不存在,于是就出现了上述错误。

想要解决这种错误,就要引入两个新概念global和nonlocal。

global 和 nonlocal

global关键字将允许函数使用全局变量。

x = 5

def func6():

global x

x += 1

print(x)

>>> func6()

6

大部分时候我们不推荐使用global关键字。

nonlocal关键字允许函数使用上级作用域中的变量,但不能是全局变量。

def func7():

x = 5

def func8():

nonlocal x

x += 1

print(x)

func8()

print(x)

>>> func7()

6

6

上面的示例运行正常。

递归函数

当一个函数直接或间接调用自身的时候,就是一个递归函数。

现在,我们采用循环的方式生成一个10位的斐波那契数列:

per = 0

cur = 1

print(cur,end=',')

for i in range(9):

per,cur = cur,per+cur

print(cur,end=',')

>>>1,1,2,3,5,8,13,21,34,55,

如果用递归的方式:

def fib(n):

return 1 if n<2 else fib(n-1) + fib(n-2)

for i in range(10):

print(fib(i), end=',')

>>>1,1,2,3,5,8,13,21,34,55,

可以看到,递归的代码更简洁。

使用递归有几个注意事项:

1.递归函数必须有退出条件,无限递归就是死循环

2.递归深度不宜过深,否则栈溢出,python会抛出异常

3.一般来说递归性能会比较差

函数递归还有另外一种情况:间接递归。

举个简单的例子:

def first(n):

return second(n) if n>1 else 1

def second(n):

n -= 1

return first(n)

有时候,间接递归可以有效防止栈帧溢出。

匿名函数

除了递归之外,python还有一种可以大幅减少代码量的函数语法,就是匿名函数。

匿名函数使用 lambda 关键字作为标识。整个表达式由“lambda关键字”+“参数”+“表达式”组成。

在下面的示例中,两种写法完全等价。

lambda x,y:x+y

def add(x,y):

return x+y

匿名函数只能写在同一行,无法跨行。这种函数形式一般用于像高阶函数传参时使用。

生成器

有时候我们使用递归函数,每次只对一个结果进行操作,操作完成后才会对下一个结果进行操作。这种情况下,递归函数一次性生成所有结果会出现性能浪费。针对这种情况,我们可以使用生成器函数来解决。

生成器函数是指表达式中包含 yeild 关键字的函数,这种情况下函数不会一次性返回所有结果,而是会在每次调用的时候产生一个结果,直至求值结束,即所谓“惰性求值”。

看下面的示例:

def inc():

for i in range(5):

yield i

x = inc()

>>> next(x)

0

>>> next(x)

1

注意:直接调用 next(inc())每次都会返回0,原因在于这种形式相当于每次都在重新调用inc函数,生成新的对象。

yield 与 return 的不同在于:

return 会终止整个函数运行, yield 相当于是暂停函数运行。简单来说,一个函数只有一个 return 值,但却可以有多个 yield 。

当没有多余的 yield 语句可供执行之后,调用next会抛出StopIteration异常。

在 python3.3 版本之后,还有一种新的语法: yield from 。

示例如下:

def inc():

yield from range(5)

该示例与前一个示例等价。

当我们熟练掌握生成器函数之后,还可以借助它实现协程(coroutine),当然我们也可以使用 asyncio 标准库,在3.5之后还可以直接使用 async 和 await 关键字,这个我们以后再说。

高阶函数

Python的高级函数是指接受一个或多个函数作为参数,或者输出一个函数的函数。

示例:

def counter(base):

def inc(step =1):

nonlocal base

base += step

return base

return inc

a = counter(3)

a()

柯里化

柯里化是指将接受两个参数的函数修改为接受一个参数的过程。

示例:

def counter1(base,step=1):

base += step

return base

查看高阶函数中的示例。

counter 可以视为 counter1 柯里化之后的函数。

装饰器

正常情况下,业务函数是不建议修改的,简单修改很可能引发连锁反应。

那么,在业务函数无法修改,但又确实需要增强某些功能的时候,我们可以使用函数装饰器。

假设我们需要在函数调用时输出一些日志信息。

那么我们可以构造一个可以打印日志的高阶函数,调用时传入一个函数即可:

def logger(fn):

def wrapper(*args,**kwargs):

print('begin')

x = fn(*args,**kwargs)

print('end')

return x

return wrapper

以上就是一个装饰器的雏形。

调用时,传入一个函数,再传入实参就可以。

日常使用中,python提供一个语法糖:

@logger

def add(x,y):

return x+y

>>> add(3,4)

begin

end

7

使用时,直接调用add函数即可。

装饰器有一个副作用,会将原函数文档和属性进行替换,一般而言我们是不允许它进行变动的。

def logger(fn):

def wrapper(*args,**kwargs):

'''

wrapper

'''

print('begin')

x = fn(*args,**kwargs)

print('end')

return x

return wrapper

@logger

def add(x,y):

'''

add

'''

return x+y

>>> print('name:{},doc:{}'.format(add.__name__,add.__doc__))

name:wrapper,doc:

wrapper

可见add函数的属性已经被替换了。

解决方案并不难,我们可以在装饰器logger上再进行一层装饰,将原函数属性复制到装饰器属性就可以。

def copy_properties(src):

def _copy(dst):

dst.__name__ = src.__name__

dst.__doc__ = src.__doc__

return dst

return _copy

def logger(fn):

@copy_properties(fn)

def wrapper(*args,**kwargs):

'''

wrapper

'''

print('begin')

x = fn(*args,**kwargs)

print('end')

return x

return wrapper

@logger

def add(x,y):

'''

add

'''

return x+y

>>> print('name:{},doc:{}'.format(add.__name__,add.__doc__))

name:add,doc:

add

我们的示例实现的功能,其实可以通过functools模块内的functools.update_wrapper和functools.wraps实现(详情请查看源码)。

Python函数总结完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值