聪哥哥教你学Python之函数

什么是函数?

从数学的角度分析:

函数的定义:给定一个数集A,假设其中的元素为x。现对A中的元素x施加对应法则f,记作f(x),得到另一数集B。假设B中的元素为y。则y与x之间的等量关系可以用y=f(x)表示。我们把这个关系式就叫函数关系式,简称函数。函数概念含有三个要素:定义域A、值域C和对应法则f。其中核心是对应法则f,它是函数关系的本质特征。

或许会人问我,亲爱的聪哥哥你是讲数学吗?数学的函数似乎与我们编程中的函数不一样。

我没有再讲数学,但是你可以从平时的函数联系到数学。记得某位大师说过一句话,技术的核心,本质上就是数学。

其实你可以想一想,我们平时写代码封装的对应的函数,在某种意义上,是按照一种关系。这种关系本质就是数学。

还记得在高中的时候做数学题目吗?

做题目除了看题仔细分析外,顺便会用某种关系式太套。而且有的时候关系式并不适用。像我曾经做的一个题目,写了一大堆,虽然最后12分的题目,全部拿到分数了,但是我却在这个12分花费大量的时间,以至于相对容易的几何题目和一些其他大题,虽说不一定分数全部拿到,至少一半应该没问题。最让我气人的是,那个12分,我写的很满,到时,看到我的同桌居然非常的简洁就拿到了12分,当时心中十分气愤,顿时有种非常不爽的感觉。不过从中看出数学的简洁之美。好了,扯了会蛋,该进入正题了。

各位奋战在一线的开发者们,为什么写函数,我相信你们都明白,有很多时候,许多重复代码段,我们只需编写一个对应的函数即可解决代码冗余的问题,使代码变得优雅,变得可扩展性好。这个函数的表达,其实我们也在使用一定的关系式(函数式)来概括。

一、调用函数

Python内置很多函数,方便我们根据对应的需求来调用,即便有些函数没有内置,我们可以使用对应的pip来安装。

绝对值函数:abs()

返回最大值:max()

数据类型转换函数:int()、float()、str()、bool()等

在此,聪哥哥有话说:

调用Python的函数,需要根据函数定义,传入正确的参数。如果函数调用出错,一定要学会看错误信息,所以英文很重要。

二、定义函数

在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。

返回单个值:

# -*- coding: utf-8 -*-
def my_abs(x):
	if x >= 0:
		return x
	else:
		return -x
		
print(my_abs(-99))

请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。

如果没有return语句,函数执行完毕后也会返回结果,只是结果为Nonereturn None可以简写为return

返回多个值:

# -*- coding: utf-8 -*-
import math
def move(x,y,step,angle=0):
	nx = x + step * math.cos(angle)
	ny = y - step * math.sin(angle)
	return nx,ny
r=move(100,100,60,math.pi/6)
print(r)

聪哥哥有话说:

定义函数时,需要确定函数名和参数个数;

如果有必要,可以先对参数的数据类型做检查;

函数体内部可以用return随时返回函数结果;

函数执行完毕也没有return语句时,自动return None

函数可以同时返回多个值,但其实就是一个tuple。

三、函数的参数

定义函数的时候,我们把参数的名字和位置确定下来,函数的接口定义就完成了。对于函数的调用者来说,只需要知道如何传递正确的参数,以及函数将返回什么样的值就够了,函数内部的复杂逻辑被封装起来,调用者无需了解。

Python的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码。

参数有很多种:

比如有位置参数、默认参数、可变参数、关键字参数、命名关键字参数、参数组合等等。

(1)位置参数

示例代码:

def power(x):
    return x * x
print(power(5))

(2)默认参数

示例代码:

def power(x, n=2):
    s = 1
    while n > 0:
        n = n - 1
        s = s * x
    return s
print(power(5))

(3)可变参数

示例代码:

def calc(*numbers):
    sum = 0
    for n in numbers:
        sum = sum + n * n
    return sum

print(calc(1,2))

(4)关键字参数

示例代码:

def person(name, age, **kw):
    print('name:', name, 'age:', age, 'other:', kw)

print(person('聪哥哥', 18, city='Beijing'))

(5)命名关键字参数

示例代码:

def person(name, age, **kw):
    if 'city' in kw:
        # 有city参数
        pass
    if 'job' in kw:
        # 有job参数
        pass
    print('name:', name, 'age:', age, 'other:', kw)

print(person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456))

(6)参数组合

示例代码:

def f1(a, b, c=0, *args, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)

def f2(a, b, c=0, *, d, **kw):
    print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)

print(f1(1, 2))
print(f2(1, 2, d=99, ext=None))

聪哥哥有话说:

(1)Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。=;

(2)默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误;

(3)要注意定义可变参数和关键字参数的语法:

(4)*args是可变参数,args接收的是一个tuple;

(5)**kw是关键字参数,kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

四、递归函数

什么叫递归?

引用百度百科的回答:

程序调用自身的编程技巧称为递归( recursion)。递归作为一种算法程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。

示例代码:

# -*- coding: utf-8 -*-
def fact(n):
    if n==1:
        return 1
    return n * fact(n - 1)
	
print(fact(10))

递归函数的优点是定义简单,逻辑清晰。理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

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

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

聪哥哥怎么看:

(1)使用递归函数的优点是逻辑简单清晰,缺点是过深的调用会导致栈溢出。

(2)针对尾递归优化的语言可以通过尾递归防止栈溢出。尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。

(3)Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

猜你喜欢

转载自blog.csdn.net/JavaAndchief/article/details/83687903