关于我转生开始学python的那件事(七)——函数

什么是函数

函数是一种代码抽象的方式,通过调用函数,我们可以快捷地实现某些运算

调用函数

在python中内置了许多常见的函数,我们可以直接进行调用,而哪些函数可以在官网查询到,或者我们也可以在交互环境中使用help()来获得该函数的更多信息

以取绝对值abs函数为例:

>>> abs(200)
200
>>> abs(-100)
100

而如果输入了错误的信息,可以根据返回的报错信息进行修改即可

类似地,在max()中,我们可以输入多个参数,然后该函数会返回这串数字中的最大值

>>> max(2,4,6)
6

当我们想要进行数据类型转换时,我们可以使用int()函数把其他的数据类型转换为整数

>>> int(123)
123
>>> int('123')
123
>>> int(12.34)
12

可以看到,字符串'123',浮点数12.34被转换为了整数

相似地,我们可以使用float()函数将一个值转换为浮点数

>>> float('12.34')
12.34

我们可以使用str()函数将一个值转换为字符串

>>> str(12.34)
'12.34'
>>> str(100)
'100'

我们可以使用bool()将一个值转换为布尔值(即True或False),而在python中,非零数值会被认为是True,空字符串会被认为是False

>>> bool(1)
True
>>> bool('')
False

最终,使用hex()函数我们可以将一个整数转换为十六进制字符串

>>> n1 = 255
>>> print(hex(n1))
0xff

另外,我们可以为函数赋名

>>> a = int
>>> a(12.34)
12

定义函数

在python中,我们使用def语句来定义函数,然后在缩紧块中编写函数体,函数的返回值用return来返回

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

print(my_abs(-100))

image-20250312145423226

如果我们将其保存为了my_abs.py文件,我们也可以使用import来导入直接运用该函数

至于import的用法后面会讲到

同样地,我们也可以定义一个什么都不干的空函数

def nop():
    pass

这个函数什么都不做,会直接pass掉,一般用来当还没想好怎么写函数代码时先让代码跑起来

同样的,pass也能在其他地方用到

if x >= 18:
    pass

但是需要注意的是,我们自己定义的my_abs函数和官方的abs函数相比,缺少了返回报错的参数检查,所以我们可以为自己的函数进行一些修改添加

def my_abs(x):
    if not isinstance(x, (int, float)):
        raise TypeError("bad operand type for abs(): '" + type(x).__name__ + "'")

    if x >= 0:
        return x
    else:
        return -x

当用户传入的x不是整数或浮点数时,将会抛出名为bad operand type for abs()的报错,isinstance用来检测x是否是int,float的数据类型

同样地,函数也可以返回多个值

import math

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

我们通过定义函数move,便能够实现给出坐标、位移和角度,从而计算出新的坐标

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)

此时我们说到的x,y是一个tuple

练习使用定义函数方法解一元二次方程:

import math

def quadratic(a, b, c):
    d = b * b - 4 * a * c
    if d > 0:
        x1 = (-b + math.sqrt(d)) / (2 * a)
        x2 = (-b - math.sqrt(d)) / (2 * a)
        return (x1, x2)
    elif d == 0:
        x = -b / (2 * a)
        return (x,)
    else:
        return None  

print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4))

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

函数的参数

位置参数

当我们想要计算一个平方的函数时候,我们可以定义如下一个函数

def pingfang(x):
    return x * x

那么我们想要计算一个数字的立方的时候,我们可以修改一下这个函数,做到

def pingfang(x):
    return x * x * x

但是如果我们要计算三次方,四次方...n次方呢

所以如果我们能够定义一个函数power(x, n),能够用来计算x的n次方岂不是很好

于是我们有了

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

在这段代码中,先将s赋值为1用于存储结果,然后while n > 0: 进入循环,表示只要 n 还没变成 0,就继续计算;

s = s * x:每次循环都将 s 乘上 x,等价于累乘 x,重复 n 次。

n = n - 1:指数 n 逐渐减少,确保循环在 n 次后终止。

当n为0时候跳出while,返回s即为多次方值

修改后的power(x, n)函数有两个参数:xn,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数xn

默认参数

在这个函数中,我们仍然面临着一个问题,即即使要计算我们最常见的二次方,也需要手动输入2,这实在是太麻烦了,那么我们如何为函数提供一个默认参数呢

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

在这个函数中 n=2 是一个默认参数,这意味着:

​ • 如果调用 power(x) 时不传递 n,它会默认使用 n=2,即计算 x²。

​ • 如果调用 power(x, n) 时提供了 n,那么 n 的值就会被覆盖,而不会强制等于 2。

在设置默认参数时,我们仍有一些需要注意的事情:

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

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

  • 默认参数必须指向不变对象!

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

    在这个函数中,作用是传入一个列表,添加END后再返回,但是如果我们以默认值执行,将会发现很快返回值便会变成

    >>> add_end()
    ['END', 'END']
    >>> add_end()
    ['END', 'END', 'END']

    似乎函数记住了上次输出后的结果...

    这究竟是怎么回事呢?

    原来,每次计算之后,相当于对L进行了重新赋值,此时的L变成了一个可变对象

    那么如何解决呢,只需要让我们指向一个不变对象即可

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

    这样问题便解决了

    为什么要设计strNone这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

可变参数

可变参数即意为传入的参数数量是可变的,例如我们传入a,b,c...,想要计算a^2 + b^2 + c^2 + ……

由于参数个数不确定,我们可以把传入的数字作为一个list或tuple传入

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

这时候我们需要组装好一个list或tuple进行输入

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

那么我们将输入变为可变参数

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

经过修改之后,我们便可以有以下计算的代码了

b = input('输入求和数字(可输入多个,用空格分隔): ').split()
b = [float(num) for num in b]  # 将字符串转换为浮点数列表

def cal(*numbers):
    total_sum = 0
    for n in numbers:
        total_sum += n ** 2  # 计算平方和
    return total_sum

a = cal(*b)  # 传入解包的参数
print(a)

关键字参数

关键词参数允许我们传入0个或包含任意个包含参数名的参数,这些关键词参数会自动组装成一个dict

例如这个代码中,我们使用**kw来将剩余内容自动组装为字典

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

我们在交互环境中尝试一下

>>> def person(name,age, **kw):
...     print('name:', name, 'age:', age, 'other:', kw)
... 
>>> person('Andy', 19, city='Hebei')
name: Andy age: 19 other: {'city': 'Hebei'}

可以看到cityHebei被自动组装成了一个字典

关键字参数可以用来收集必须参数之外的内容

同时,关键词参数也支持我们将字典引入

>>> def person(name,age, **kw):
...     print('name:', name, 'age:', age, 'other:', kw)
... 
>>> addon = {'job':'teacher'}
>>> person('Jack', 20, job=addon['job'])
name: Jack age: 20 other: {'job': 'teacher'}

我们直接引入了addon字典

函数的递归

在函数中,如果一个函数内部调用函数自身,那么就构成了一个递归,这个函数就是递归函数

例如我们想要计算n!=n*(n-1)*(n-2)*...*2*1,用函数fact(n)来表示

那我们可以看出fact(n) = n * fact(n-1),只有当n=1时候才需要特殊考虑

那么我们就可以用递归的方法写出fact(n)

def fact(n):
    if n == 1:
        return 1
    return n * fact(n-1)
>>> fact(1)
1
>>> fact(5)
120
>>> fact(100)
93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
>>> fact(1000)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in fact
  File "<stdin>", line 4, in fact
  File "<stdin>", line 4, in fact
  [Previous line repeated 996 more times]
RecursionError: maximum recursion depth exceeded

我们在交互环境中进行测试,发现在计算1000时候出现了报错

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

那么如何解决呢,我们可以使用尾递归优化,但是很可惜在python中并不支持尾递归优化,栈层数多了依然会栈溢出

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)

这便是将上面函数改写为尾递归函数之后的格式,可以看到我们在最后一步才进行乘法运算

博客内容均系原创,未经允许严禁转载!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇