【Python】DS的基础学习笔记5:函数

函数

5.1 函数的定义及调用

5.1.1 为什么要用函数

  1. 提高代码复用性——抽象出来,封装为函数
  2. 将复杂的大问题分解成一系列小问题,分而治之——模块化设计的思想
  3. 利于代码的维护和管理

顺序式

# 5的阶乘
n = 5
res = 1
for i in range(1, n+1):
    res *= i
print(res)

# 20的阶乘
n = 20
res = 1
for i in range(1, n+1):
    res *= i
print(res)

120
2432902008176640000

抽象成函数

def factoria(n):
    res = 1
    for i in range(1, n+1):
        res *= i
    return res


print(factoria(5))
print(factoria(20))

5.1.2 函数的定义与调用

白箱子:输入——处理——输出
三要素:参数、函数体、返回值

1 定义

def 函数名(参数):
  函数体
  return 返回值

# 求正方形的面积
def area_of_square(length_of_side):
    square_area = pow(length_of_side, 2)
    return square_area
2 调用

函数名(参数)

area = area_of_square(5)
print(area)

25

5.1.3 参数传递

0 形参与实参
  • 形参(形式参数):函数定义时的参数,实际上是变量名
  • 实参(实际参数):函数调用时的参数,实际上就是变量的值
1 位置参数
  • 严格按照位置顺序,用实参对形参进行赋值(关联)
  • 一般用在参数比较少的时候
def function(x, y, z):
    print(x, y, z)
    

function(1, 2, 3)  # x = 1; y = 2; z = 3

1 2 3

  • 实参与形参个数必须一一对应,一个不能多,一个不能少
function(1, 2)

报错

function(1, 2, 3, 4)

报错2

2 关键字参数
  • 打破位置限制,直呼其名进行值的传递(形参 = 实参)
  • 必须遵守实参与形参数量上一一对应
  • 多用在参数比较多的场合
def function(x, y, z):
    print(x, y, z)


function(y=1, z=2, x=3)

3 1 2

  • 位置参数可以与关键字参数混合使用
  • 但是,位置参数必须放在关键字参数前面
function(1, z=2, y=3)

1 3 2

  • 不能为同一个形参重复传值
function(1, z=2, x=3)

报错3

3 默认参数
  • 在定义阶段就给形参赋值——该形参的常用值
  • 默认参数必须放在非默认参数后面
  • 机器学习库中类的方法里非常常见
  • 调用函数时,可以不对该形参传值
def register(name, age, sex="male"):
    print(name, age, sex)


register("DoubleS", 18)

DoubleS 18 male

  • 也可以按正常的形参进行传值
def register(name, age, sex="male"):
    print(name, age, sex)


register("林志玲", 38, "female")

林志玲 38 female

  • 默认参数应该设置为不可变类型(数字、字符串、元组)
def function(ls=[]):
    ls.append(1)
    print(ls)


function()
function()
function()

[1]
[1, 1]
[1, 1, 1]

ls地址信息并未发生变化,后续调用在地址的列表上进行

def function(ls="Python"):
    ls += "3.7"
    print(ls)


function()
function()
function()

Python3.7
Python3.7
Python3.7

字符串为不可变类型,没有“记忆”功能

  • 让参数变成可选的
def name(first_name, last_name, middle_name=None):
    if middle_name:
        return first_name + middle_name + last_name
    else:
        return first_name + last_name


print(name("周", "伦", "杰"))
print(name("Double", "S"))

周杰伦
DoubleS

4 可变长参数 *args
  • 不知道会传来多少参数 *args
  • 该形参必须放在参数列表最后
def foo(x, y, z, *args):
    print(x, y, z)
    print(args)


foo(1, 2, 3, 4, 5, 6)  # 多余的参数,打包传递给args

1 2 3
(4, 5, 6)

  • 实参打散
def foo(x, y, z, *args):
    print(x, y, z)
    print(args)


foo(1, 2, 3, [4, 5, 6])
foo(1, 2, 3, *[4, 5, 6])  # 打散的是列表、字符串、元组或集合

1 2 3
([4, 5, 6],)
1 2 3
(4, 5, 6)

5 可变长参数 **kwargs
def foo(x, y, z, **kwargs):
    print(x, y, z)
    print(kwargs)


foo(1, 2, 3, a=4, b=5, c=6)  # 多余的参数,以字典形式打包传递给kwargs

1 2 3
{‘a’: 4, ‘b’: 5, ‘c’: 6}

  • 字典实参打散
def foo(x, y, z, **kwargs):
    print(x, y, z)
    print(kwargs)


foo(1, 2, 3, **{"a": 4, "b": 5, "c": 6})

1 2 3
{‘a’: 4, ‘b’: 5, ‘c’: 6}

  • 可变长参数的组合使用
    可接收任意多参数的方法
def foo(*args, **kwargs):
    print(args)
    print(kwargs)


foo(1, 2, 3, a=4, b=5, c=6)

(1, 2, 3)
{‘a’: 4, ‘b’: 5, ‘c’: 6}

5.1.4 函数体与变量作用域

  • 函数体就是一段在函数被调用时才会执行的代码,代码构成与其他代码并无不同
  • 局部变量——仅在函数体内定义和发挥作用
def multiply(x, y):
    z = x * y
    return z


print(multiply(2, 9))
print(z)

18
print(z)
NameError: name ‘z’ is not defined

  • 全局变量——外部定义的都是全局变量
  • 全局变量可以在函数体内直接被使用
n = 3
ls = [0]


def multiply(x, y):
    z = n * x * y
    ls.append(z)
    return z


print(multiply(2, 9))
print(ls)

54
[0, 54]

  • 通过global在函数体内定义全局变量
n = 3
ls = [0]


def multiply(x, y):
    global z
    z = n * x * y
    ls.append(z)
    return z


multiply(2, 9)
print(z)

54

5.1.5 返回值

1 单个返回值
def foo(x):
    return x**2
2 多个返回值——以元组的形式
def foo(x=1):
    return 1, x, x**2, x**3  # 用逗号分开,打包返回


print(foo(3))
a, b, c, d = foo(3)  # 解包赋值
print(a)
print(b)
print(c)
print(d)

(1, 3, 9, 27)
1
3
9
27

3 可以有多个return语句,一旦其中一个执行,代表了函数运行的结束
def is_holiday(day):
    if day in {"Sunday", "Saturday"}:
        return "Is holiday"
    else:
        return "Not holiday"
    print("I don't know")  # 没有机会运行


print(is_holiday("Sunday"))
print(is_holiday("Monday"))

Is holiday
Not holiday

4 没有return语句,返回值None
def foo():
    print("我是孙悟空")


res = foo()
print(res)

我是孙悟空
None

5.1.6 几点建议

1 函数及其参数的命名参考变量的命名
  • 字母小写及下划线组合
  • 有实际意义
2 应包含简要阐述函数功能的注释,注释紧跟函数定义后面
def foo():
    # 这个函数的作用是为了大家看注释的位置
    pass
3 函数定义前后各空两行
def f1():
    pass

            # 空出两行
def f2():
    pass


def f3(x=3):  # 默认参数赋值等号两侧不需要加空格
    pass


# …
4 默认参数赋值等号两侧不需加空格

5.2 函数式编程实例

模块化编程思想

  • 自顶向下,分而治之

【问题描述】

  • 小丹和小伟羽毛球打得都不错,水平也在伯仲之间,小丹略胜一筹,基本上打100个球,小丹能赢55次,小伟能45次。
  • 但是每次大型比赛(1局定胜负,谁先赢到21分,谁就获胜),小丹赢得概率远远大于小伟,小伟很不服气
  • 你能通过模拟实验,来揭示其中的奥妙吗?

【问题抽象】

  1. 在小丹与小伟的二元比赛系统中,小丹每球获胜概率为55%,小伟每球获胜概率45%;
  2. 每局比赛,先赢21球(21分)者获胜
  3. 假设进行n = 1000场独立比赛,小丹会获胜多少场?(n比较大的时候,实验结果约为真实期望)

【问题分解】

def main():
    # 主要逻辑
    prob_A, prob_B, number_of_games = get_inputs()  # 获取原始数据
    win_A, win_B = sim_n_games(prob_A, prob_B, number_of_games)  # 获取模拟结果
    print_summary(win_A, win_B, number_of_games)  # 结果汇总输出
1 输入原始数据
def get_inputs():
    # 输入原始数据
    prob_A = eval(input("请输入运动员A每球获胜概率(0~1):"))
    prob_B = round(1 - prob_A, 2)
    number_of_games = eval(input("请输入模拟的场次(正整数):"))
    print("模拟比赛总场数:", number_of_games)
    print("A选手每球获胜概率:", prob_A)
    print("B选手每球获胜概率:", prob_B)
    return prob_A, prob_B, number_of_games

单元测试

prob_A, prob_B, number_of_games = get_inputs()

请输入运动员A每球获胜概率(0~1):0.45
请输入模拟的场次(正整数):1000
模拟比赛总场数: 1000
A选手每球获胜概率: 0.45
B选手每球获胜概率: 0.55

2 多场比赛模拟
def sim_n_games(prob_A, prob_B, number_of_games):
    # 模拟多场比赛的结果
    win_A, win_B = 0, 0  # 初始化A、B获胜场次
    for i in range(number_of_games):  # 迭代number_of_games次
        score_A, score_B = sim_one_game(prob_A, prob_B)  # 获得模拟依次比赛的比分
        if score_A > score_B:
            win_A += 1
        else:
            win_B += 1
    return win_A, win_B
import random
def sim_one_game(prob_A, prob_B):
    # 模拟一场比赛的结果
    score_A, score_B = 0, 0
    while not game_over(score_A, score_B):
        if random.random() < prob_A:  # random.random()生产[0,1)之间的随机小数,均匀分布
            score_A += 1
        else:
            score_B += 1
    return score_A, score_B
def game_over(score_A, score_B):
    # 单场模拟结束条件,一方先达到21分,比赛结束
    return score_A == 21 or score_B == 21

单元测试 用assert——断言进行

  • assert expression
  • 表达式结果为false的时候触发异常
assert game_over(21, 8) == True
assert game_over(9, 21) == True
assert game_over(11, 8) == True

报错

print(sim_one_game(0.55, 0.45))
print(sim_one_game(0.7, 0.3))
print(sim_one_game(0.2, 0.8))

print(sim_n_games(0.55, 0.45, 1000))

(21, 13)
(21, 14)
(4, 21)
(742, 258)

3 结果汇总输出
def print_summary(win_A, win_B, number_of_games):
    # 结果汇总输出
    print("共模拟了{}场比赛".format(number_of_games))
    print("选手A获胜了{}场,占比{:.1%}".format(win_A, win_A/number_of_games))
    print("选手B获胜了{}场,占比{:.1%}".format(win_B, win_B/number_of_games))
print_summary(729, 271, 1000)

共模拟了1000场比赛
选手A获胜了729场,占比72.9%
选手B获胜了271场,占比27.1%

4 总结

总代码为:

import random


def main():
    # 主要逻辑
    prob_A, prob_B, number_of_games = get_inputs()  # 获取原始数据
    win_A, win_B = sim_n_games(prob_A, prob_B, number_of_games)  # 获取模拟结果
    print_summary(win_A, win_B, number_of_games)  # 结果汇总输出


def get_inputs():
    # 输入原始数据
    prob_A = eval(input("请输入运动员A每球获胜概率(0~1):"))
    prob_B = round(1 - prob_A, 2)
    number_of_games = eval(input("请输入模拟的场次(正整数):"))
    print("模拟比赛总场数:", number_of_games)
    print("A选手每球获胜概率:", prob_A)
    print("B选手每球获胜概率:", prob_B)
    return prob_A, prob_B, number_of_games


def sim_n_games(prob_A, prob_B, number_of_games):
    # 模拟多场比赛的结果
    win_A, win_B = 0, 0  # 初始化A、B获胜场次
    for i in range(number_of_games):  # 迭代number_of_games次
        score_A, score_B = sim_one_game(prob_A, prob_B)  # 获得模拟依次比赛的比分
        if score_A > score_B:
            win_A += 1
        else:
            win_B += 1
    return win_A, win_B


def sim_one_game(prob_A, prob_B):
    # 模拟一场比赛的结果
    score_A, score_B = 0, 0
    while not game_over(score_A, score_B):
        if random.random() < prob_A:  # random.random()生产[0,1)之间的随机小数,均匀分布
            score_A += 1
        else:
            score_B += 1
    return score_A, score_B


def game_over(score_A, score_B):
    # 单场模拟结束条件,一方先达到21分,比赛结束
    return score_A == 21 or score_B == 21


def print_summary(win_A, win_B, number_of_games):
    # 结果汇总输出
    print("共模拟了{}场比赛".format(number_of_games))
    print("选手A获胜了{}场,占比{:.1%}".format(win_A, win_A / number_of_games))
    print("选手B获胜了{}场,占比{:.1%}".format(win_B, win_B / number_of_games))


main()

调用:

请输入运动员A每球获胜概率(0~1):0.55
请输入模拟的场次(正整数):1000
模拟比赛总场数: 1000
A选手每球获胜概率: 0.55
B选手每球获胜概率: 0.45
共模拟了1000场比赛
选手A获胜了732场,占比73.2%
选手B获胜了268场,占比26.8%

可以发现:你以为你跟别人差距只是一点点,实际上,差距老大了

5.3 匿名函数

5.3.1 基本形式

lambda 变量: 函数体

5.3.2 常用用法

在参数列表中最适合使用匿名函数,尤其是与key = 搭配

  • 排序sort() sorted()
ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
ls.sort()
print(ls)

[(76, 94), (79, 100), (85, 85), (86, 71), (93, 88)]

ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
ls.sort(key=lambda x: x[1])
print(ls)

[(86, 71), (85, 85), (93, 88), (76, 94), (79, 100)]

ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
temp = sorted(ls, key=lambda x: x[0]+x[1])
print(temp)

[(86, 71), (85, 85), (76, 94), (79, 100), (93, 88)]

  • max() min()
ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
n = max(ls, key=lambda x: x[1])
print(n)

(79, 100)

ls = [(93, 88), (79, 100), (86, 71), (85, 85), (76, 94)]
n = min(ls, key=lambda x: x[1])
print(n)

(86, 71)

5.4 面向过程和面向对象

面向过程——以过程为中心的编程思想,以“什么正在发生”为主要目标进行编程 冰冷的、程序化的
面向对象——将现实世界的事物抽象成对象,更关注“谁在受影响”,更加贴近现实 有血有肉,拟人(物)化的

  • 以公共汽车为例
  • “面向过程”:汽车启动是一个事件,汽车到站是另一个事件……
    在编程序的时候我们关心的是某一个事件,而不是汽车本身
    我们分别对启动和到站编写程序
  • “面向对象”:构造“汽车”这个对象
    对象包含动力、服役时间、生产厂家等等一系列“属性”;
    也包含加油、启动、加速、刹车、转弯、鸣喇叭、到站、
    维修等一系列的“方法”;
    通过对象的行为表达相应事件

以上,便是第五节深入探索内容,对于函数定义与调用有了一定了解,同时明白了面向过程与面向对象的编程。
下一节将深入理解类,面向对象的编程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值