美文网首页
【Python 学习记录】三、函数

【Python 学习记录】三、函数

作者: 佳奥 | 来源:发表于2022-08-19 10:24 被阅读0次

这里是佳奥!开始学习python中内置的各种函数吧。

1 调用函数

比如求绝对值的函数abs,该函数有且仅有1个参数:

abs(-20)

如果传入的参数数量是对的,但参数类型不能被函数所接受:str是错误的参数类型

abs('s')
TypeError: bad operand type for abs(): 'str'

max函数max()可以接收任意多个参数,并返回最大的那个:

max(1,2,3,4,-5)
4

数据类型转换

Python内置的数据类型转换函数,int()函数可以把其他数据类型转换为整数:

(就像R语言中的as.numeric())

##int 
int('123')
123

int(123)
123

float('12.22')
12.22

str(100)
100 
##在VSCode内并不显示'100',但如果str(100)+100会显示
##TypeError: can only concatenate str (not "int") to str

bool(1)
True

bool('')
False

把函数名赋给一个变量,相当于给这个函数起了一个“别名”:

a=abs
a(-1)
1

练习

请利用Python内置的hex()函数把一个整数转换成十六进制表示的字符串:

n1 = 255
n2 = 1000

print(hex(n1))
0xff
print(hex(n2))
0x3e8

2 定义函数

自建函数。

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

我们以自定义一个求绝对值的my_abs函数为例:

def my_abs(x):
    if x >= 0:
        return x
    else:
        return -x

print(my_abs(-20))
20

函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。

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

def my_abs(x):
    if x >= 0:
        return 
    else:
        return -x

print(my_abs(20))
None

如果已经把my_abs()的函数定义保存为abstest.py文件了,那么,可以在该文件的当前目录下启动Python解释器,
from test import my_abs来导入my_abs()函数,注意test是文件名(不含.py扩展名):

from test import my_absprint(my_abs(-9))
9

import的用法在后续模块一节中会详细介绍。

空函数

如果想定义一个什么事也不做的空函数,可以用pass语句:

def nop():
    pass

##pass还可以用在其他语句里,缺少了pass,代码运行就会有语法错误。
if age >= 18:
    pass

实际上pass用来作为占位符。

参数检查

调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError

my_abs(1,-2)
TypeError: my_abs() takes 1 positional argument but 2 were given

但是如果参数类型不对,Python解释器就无法帮我们检查:

my_abs('A')
TypeError: '>=' not supported between instances of 'str' and 'int'

abs('A')
TypeError: bad operand type for abs(): 'str' 

当传入了不恰当的参数时,内置函数abs会检查出参数错误。

而我们定义的my_abs没有参数检查,会导致if语句出错。
让我们修改一下my_abs的定义,对参数类型做检查,只允许整数和浮点数类型的参数。数据类型检查可以用内置函数isinstance()实现:

def my_abs(x):
    if not isinstance(x,(int,float)):
        raise TypeError('bad operand type')
    if x >= 0:
        return x
    else:
        return -x

##添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误    
my_abs('A')
TypeError: bad operand type

返回多个值

比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的坐标:

import math语句表示导入math包,并允许后续代码引用math包里的sincos等函数。

import math

def move(x,y,step,angle=0):
    nx = x + step * math.cos(angle)
    ny = y - step * math.sin(angle)
    return nx,ny


x, y = move(100, 100, 60, math.pi / 6)
print(x,y)
151.96152422706632 70.0

但其实这只是一种假象,Python函数返回的仍然是单一值:

a = move(100, 100, 60, math.pi / 6)
print(a)
(151.96152422706632, 70.0)

Python的函数返回多值其实就是返回一个tuple,但写起来更方便。

练习

请定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程的两个解。
ax^2+bx+c=0
提示:

一元二次方程的求根公式为:计算平方根可以调用math.sqrt()函数:
x= (−b±√(b^2−4ac)/2a

import math

def quadratic(a, b, c):
    if b^2-4*a*c < 0:
      return TypeError('no root')
    elif b^2-4*a*c == 0:
        x = -b/(2*a)
        return x
    else:
        x1 = (-b+math.sqrt(b**2-4*a*c))/(2*a)
        x2 = (-b-math.sqrt(b**2-4*a*c))/(2*a)
        return x1,x2

print('quadratic(2,3,1)=', quadratic(2,3,1))
quadratic(2,3,1)= no root
print('quadratic(1,3,-4)=', quadratic(1,3,-4))
quadratic(1,3,-4)= (1.0, -4.0)

if quadratic(2, 3, 1) == (-0.5, -1.0):
    print('测试失败')
elif quadratic(1, 3, -4) == (1.0, 4.0):
    print('测试失败')
else:
    print('测试成功')
    
测试成功

3 函数的参数

位置参数

我们先写一个计算x^2的函数,当我们调用power函数时,必须传入有且仅有的一个参数x

def power(x):
    return x * x

power(25)
625

但是要计算x的高阶次要怎么办?可以把power(x)修改为power(x, n),用来计算x^n:

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

print(power(2,5))
32

默认参数

新的power(x, n)函数定义没有问题,但是,旧的调用代码失败了,原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用:

power(5)
TypeError: power() missing 1 required positional argument: 'n'

这个时候,默认参数就排上用场了。由于我们经常计算x^2,所以,完全可以把第二个参数n的默认值设定为2:

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

设置默认参数时,有几点要注意:

一是必选参数在前,默认参数在后,否则Python的解释器会报错;

二是如何设置默认参数。

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

举个例子,我们写个一年级小学生注册的函数,需要传入namegender两个参数:

def enroll(name,gender):
    print('name:',name)
    print('gender:',gender)

##调用enroll()函数需要传入两个参数:
enroll('Sarah','F')
name: Sarah
gender: F

如果要继续传入年龄、城市等信息怎么办?我们可以把年龄和城市设为默认参数:

def enroll(name,gender,age=6,city='Beijing'):
    print('name:',name)
    print('gender:',gender)
    print('age:',age)
    print('city:',city)

enroll('Sarah','F')
name: Sarah
gender: F
age: 6
city: Beijing

##只有与默认参数不符的学生才需要提供额外的信息:
enroll('Bon','M',city='Tianjin')
name: Bon
gender: M
age: 6
city: Tianjin

默认参数很有用,但使用不当,也会掉坑里

先定义一个函数,传入一个list,添加一个END再返回:

def add_end(L=[]):
    L.append('END')
    return print(L)

##一开始没问题
add_end([1,2,3])
[1, 2, 3, 'END']

##但是如果连续调用
add_end()
['END']
add_end()
['END', 'END']

原因解释如下:

Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]了。

所以,默认参数必须指向不变对象。

要修改上面的例子,我们可以用None这个不变对象来实现:

def add_end(L=None):
    if L is None:
        L = []
    L.append('END')
    return print(L)

add_end([1,2,3])
[1, 2, 3, 'END']

##无论调用多少次,都不会有问题:
add_end()
['END']
add_end()
['END']

为什么要设计strNone这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。

可变参数

在Python函数中,可变参数就是传入的参数个数是可变的。

我们以数学题为例子,给定一组数字a,b,c……,请计算a^2 + b^2 + c^2 + ……。

要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:

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

calc([1,2,3])
14
calc((1,3,5,7))
84

如果利用可变参数,调用函数的方式可以简化成这样:

calc(1,3,5,7)

所以,我们把函数的参数改为可变参数:

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

calc(1,2,3)
14
calc(1,3,5,7)
84
calc(0)
0

定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*号。

如果已经有一个list或者tuple,要调用一个可变参数怎么办:

nums = [1,2,3]
calc(*nums)
14

Python允许在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去。

关键字参数

关键字参数允许传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

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

person('Michael',30)
name: Michael age: 30 other: {}

##也可以传入任意个数的关键字参数
person('Michael',30,gender='M',user='admin')
name: Michael age: 30 other: {'gender': 'M', 'user': 'admin'}

和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:

extra = {'city':'Beijing'}
person('Jack',24,city=extra['city'])
name: Jack age: 24 other: {'city': 'Beijing'}

##或者简化版本
person('Jack',24,**extra)
name: Jack age: 24 other: {'city': 'Beijing'}

extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,对kw的改动不会影响到函数外的extra

命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。

仍以person()函数为例,我们希望检查是否有cityjob参数:

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

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收cityjob作为关键字参数。这种方式定义的函数如下:*后面的参数被视为命名关键字参数。

def person(name,age,*,city,job):
    print(name,age,city,job)

person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer

##命名关键字参数必须传入参数名
person('Jack', 24, 'Beijing', 'Engineer')
TypeError: person() takes 2 positional arguments but 4 were given

##由于调用时缺少参数名city和job,Python解释器把前两个参数视为位置参数,后两个参数传给*args,但缺少命名关键字参数导致报错。

命名关键字参数可以有缺省值:

def person(name,age,*,city='Beijing',job):
    print(name,age,city,job)

##由于命名关键字参数city具有默认值,可不传入city参数
person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时,要特别注意,

如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:

def person(name, age, city, job):
    ##缺少 *,city和job被视为位置参数
    pass

参数组合

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。

但是,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数

比如定义一个函数,包含上述若干种参数:

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)

f1(1,2)
a= 1 b= 2 c= 0 args= () kw= {}

f1(1,2,c=3)
a= 1 b= 2 c= 3 args= () kw= {}

f1(1,2,3,'a','b')
a= 1 b= 2 c= 3 args= ('a', 'b') kw= {}

f1(1,2,3,'a','b',x=99)
a= 1 b= 2 c= 3 args= ('a', 'b') kw= {'x': 99}    

f2(1,2,d=99,ext=None)  
a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}   

通过一个tuple和dict,也可以调用上述函数:

args=(1, 2, 3, 4)
kw={'d':99,'x':'#'}
f1(*args,**kw)
a= 1 b= 2 c= 3 args= (4,) kw= {'d': 99, 'x': '#'}

args=(1, 2, 3)
kw={'d':99,'x':'#'}
f1(*args,**kw)
= 1 b= 2 c= 3 args= () kw= {'d': 99, 'x': '#'}

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

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

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

练习

以下函数允许计算两个数的乘积,请稍加改造,变成可接收一个或多个数并计算乘积:

def mul(x, y):
    return x * y

##
def mul(*numbers):
    if len(numbers) == 0:
        pass
        raise TypeError('please enter again')
        
    else:
        sum = 1
        for n in numbers:
            sum = sum * n 
    return sum

# 测试
print('mul(5) =', mul(5))
print('mul(5, 6) =', mul(5, 6))
print('mul(5, 6, 7) =', mul(5, 6, 7))
print('mul(5, 6, 7, 9) =', mul(5, 6, 7, 9))
if mul(5) != 5:
    print('测试失败!')
elif mul(5, 6) != 30:
    print('测试失败!')
elif mul(5, 6, 7) != 210:
    print('测试失败!')
elif mul(5, 6, 7, 9) != 1890:
    print('测试失败!')
else:
    try:
        mul()
        print('测试失败!')
    except TypeError:
        print('测试成功!')

mul(5) = 5
mul(5, 6) = 30
mul(5, 6, 7) = 210
mul(5, 6, 7, 9) = 1890
测试成功!

4 递归函数

如果一个函数在内部调用自身本身,这个函数就是递归函数。
举个例子,我们来计算阶乘n! = 1 x 2 x 3 x ... x n,用函数fact(n)表示
fact(n)=n!=1×2×3×⋅⋅⋅×(n−1)×n=(n−1)!×n=fact(n−1)×n
所以,fact(n)可以表示为n x fact(n-1),只有n=1时需要特殊处理。

于是,fact(n)用递归的方式写出来就是:

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

fact(1)
1

fact(20)
2432902008176640000

使用递归函数需要注意防止栈溢出。递归调用的次数过多,会导致栈溢出。可以试试fact(1000)

fact(1000)
RecursionError: maximum recursion depth exceeded in comparison

解决递归调用栈溢出的方法是通过尾递归优化,尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。

上面的fact(n)函数由于return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:

def fact(n):
    return fact_inter(n,1)

def fact_inter(num,product):
    if num == 1:
        return product
    return fact_inter(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

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

练习

汉诺塔的移动可以用递归函数非常简单地实现。

请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法,例如:

def move(n, a, b, c):
    if n == 1:
        print(a, '-->', c)

##
def move(n, a, b, c):
    if n == 1:
        print(a, '-->', c)
    else:
        move(n-1, a, c, b)
        print(a, '-->', c)
        move(n-1, b, a, c)

move(3,'A','B','C')
A --> C
A --> B
C --> B
A --> C
B --> A
B --> C
A --> C

python函数部分就到这里。

下一篇我们继续高级特性的学习。

相关文章

  • 【Python 学习记录】三、函数

    这里是佳奥!开始学习python中内置的各种函数吧。 1 调用函数 比如求绝对值的函数abs,该函数有且仅有1个参...

  • 学Python的012天

    原创:XIAO油菜花学习进度记录:《零基础入门学习Python》P21:函数:内嵌函数和闭包《Python开发入门...

  • 学Python的013天

    原创:XIAO油菜花学习进度记录:《零基础入门学习Python》P22:函数:lambda表达式《Python开发...

  • 学Python的014天

    原创:XIAO油菜花学习进度记录:《零基础入门学习Python》P23:函数:递归是什么《Python开发入门与爬...

  • 学Python的010天

    原创:XIAO油菜花学习进度记录:《零基础入门学习Python》P19:函数:灵活即强大《Python开发入门与爬...

  • python 函数式编程之高阶函数map/reduce

    python学习笔记,特做记录,分享给大家,希望对大家有所帮助。 高阶函数map/reduce Python内建了...

  • python 函数式编程之偏函数

    python学习笔记,特做记录,分享给大家,希望对大家有所帮助。 偏函数 Python的functools模块提供...

  • Python入门之路-常用语法1

    本文主要记录一些Python里常用的简单语法,记录Python学习的点点滴滴。 1.input函数 提供用户输入的...

  • python 函数式编程之高阶函数filter

    python学习笔记,特做记录,分享给大家,希望对大家有所帮助。 高阶函数filter Python内建的filt...

  • Python学习记录 - 操作列表

    Python学习记录 - 操作列表 注意避免缩进错误 创建数字列表使用函数 range()for value in...

网友评论

      本文标题:【Python 学习记录】三、函数

      本文链接:https://www.haomeiwen.com/subject/jxdsgrtx.html