Python函数

Python函数

5.1什么是函数

Python中函数的定义:函数是逻辑结构化和过程化的编程方法。

5.1.1 定义函数

内建函数安装完Python源码就能够直接使用,是系统自带的。

除了内建函数以外,我们还可以根据需要创建新的自定义函数。因为系统自带的函数数量是有限的,功能也是有限的。想要通过函数做更多事情,就只能自己设计符合需求的函数。

def square_sum(a,b):
    a = a**2
    b = b**2
    c = a+b
    return a,b,c
square_sum(3,4)
//(9,16,25)

如果return后面没有返回值,则函数将返回None。None是Python中的空数据,用来表示什么都没有。

5.1.2 实参和形参

形参变量只有在函数内部有效,函数调用结束返回主调函数后不能继续使用该形参。实参可以是常量、变量、表达式或函数等,在进行函数调用时,它们必须要有确定的值,以便把值传给形参。

5.1.3 返回值

要想把结果返回值传给上一层的调用者,需要在函数中使用return关键字。

函数运行完毕后得到了一个返回值,这个返回值可能通过变量形式保存下来,也就是说函数的返回值是可以传递给变量的。

总结,函数可以返回执行结果值。函数的返回值有三类:返回单个值、返回None、返回多个值。

5.2 实参与形参之间的传递方式

函数传递实参种类很多,比如位置实参,这要求实参的顺序与形参的顺序相同;也可以使用关键字实参,它要求每个实参都由变量名和值组成;甚至还可以使用列表和字典等。

5.2.1 位置实参

调用函数时,Python必须将函数执行时用到的每个实参都映射到函数定义中的每个形参。为此,最简单的关联方式是基于顺序的传参,而这种关联方式被称为位置实参。

5.2.2 关键字实参

关键字实参是非常直观地传递数值的方式,它把形参的值和实参的值通过=关联起来,这种传参方式的优点是避免了参数混淆,不会因为顺序的改变而出错,而且还清楚地指出函数调用中各个值的用途。

def describe_pet(animal_type,pet_name):
    print("\nI have a "+animal_type+".")
    print("My"+animal_type+"'s name is "+pet_name.title()+".")
describe_pet(animal_type='mouse',pet_name='jerry')
//I have a mouse.
//My mouse's name is Jerry.

5.2.3 默认参数

如果我们忘记输入实参或者我们根本没有实参来输入,函数会抛出异常,为避免这种情况发生,Python允许为参数设置默认值,即在定义函数时,直接给形式参数指定一个默认值,这样的话,即便调用函数时没有给拥有默认值的形参传递参数,该函数可以直接使用定义函数时设置的默认值。

def book(name="Python",message="从小白到大牛"):
    print("语言:",name)
    print("书名是:",name+message)
book()
//语言:Python
//书名是:Python从小白到大牛

由于Python要求在调用函数时关键字参数必须位于位置参数的后面,因此在定义函数时指定了默认值的参数(关键字参数)必须在没有默认值的参数之后。默认参数和关键字看起来很像,但两者有明显的区别,默认参数是在形参定义的时候指定的,而关键字参数是在实参定义时指定的。

5.2.4 可变参数

可变参数是指传入的参数个数是可变的,可以是1个、2个、3个等,甚至还可以是0个,适用于一个函数不能确定其输入参数个数的场景。

def get_remainder(*args):
    product = 1
    for arg in args:
        product *= int(arg)
    return product % 20
result = get_remainder(1,2,3,4)
print(result)
//4

参数前面加*号就表示这是一个可变长参数,调用 get_remainder时,只需要将参与计算的数据传入函数中,并且用逗号隔开即可,既灵活又方便。

接下来说两个星号的可变长参数——* * kwargs。两者的区别在于两个星号表示接受键值对的动态参数,数量任意。目前只有字典才包含键值对, * *kwargs允许将字典作为参数传递给函数内部。

def test_args_kwargs(arg1,arg2,arg3):
    print("arg1:",arg1)
    print("arg2:",arg2)
    print("arg3:",arg3)
args = ("two",3,5)
test_args_kwargs(* args)
//arg1:"two"  arg2:3   arg3:5
kwargs = {"arg3":3,"arg2":"two","arg1":5}
test_args_kwargs(* * kwargs)
//arg1:5 arg2:two arg3:3

*args和 * * kwargs 组合起来就可以传入任意形式的参数,这在参数未知的情况下是很有效的,同时加强了函数的可拓展性。注意,加强拓展性的同时也就意味着降低了稳定性,所以使用之前一定要充分了解代码。

5.3 局部变量和全局变量

函数参数的作用范围是有限的,这个范围称为作用域。参数作用域在函数内部,而函数与外部沟通的桥梁则为变量,外部变量和函数通过赋值来传递数据。

数据在函数内外以不同的形式流动,这个时候就引出了全局变量和局部变量的概念。全局变量与局部变量两者的本质区别就在于作用域,如果一个变量既能在一个函数内使用也能在其他函数内使用,这样的变量就是全局变量。全局变量就是在模块内部和函数的外部都存在,且所有的函数都可以访问。注意,函数内部可以访问但不能直接赋值。

a=100
def test1():
    print(a)
def test2():
    print(a)
test1()
//100
test2()
//100

局部变量与全局变量不同,它是定义在函数内部,且只能在函数内部存在,函数的形参就是局部变量的一种类型。

a=100
b=200
def fx(c):
    d=300
    print(a,b,c,d)
fx(300)
//100,200,300,300

在函数内首次对变量赋值是创建局部变量,再次为变量赋值是修改局部变量的绑定关系,无论怎样修改,函数内部的赋值语句都不会对全局变量造成影响。

5.4 递归函数

编程语言中,函数直接或间接调用函数本身,则该函数称为递归函数。

def fact(n):
    if n == 1:
        return 1
    return n * fact(n-1)
fact(10)
//3628800

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。使用递归函数需要注意防止栈溢出。函数调用是通过栈这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致内存溢出。

解决递归调用内存溢出的方法是通过尾递归优化。尾递归是指在函数返回的时候调用自身本身而且return语句不能包含表达式。编译器或解释器会把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,避免出现栈溢出的情况。事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

def fact(n):
    return fact_iter(n,1)
def fact_iter(num,product):
    if num == 1:
        return product
    return fact_iter(num-1,num * product)
fact_iter(5,1)
//120

针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。Python的标准解释器没有针对尾递归做优化,任何递归函数都存在内存溢出的问题。

5.5 闭包

如果B函数定义在A函数的作用域中,且B函数中引用了A函数的局部变量,那么这个函数就是一个闭包。

这类似于普通模块函数和模块中定义的全局变量的关系,修改外部变量能影响内部作用域中的值,而在内部作用域中定义同名变量则将遮蔽(隐藏)外部变量。如果需要在函数中修改全局变量,可以使用关键字global修饰变量名。nonlocal可以在闭包中修改外部变量。

def f():
    n = 1
    def inner():
        nonlocal n
        n = 'x'
    print(n)
    inner()
print(n)
f()
//1  x

5.6 函数与函数式编程

函数是程序的一种封装方式,把大段代码拆成函数后,通过一层一层地调用,就把复杂流程分解成简单的小任务,这种分解方式称之为面向过程的程序设计。函数就是面向过程程序设计的基本单元。函数式编程,虽然也可以归结为面向过程的程序设计,但其思想更接近数学计算。

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们就认为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此这种函数是有副作用。函数式编程使用一系列的函数解决问题。函数仅接受输入并产生输出,不包含任何能影响产生输出的内部状态。任何情况下,使用相同的参数调用函数始终能产生相同的结果。在一个函数式程序中,输入的数据流过一系列的函数,每一个函数根据它的输入产生输出。函数式风格避免编写有边界效应的函数,完全没有边界效应的函数被称为纯函数式的。避免边界效应意味着不使用在程序运行时可变的数据结构,输出只依赖于输入。

函数式编程刚好站在了面向对象编程的对立面,面向对象编程通常包含内部状态,和许多能修改这些状态的函数,程序则由不断修改状态构成;函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作。但这并不是说无法同时使用函数式编程和面向对象编程,事实上,复杂的系统一般会采用面向对象技术建模,但混合使用函数式风格还能让我们额外享受函数式风格的优点。

1.逻辑可证,这是一个学术上的优点,没有边界效应使得更容易从逻辑上证明程序是正确的,而不是通过测试。

2.模块化,函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块,小的函数更易于阅读和检查错误。

3.组件化,函数更容易加以组合形成新的功能。

4.易于调试,细化的、定义清晰的函数使得调试更加简单。当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方。

5.易于测试,不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易。

6.更高的生产率,函数式编程产生的代码比其他技术更少,并且更容易阅读和维护。

Python不是且也不大可能会成为一种函数式编程语言,但它支持许多有价值的函数式编程语言构建方式,也有些表现得像函数式编程机制,但传统意义上不能被认为是函数式编程语言的构建方式。其内容包括高阶函数、返回函数、匿名函数、装饰器和偏函数等。高阶函数从字面上理解,除了有普通函数功能还是高级内容,即可以把函数作为参数或返回值的函数,从这个角度来说增强了函数处理能力。它定义为可以接收函数为参数或返回一个函数作为参数的函数。返回函数是指高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

匿名函数即lambda表达式函数,经常用作函数参数或生成表达式的场景中,闭包里面的函数可以看成是匿名函数来理解。

Python装饰器就是用于拓展原来函数功能的一种函数,目的是在不改变原函数名的情况下,给函数增加新的功能。这个函数的特殊之处在于它的返回值也是一个函数,这个函数是内嵌原函数的函数。

偏函数是将所要承载的函数作为partial()函数的第一个参数,原函数的各个参数依次作为partial()函数后续的参数,除非使用关键字参数。

Python允许lambda关键字创造匿名函数。匿名是因为不需要以标准的方式来声明,比如def关键字。然而作为函数,它们也能有参数。一个完整的lambda语句代表了一个表达式,这个表达式的定义体必须和声明放在同一行。

lambda_add=lambda x,y:x+y
lambda_add(2,3)
//5

匿名参数的参数是可选的,如果使用参数,参数通常也是表达式的一部分。它与使用def定义的函数完全一样,可以使用lambda_add作为函数名进行调用。lambda的设计目的是为了编写偶尔为之的、简单的、可预见不会修改的匿名函数。

Python的高阶函数就是指一个函数作为参数传递给另外一个函数的用法。

def add(x,y,f):
    return f(x)+f(y)
add(1,-2,abs)
//3

把函数作为参数传递,能够使编码传参上更具有灵活性,我们可以根据某些变量的不同,传入不同的函数进去,这样代码更简洁更好理解,不需要再重新写一大堆代码。

在进行短小操作,如获取排序结果时,使用lambda非常方便。但如果lambda的内容超过一行,那么使用普通的函数定义可能更好。通常传递函数可以避免重复,但在使用时要经常提醒自己,额外的结构是否会让代码清晰度下降。通常,将其分解成更小的辅助函数会更清晰。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值