深入学习Python自定义函数

news2024/12/25 13:01:26

目录

  • 0. 前言
  • 1. 最基础的自定义函数写法
  • 2. 参数传递
  • 3. 函数返回值
  • 4. 作用域
  • 5. 匿名函数
  • 6. 装饰器
  • 7. 闭包
  • 8. 生成器
  • 9. 为自定义函数写注解

0. 前言

今天来深入学习 Python 中的自定义函数,为的是日后能够写出时间和内存更好的优化,以及形成良好的编程习惯。

操作系统:Windows10 专业版

开发环境:Pycahrm Comunity 2022.3

Python解释器版本:Python3.8

1. 最基础的自定义函数写法

以下这种自定义函数写法应该是很多人所用的方式了,包括我本人也喜欢这么做:
test.py

# 函数定义
def greet(name):
    print(f"Hello, {name}!")

# 函数调用
greet("Alice")
# 输出:Hello, Alice!

2. 参数传递

这里我举例了三种参数传递的方式,你可以通过 关键字位置以及默认参数来为函数传递参数,这些你已经在Python基础中有所了解:

# 位置参数
def add(x, y):
    return x + y

result = add(3, 4)
print(result)
# 输出:7

# 关键字参数
def person_info(name, age):
    print(f"Name: {name}")
    print(f"Age: {age}")

person_info(age=25, name="Alice")
# 输出:
# Name: Alice
# Age: 25

# 默认参数
def greet(name, message="Hello"):
    print(f"{message}, {name}!")

greet("Alice")
# 输出:Hello, Alice!
greet("Bob", "Hi")
# 输出:Hi, Bob!

3. 函数返回值

函数返回值就是返回你需要的运算结果(可以是各种数值,字符,类等对象),你只需要在函数结束的位置添加上 return num 就能够获取到你想要的返回值:

def add_and_subtract(x, y):
    add_result = x + y
    sub_result = x - y
    return add_result, sub_result

result1, result2 = add_and_subtract(5, 3)
print(result1)  # 输出:8
print(result2)  # 输出:2

4. 作用域

变量的作用域,顾名思义就是变量的生效范围,包括全局作用域和局部作用域,以及如何在不同的作用域中声明和使用变量。

# 全局变量
count = 0

def increment():
    global count  # 使用 global 关键字声明对全局变量的引用
    count += 1

increment()
print(count)  # 输出:1

# 局部变量
def multiply(x, y):
    result = x * y  # result 是一个局部变量,只在 multiply 函数内可见
    return result

print(multiply(3, 4))  # 输出:12
print(result)  # 会引发报错(由于result仅在multiply()生效导致)

5. 匿名函数

学习使用 lambda 关键字创建匿名函数。这些函数是一种简化定义和使用的特殊函数,可以在需要函数的地方使用。

我自认为这种函数完完全全就是为了增加他人的阅读难度,所以不推荐使用,但我还是在下方举例:

# 使用匿名函数计算两个数的和
add = lambda x, y: x + y
result = add(3, 4)
print(result)  # 输出:7

6. 装饰器

在装饰器模式中,wrapper 是一个内部函数(也称为闭包),它由装饰器函数返回。wrapper 函数用于将额外的功能添加到原始函数上。

在装饰器中,wrapper 函数通常用于在调用原始函数之前或之后执行一些操作,或者在其执行过程中进行修改。这样,通过调用 wrapper 函数,可以实现装饰器所需的额外功能。

在装饰器的实现中,wrapper 函数的参数列表通常使用 *args 和 **kwargs,以便能够接收任意数量和类型的位置参数和关键字参数,并将它们传递给原始函数。通过调用 func(*args, **kwargs),可以调用原始函数并获取其返回值。

在装饰器的返回语句中,通常会返回 wrapper 函数本身。在使用 @装饰器 语法将装饰器应用到函数上时,实际上是将原始函数作为参数传递给装饰器函数,并将装饰器函数的返回值即 wrapper 函数作为新的函数定义。

wrapper 函数是装饰器的核心部分,它代表了一个新的函数,通过在原始函数的前后注入额外的代码或修改其行为,实现了装饰器所需的功能。

例如,我们接下来为 print() 函数添加时间戳

import datetime

# 定义装饰器
def timestamp_decorator(func):
    def wrapper(*args, **kwargs):
        # 获取当前时间
        timestamp = datetime.datetime.now()
        # 格式化时间戳
        formatted_timestamp = timestamp.strftime("%Y-%m-%d %H:%M:%S")
        # 调用原函数并打印时间戳
        print("[{}]".format(formatted_timestamp), end=" ")
        return func(*args, **kwargs)
    return wrapper

@timestamp_decorator
def print_with_timestamp(*args):
    print(*args)

# 使用
print_with_timestamp("Hello, World!")

# 打印以下内容
# [2023-07-16 02:04:59] Hello, World!

这段代码先定义了一个装饰器,然后往下使用了“@”调用了装饰器函数为print_with_timestamp函数装饰。

这样使用print_with_timestamp打印的函数会带上时间戳,这在服务器中是很常见的,因为服务器需要记录某一时刻发生的事件。

其实使用装饰器与下面这段代码效果是一样的:

def print_with_time(*args):
    # 获取当前时间
    timestamp = datetime.datetime.now()
    # 格式化时间戳
    formatted_timestamp = timestamp.strftime("%Y-%m-%d %H:%M:%S")
    # 调用原函数并打印时间戳
    print("[{}] {}".format(formatted_timestamp,*args))

print_with_time("Hello, World!")

但是如果有很多个函数需要你记录时间戳呢?为所有函数添上相同重复的语句显然不太合适,主函数中多次调用打印时间戳的函数也会影响美观,所以使用装饰器是最佳的选择。

7. 闭包

闭包是一种在函数内部定义的函数,并且它可以访问外部函数的变量。

换句话说,闭包是一个函数对象,它能够记住并访问创建它时所在的环境中的变量,即使创建它的环境不存在了。

当一个内部函数引用了外部函数的变量,并且在外部函数执行完毕之后仍然存在,这个内部函数就被认为是一个闭包。

闭包的主要优点是:

  • 它可以保护隐藏在外部函数作用域中的数据,使其不能被意外修改。
  • 它可以为外部函数提供一种延续性,即使外部函数已经执行完毕,闭包仍然可以访问和使用外部函数的变量。
  • 它可以允许我们在多个函数调用之间共享数据。

这些内容很绕,如果你还是不明白,你可以来运行一下以下示例:

def color_print(color):
    def print_with_color(text):
        print(f"\033[{color}m{text}\033[0m")
    return print_with_color

# 创建不同的颜色打印函数
red_print = color_print("31")  # 红色
green_print = color_print("32")  # 绿色
yellow_print = color_print("33")  # 黄色

# 使用颜色打印函数
red_print("Hello, World!")
green_print("Hello, World!")
yellow_print("Hello, World!")

它的运行效果如下:
在这里插入图片描述

我们定义了一个color_print闭包函数,它接受一个color参数。

color_print函数内部定义了一个print_with_color函数,它会打印带有指定颜色的文本。color_print函数返回这个print_with_color函数,形成了闭包。

我们可以通过调用color_print函数并指定颜色参数来创建不同颜色的打印函数。我们可以通过调用这些打印函数来打印相应颜色的文本。

8. 生成器

当我们使用生成器函数时,我们使用yield关键字来定义一个迭代器。生成器函数每次调用都会生成一个值,并返回给迭代器,而不是一次性返回所有的值。这样,在迭代器中只会保留当前生成的值,而不会占用额外的内存来存储整个数据序列。

以下是生成器的优点:

  • 节省内存:生成器函数可以生成大数据序列,而不会在内存中一次性存储整个序列。这对于处理非常大的数据集或无限序列特别有用。

  • 惰性计算:生成器函数可以进行惰性计算,即只在需要时才生成值。这在处理复杂计算或需要大量计算的情况下很有用,因为它只会在需要时进行计算,并不会浪费时间和资源。

  • 无限序列:生成器函数可以用于生成无限序列,例如斐波那契数列或素数序列。通过生成器函数,我们可以在需要时生成无限序列的下一个值,而不必事先计算完整的序列。

  • 管道处理:生成器函数可以用作数据处理的管道,通过一系列生成器对数据进行连续处理。每个生成器在接收到上一个生成器的输出后,生成并返回下一个值,从而实现一系列的数据处理操作。

  • 状态保留:生成器函数可以保留其状态,使得函数在多次调用之间可以“记住”之前的状态。这对于需要在多个步骤中共享信息或保存上下文很有用。

生成器提供了一种更灵活、高效、节省内存的迭代数据序列的方式,对于处理大数据集、惰性计算、管道处理和无限序列等情况非常有用。它们可以极大地提升代码的可读性、效率和性能。

下面我将使用一些示例带你感受它的魅力:

# 1. 节省内存,使用生成器函数生成大数据序列
def large_data_generator(n):
    for i in range(n):
        yield i

# 使用示例
data = large_data_generator(1000000)  # 生成一个大数据序列
for item in data:
    print(item)

生成器函数每次只生成一个值,并在需要时生成下一个值,这样就不会一次性生成整个序列,从而减少了内存的使用。

# 2. 惰性计算,根据需要生成值
def lazy_computation():
    x = 1
    yield x
    x += 1
    yield x
    x *= 2
    yield x

computation = lazy_computation()  # 进行惰性计算
print(next(computation))
print(next(computation))
print(next(computation))

函数lazy_computation()定义了一个生成器函数,该函数在每次调用时生成一个值并通过yield语句返回。

在每次生成一个值之后,函数会暂停执行,等待下一次调用。这样可以逐步计算值,而不是一次性计算所有值。

# 3. 生成无限序列,例如斐波那契数列
def fibonacci_sequence():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fibonacci = fibonacci_sequence()  # 生成无限斐波那契数列
for _ in range(10):
    print(next(fibonacci))

函数fibonacci_sequence()定义了一个生成器函数,使用while循环生成斐波那契数列。在每次循环迭代中,通过yield语句返回当前的斐波那契数。同时,通过同时更新变量a和b来计算下一个斐波那契数。

我们创建了一个生成器对象fibonacci并通过循环调用next()函数来获取生成器返回的值。由于生成器函数是用while True语句定义的,所以它会无限地生成新的斐波那契数。

# 4. 管道处理,使用连续的生成器函数进行数据处理
def pipeline_processing(data):
    def stage_a(data):
        for item in data:
            yield item * 2

    def stage_b(data):
        for item in data:
            yield item + 5

    for result in stage_b(stage_a(data)):
        yield result

numbers = [1, 2, 3, 4, 5]  # 管道处理
processed_data = pipeline_processing(numbers)
for item in processed_data:
    print(item)

函数pipeline_processing()定义了一个主要的生成器函数,它接收一个数据输入,并经过一系列的阶段处理后生成处理后的数据输出。每个阶段都是一个内部的生成器函数。

函数stage_a()和stage_b()是这两个处理阶段的生成器函数。它们分别对输入数据进行不同的处理,通过yield语句返回处理后的结果。

在pipeline_processing()函数中,先调用stage_a()生成器函数对输入数据进行处理,然后将处理后的结果作为参数传递给stage_b()生成器函数进行下一阶段的处理。在最后一阶段的循环中,通过yield语句返回最终处理结果。

对于输入数据 [1, 2, 3, 4, 5],经过处理后的输出如下: 7 9 11 13 15

# 5. 状态保留,生成器函数可以保留上一个状态
def stateful_generator():
    state = 0
    while True:
        yield state
        state += 1

state = stateful_generator()  # 状态保留的生成器函数
print(next(state))
print(next(state))
print(next(state))

函数stateful_generator()定义了一个生成器函数,使用变量state来保存状态。每次循环迭代时,通过yield语句返回当前的状态,并在下一次迭代之前更新状态。

我们创建了一个生成器对象state,并通过调用next()函数来获取生成器返回的值。每次调用next()函数时,生成器会从上一次yield语句的位置恢复执行,并返回生成器函数的当前状态。

将输出: 0 1 2

这是因为每次调用next(state),都会获取生成器函数的下一个状态并打印出来。

9. 为自定义函数写注解

相信大家在写代码时候肯定遇到过这种代码,会遇到读不懂的情况:

from typing import List

def fun(questions: List[int]) -> int:
    """
    这是一个示例函数,接受一个整数列表作为输入,并返回一个整数值。
    
    参数:
    - questions: 整数列表,包含待处理的问题
    
    返回:
    - 整数值,表示问题的答案
    """
    # 函数体
    return 1

其实questions是我们传进去的参数,“:List[int]” 表面了传入的questions类型是整数列表

最后的 " -> int" 表明函数的返回值是int类型。

需要注意的是这些东西都只起到提示作用,并不会改变传入值返回值的类型和值。

这段代码的优点如下:

  • 类型注解:通过在函数定义的参数和返回值上进行类型注解,明确指定了函数的输入和输出类型,这样其他开发人员在使用和阅读代码时更容易理解函数的预期行为和使用方式。

  • 错误预防:类型注解可以帮助开发人员在编写代码时捕获潜在的类型错误。如果无意中将其他类型的数据传递给函数,或者函数返回了错误的类型,类型检查器可以在开发阶段或静态分析阶段发现问题,避免了在运行时出现潜在错误。

  • 文档说明:类型注解可以作为代码文档的一部分,帮助其他开发人员了解函数的预期输入和输出。它可以增加代码的可读性,让其他人更容易理解函数的目的和返回值的含义。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/758448.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

原型链:揭开JavaScript背后的神秘面纱

文章目录 1. 对象2. 原型(prototype)3. 原型链(prototype chain)4. 构造函数(constructor)5. prototype 属性6. 实例(instance)7. 原型继承(prototype inheritance&#…

HFSS仿真微带型威尔金森功分器学习笔记

HFSS仿真微带型威尔金森功分器 文章目录 HFSS仿真微带型威尔金森功分器1、 求解器设置2、 建模3、 边界条件设置4、 激励方式设置5、 扫频设置6、 设计检查,仿真分析7、 数据后处理 设计要求: 频带范围0.9~1.1GHz输入端口的回波损耗>20dB频带…

STL算法篇之拷贝修改类算法

STL算法篇之拷贝修改类算法 拷贝类算法copy与copy_backwardremove与remove_copyremove_if与remove_copy_if 修改类算法replace与replace_copyreplace_if与replace_copy_ifiter_swap与swap与swap_range、unique与unique_copy 拷贝类算法 1.copy 区间拷贝 2.copy_backward 逆向拷…

Redis双写一致性?

双写一致性:当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。 Redis作为缓存,mysql的数据如何与redis进行同步呢?(双写一致性) 1.我们当时做排行榜业务时,把历史榜…

CentOS7中安装docker并配置阿里云加速器

文章目录 一、docker的安装二、docker的卸载三、配置加速器四、docker-compose安装五、docker-compose卸载六、docker-compose相关命令七、常用shell组合 一、docker的安装 参考:https://docs.docker.com/engine/install/centos 本文内容是基于:CentOS L…

Python 算法基础篇:大O符号表示法和常见时间复杂度分析

Python 算法基础篇:大 O 符号表示法和常见时间复杂度分析 引言 1. 大 O 符号表示法 a ) 大 O 符号的定义 b ) 示例代码 2. 常见时间复杂度分析总结 引言 在分析和比较算法的性能时,时间复杂度是一项重要的指标。而大 O 符号表示法是用来描述算法时间复杂…

Redis 从入门到精通【进阶篇】之Lua脚本详解

文章目录 0. 前言1. Redis Lua脚本简介1.1 Lua脚本介绍Lua语言概述:Lua脚本的特点: 1.2 Redis中为何选择LuaLua与Redis的结合优势Lua脚本在Redis中的应用场景 2. Redis Lua脚本的执行流程1. 加载脚本:1.1 脚本缓存机制:1.2 脚本加…

C++ 可变参数函数用法与template模板泛型编程

目录 1、可变参数函数 (1)定义 (2)常用使用场景 2、template模板用法 1、可变参数函数 (1)定义 可变参数函数的可变参数一般使用省略号表示,如下: void func(int a,...);{} &…

牛P!安卓渗透神器PhoneSploit-Pro

工具介绍 一种集成的黑客工具,可使用ADB(Android Debug Bridge) 和Metasploit-Framework完成自动化,一键获取 Meterpreter 会话。 关注【Hack分享吧】公众号,回复关键字【230524】获取下载链接 如果设备打开了 ADB 端口,该工具可…

关于Context和ContextImpl还有ContextWrapper的关系

关于Context和ContextImpl还有ContextWrapper的关系 1.Context和ContextImpl还有ContextWrapper的关系 ​ 图一.Context和ContextImpl还有ContextWrapper的关系示意图 1.1.ContextImpl是Context的实现类 从Context和ContextImpl的源代码中,可以看出Context是一个抽象类,具体…

pytorch+CRNN实现

最近接触了一个仪表盘识别的项目,简单调研以后发现可以用CRNN来做。但是手边缺少仪表盘数据集,就先用ICDAR2013试了一下。 结果遇到了一系列坑。为了不使读者和自己在以后的日子继续遭罪。我把正确的代码发到下面了。 1)超参数请不要调整&am…

实体店搭建多用户商城系统有什么好处

现在很多的线下店铺都开始慢慢的转型线上了,想线上线下相结合,但是最近很多的商家都在问什么样的B2B2C商城系统开发适合线下店铺呢?这个问题今天加速度jsudo小编给大家一起整理如下,相信商家看完后就知道如何选择一款合适的商城系统了。 一、…

Spring Batch之读数据—读XML文件(三十二)

一、XML格式文件解析 XML是一种通用的数据交换格式,它的平台无关性、语言无关性、系统无关性,给数据集成与交换带来了极大的方便。XML在Java领域的解析方式有两种,一种叫SAX,另一种叫DOM。SAX是基于事件流的解析,DOM是…

刷题总结1

暑假第二周练习题 - Virtual Judge (vjudge.net) 该题就是将含4的数字全部跳过,不难发现,这就导致每位数都要少一个树,这就和9进制十分像,我们只要将该数字转化为9进制,然后将该9进制树的每位大于等于4的树加一就行了&…

【CXL】CXL QoS Telemetry 介绍

🔥点击查看精选 CXL 系列文章🔥 🔥点击进入【芯片设计验证】社区,查看更多精彩内容🔥 📢 声明: 🥭 作者主页:【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

51单片机的智能交通控制系统【含仿真+程序+演示视频带原理讲解】

51单片机的智能交通控制系统【含仿真程序演示视频带原理讲解】 1、系统概述2、核心功能3、仿真运行及功能演示4、程序代码 1、系统概述 该系统由AT89C51单片机、LED灯组、数码管组成。通过Protues对十字路口红绿灯控制逻辑进行了仿真。 每个路口包含了左转、右转、直行三条车道…

rapid_latex_ocr: 更快更好用的公式图像转latex工具

Rapid Latex OCR rapid_latex_ocr是一个将公式图像转为latex格式的工具。仓库中的推理代码来自修改自LaTeX-OCR,模型已经全部转为ONNX格式,并对推理代码做了精简,推理速度更快,更容易部署。仓库只有基于ONNXRuntime或者OpenVINO推…

AI辅助瞄准系统开发与实战(一)

文章目录 前言系统窗体设计提示弹窗功能主体页面 windows窗体绘制矩形绘制自定义线程池完整代码 总结 前言 直接看效果,狗头: 之所以搞这个的话,当然主要一方面是因为确实有点意思在里面,此外在很久以前,也有很多的UP…

光伏并网逆变器低电压穿越MATLAB仿真模型

使用MATLAB 2017b搭建 光伏逆变器低电压穿越仿真模型,boost加NPC拓扑结构,基于MATLAB/Simulink建模仿真。具备中点平衡SVPWM控制,正负序分离控制,pll,可进行低电压穿越仿真。 控制结构完整,波形完美&…

Web入门-HTTP协议

目录 HTTP概述 HTTP特点 HTTP请求协议 请求数据的格式 响应数据的格式 响应的状态码 HTTP协议的解析 HTTP概述 HTTP:Hyper Text Transfer Protocol,超文本传输协议,规定浏览器和服务器之间数据传输的规则。(即请求数据和响应数据的格式)以上一篇…