高级 Python:函数
一、说明
读完标题后,你可能会问自己一些类似的事情,“Python 中的函数是一个高级概念?如何?所有课程都引入了功能作为语言的基本块。你既是对的,也是错的。
大多数关于 Python 的课程都将函数作为基本概念和构建块进行介绍,因为没有它们,您将根本无法编写函数式代码。这与函数式编程范式完全不同,函数式编程范式是一个单独的概念,但我也将触及这个概念。
在我们深入研究 Python 函数的高级复杂性之前,让我们简要回顾一些基本概念和您可能已经知道的事情。
二、简要基础知识
所以你开始编写你的程序,在某个时候你最终会写出相同的代码序列。你开始重复自己和代码块。事实证明,这是引入功能的好时机和地点。至少,是这样。在 Python 中,将函数定义为:
def shout(name):print(f'Hey! My name is {name}.')
在软件工程领域,我们区分了功能定义的各个部分:
def- 用于定义函数的 Python 关键字。shout- 函数名称。shout(name)- 函数声明。name- 函数参数。print(...)是函数体的一部分,或者我们如何称呼它为函数定义。
一个函数可以返回一个值,也可以根本没有返回值,就像我们之前定义的那个。当函数返回值时,它可以返回一个或多个:
def break_sentence(sentence):return sentence.split(' ')
结果是一个元组,您可以解压缩或选择任何元组元素来继续。
对于那些不知情的人来说,Python 中的函数是一等公民。这是什么意思?这意味着您可以像使用任何其他变量一样使用函数。您可以将它们作为参数传递给其他函数,从函数返回它们,甚至将它们存储在变量中。下面是其中一个示例:
def shout(name):return f'Hey! My name is {name}.'# we will use break_sentence defined above# assign function to another variable
another_breaker = break_sentence another_breaker(shout('John'))
# ['Hey!', 'My', 'name', 'is', 'John.']# Woah! Yes, this is a valid way to define function
name_decorator = lambda x: '-'.join(list(name))name_decorator('John')
# 'J-o-h-n'
等等,这个 lambda 是什么?这是在 Python 中定义函数的另一种方式。这就是所谓的未命名或匿名函数。好吧,在这个例子中,我们将它分配给一个名为 name_decorator 的变量,但您可以将 lambda 表达式作为另一个函数的参数传递,而无需命名它。我将很快介绍这一点。
剩下的就是给出一个示例,说明如何将函数作为参数传递或从另一个函数作为值返回。这是我们正在向先进概念迈进的部分,所以请耐心等待。
def dash_decorator(name):return '-'.join(list(name))def no_decorator(name):return namedef shout(name, decorator=no_decorator):decorated_name = decorator(name)return f'Hey! My name is {decorated_name}'shout('John')
# 'Hey! My name is John'shout('John', decorator=dash_decorator)
# 'Hey! My name is J-o-h-n'
这就是将lambda函数作为参数传递给另一个函数的样子。功能呢?好吧,看看下面的例子:
def shout(name, decorator=lambda x: x):decorated_name = decorator(name)return f'Hey! My name is {decorated_name}'print(shout('John'))
# Hey! My name is Johnprint(shout('John', decorator=dash_decorator))
# Hey! My name is J-o-h-n
现在,默认的装饰函数是 lambda并按原样返回参数的值(幂等)。在这里,它是匿名的,因为它没有附加名称。
请注意, print 也是一个函数,我们在其中传递一个函数作为参数。本质上,我们是链接函数。这可以引导我们走向函数式编程范式,这是你可以在 Python 中选择的路径。我将尝试专门针对这个主题写另一篇博文,因为它对我来说非常有趣。现在,我们将保持过程式编程范例;也就是说,我们将继续我们迄今为止所做的事情。
如前所述,函数可以分配给变量,作为参数传递给另一个函数,并从该函数返回。我已经向您展示了前两种情况的一些简单示例,但是从函数返回函数怎么样?起初我想让它非常简单,但话又说回来,这是一个高级 Python!
三、中级或高级零件
这绝不是 Python 中函数和函数高级概念的指南。有很多很棒的材料,我将在这篇文章的最后留下。但是,我想谈谈我发现非常有趣的几个有趣的方面。
Python 中的函数是对象。我们怎样才能弄清楚这一点? Python 中的每个对象都是一个类的实例,该类最终继承自一个称为 type 的特定类。其细节很复杂,但为了能够了解这与函数有什么关系,这里有一个示例:
type(shout)
# functiontype(type(shout))
# type
当您在 Python 中定义一个类时,它会自动继承该类。继承哪个类?objectobject
type(object)
# type
我应该告诉你 Python 中的类也是对象吗?事实上,这对初学者来说是令人难以置信的。但正如吴恩达所说,这并不那么重要;别担心。
好的,所以函数是对象。当然,函数应该有一些神奇的方法,对吧?
shout.__class__
# functionshout.__name__
# shoutshout.__call__
# <method-wrapper '__call__' of function object at 0x10d8b69e0>
# Oh snap!
魔术方法 __call__ 是为可调用的对象定义的。所以我们的shout对象(函数)是可调用的。我们可以带或不带参数调用它。但这很有趣。我们之前所做的是定义一个喊叫函数,并获取一个可以使用 __call__ 魔术方法调用的对象,该方法是一个函数。你看过电影《盗梦空间》吗?
所以,我们的函数实际上并不是一个函数,而是一个对象。对象是类的实例并包含方法和属性,对吗?这是您应该从 OOP 中了解的内容。我们怎样才能知道对象的属性是什么?有一个名为 vars 的 Python 函数,它返回对象属性及其值的字典。让我们看看下一个示例中会发生什么:
vars(shout)
# {}shout.name = 'Jimmy'vars(shout)
# {'name': 'Jimmy'}
这很有趣。并不是说你可以马上弄清楚它的用例。即使你能找到它,我也强烈建议你不要使用这种黑魔法。这并不容易遵循,尽管它是一个有趣的弯曲。我之所以向你展示这一点,是因为我们想证明函数确实是对象。请记住,Python 中的所有内容都是一个对象。这就是我们引入 Python 的方式。
现在,期待已久的功能又回来了。这个概念也非常有趣,因为它为您提供了很多实用性。只要有一点点句法糖,你就会变得非常有表现力。让我们开始吧。
首先,一个函数的定义可以包含另一个函数的定义。甚至不止一个。这里有一个很好的例子:
def shout(name):def _upper_case(s):return s.upper()return _upper_case(name)
如果你认为这只是一个错综复杂的版本name.upper(),你是对的。但是等等,我们正在到达那里。
因此,鉴于前面的示例是功能齐全的 Python 代码,您可以尝试在函数内定义的多个函数。这个巧妙的技巧有什么价值?好吧,您可能会遇到这样的情况:您的函数很大,并且包含重复的代码块。这样,定义子函数将增加可读性。在实践中,巨大的函数是代码味道的标志,强烈建议将它们分成几个较小的函数。因此,遵循这个建议,您将很少需要在彼此内部定义多个函数。需要注意的一件事是 _upper_case 函数是隐藏的,并且在最终定义并可供调用的shout函数的范围内无法访问。这样,您就无法轻松测试它,这是这种方法的另一个问题。
然而,在一种特定情况下,在另一个函数中定义一个函数是一种可行的方法。这是当你实现函数的装饰器时。这与我们在前面的示例之一中用来修饰名称字符串的函数无关。
四、Python 中的装饰器函数
什么是装饰器函数?可以把它看作是包装函数的函数。这样做的目的是为现有函数引入其他功能。例如,假设您希望在每次调用函数时进行记录:
def my_function():return sum(range(10))def my_logger(fun):print(f'{fun.__name__} is being called!')return funmy_function()
# 45my_logger(my_function)
# my_function is being called!
# <function my_function at 0x105afbeb0>my_logger(my_function)()
# my_function is being called!
# 45
注意我们如何装饰我们的功能;我们将其作为参数传递给装饰参数。但这还不够!请记住,装饰器返回函数,并且需要调用(调用)此函数。这是最后一次调用的作用。
现在,在实践中,您真正想要的是装饰保留在原始函数的名称下。在我们的例子中,我们希望在解释器解析我们的代码之后,是修饰函数的名称。这样一来,我们就能让事情变得简单易懂,并且我们确保代码的任何部分都无法调用函数的未修饰版本。例:my_function
def my_function():return sum(range(10))def my_logger(fun):print(f'{fun.__name__} is being called!')return funmy_function = my_logger(my_function)my_function(10)
# my_function is being called!
# 45
您会承认,我们将函数名称重新分配给装饰名称的部分很麻烦。你必须牢记这一点。如果要记录许多函数调用,则会有很多重复代码。这就是句法糖的用武之地。定义修饰器函数后,可以使用它来修饰另一个函数,方法是在函数定义前面加上修饰器函数的名称。例:@
def my_logger(fun):print(f'{fun.__name__} is being called!')return fun@my_logger
def my_function():return sum(range(10))my_function()
# my_function is being called!
# 45
这是 Python 的禅宗。看看代码的表现力和简单性。
这里有一件重要的事情需要注意!尽管输出有意义,但这不是您所期望的!在加载 Python 代码时,解释器将调用该函数并有效地运行它!您将获得日志输出,但这不会是我们首先想要的。现在看代码:my_logger
def my_logger(fun):print(f'{fun.__name__} is being called!')return fun@my_logger
def my_function():return sum(range(10))my_function()
# my_function is being called!
# 45
my_function()
# 45
为了能够在调用原始函数后运行装饰器代码,我们必须将其包装在另一个函数周围。这就是事情可能会变得混乱的地方。下面是一个示例:
def my_logger(fun):def _inner_decorator(*args, **kwargs):print(f'{fun.__name__} is being called!')return fun(*args, **kwargs)return _inner_decorator@my_logger
def my_function(n):return sum(range(n))print(my_function(5))
# my_function is being called!
# 10
在此示例中,也有一些更新,因此让我们回顾一下它们:
- 我们希望能够将参数传递给 my_function。
- 我们希望能够装饰任何函数,而不仅仅是 my_function。因为我们不知道未来函数的参数的确切数量,所以我们必须尽可能保持通用性,这就是我们使用 *args 和 **kwargs 的原因。
- 最重要的是,我们定义了 _inner_decorator,每次在代码中调用 my_function 时都会调用它。它接受位置参数和关键字参数,并将它们作为参数传递给修饰函数。
始终记住,装饰器函数必须返回一个函数,该函数接受相同的参数(数字及其各自的类型)并返回相同的输出(同样,数字及其各自的类型)。也就是说,如果你想让函数用户不感到困惑,代码阅读器不试图弄清楚到底发生了什么。
例如,假设您有两个结果不同的函数,但也需要参数:
@my_logger
def my_function(n):return sum(range(n))@my_logger
def my_unordinary_function(n, m):return sum(range(n)) + mprint(my_function(5))
# my_function is being called!
# 10print(my_unordinary_function(5, 1))
# my_unordinary_function is being called!
# 11
在我们的示例中,装饰器函数只接受它装饰的函数。但是,如果要传递其他参数并动态更改装饰器行为,该怎么办?假设您要调整记录器装饰器的详细程度。到目前为止,我们的装饰器函数已经接受了一个参数:它装饰的函数。但是,当装饰器函数有自己的参数时,这些参数将首先传递给它。然后,装饰器函数必须返回一个接受修饰函数的函数。从本质上讲,事情变得越来越复杂。还记得电影《盗梦空间》的参考资料吗?
下面是一个示例:
from enum import IntEnum, auto
from datetime import datetime
from functools import wrapsclass LogVerbosity(IntEnum):ZERO = auto()LOW = auto()MEDIUM = auto()HIGH = auto()def my_logger(verbosity: LogVerbosity):def _inner_logger(fun):def _inner_decorator(*args, **kwargs):if verbosity >= LogVerbosity.LOW:print(f'LOG: Verbosity level: {verbosity}')print(f'LOG: {fun.__name__} is being called!')if verbosity >= LogVerbosity.MEDIUM:print(f'LOG: Date and time of call is {datetime.utcnow()}.')if verbosity == LogVerbosity.HIGH:print(f'LOG: Scope of the caller is {__name__}.')print(f'LOG: Arguments are {args}, {kwargs}')return fun(*args, **kwargs)return _inner_decoratorreturn _inner_logger@my_logger(verbosity=LogVerbosity.LOW)
def my_function(n):return sum(range(n))@my_logger(verbosity=LogVerbosity.HIGH)
def my_unordinary_function(n, m):return sum(range(n)) + mprint(my_function(10))
# LOG: Verbosity level: LOW
# LOG: my_function is being called!
# 45print(my_unordinary_function(5, 1))
# LOG: Verbosity level: HIGH
# LOG: my_unordinary_function is being called!
# LOG: Date and time of call is 2023-07-25 19:09:15.954603.
# LOG: Scope of the caller is __main__.
# LOG: Arguments are (5, 1), {}
# 11
我不会详细描述与装饰器无关的代码,但我鼓励您查找并学习。这里我们有一个装饰器,以不同的详细程度记录函数调用。如前所述,my_logger 装饰器现在接受动态更改其行为的参数。将参数传递给它后,它返回的结果函数应该接受一个要装饰的函数。这是 _inner_logger 函数。现在,您应该了解装饰器代码的其余部分在做什么。
五、结论
我写这篇文章的第一个想法是写一些高级主题,比如 Python 中的装饰器。但是,正如您现在可能知道的那样,我也提到并使用了许多其他高级主题。在以后的文章中,我将在一定程度上解决其中的一些问题。尽管如此,我对你的建议是,也要从其他来源了解这里提到的事情。如果你正在使用任何编程语言进行开发,掌握这些函数是必须的,但掌握你选择的编程语言的所有方面可以让你在编写代码方面有很大的优势。
我希望我已经为你介绍了一些新的东西,并且你现在对作为高级 Python 程序员编写函数充满信心。
六、引用
- Python 装饰器入门
- Python 内部函数:它们有什么用?
- 枚举 HOWTO
相关文章:
高级 Python:函数
伊利亚拉扎列维奇 一、说明 读完标题后,你可能会问自己一些类似的事情,“Python 中的函数是一个高级概念?如何?所有课程都引入了功能作为语言的基本块。你既是对的,也是错的。 大多数关于 Python 的课程都将函数作为基…...
【学习笔记】[PA2019] Osady i warownie 2
这题好抽象😱 EI 说这题可以转化为对偶图,但是我完全没看懂😅 考虑维护最向右和向下的两条路径,那么不能放的位置就是两条路径的交(感性理解一下) 考虑抽象的描述这条路径, r i r_i ri表示…...
Flask——接口路由技术
接口路由技术 一、Flask 简介1、环境安装:2、一个最小的应用3、两种运行方式 二、定义路由1、普通路由2、动态路由3、限定类型4、地址尾部的“/” 三、请求与响应-请求方法四、请求与响应-处理请求数据1、request的常用属性/方法2、get 请求参数3、json 请求4、表单…...
Dubbo篇---第一篇
系列文章目录 文章目录 系列文章目录一、说说一次 Dubbo 服务请求流程?二、说说 Dubbo 工作原理三、Dubbo 支持哪些协议?一、说说一次 Dubbo 服务请求流程? 基本工作流程: 上图中角色说明: 二、说说 Dubbo 工作原理 工作原理分 10 层: 第一层:service 层,接口层,…...
powermock-成员变量赋值
powermock成员变量设置 //被测试类 Service public class Demo {private String aaa ;public String method1(){return aaa;} }//测试类,测试类中使用了mockito、和powermock,用powermock设置成员变量相较于mockito简洁一些,一般mockito和po…...
Axios请求成功和失败时分别执行哪个函数?
在 Axios 中,请求成功和失败时分别执行的函数是 then 和 catch。 特点: then 函数用于处理请求成功的情况,它接受一个回调函数作为参数,在请求成功时会调用该回调函数。catch 函数用于处理请求失败的情况,它也接受一…...
【Linux】进程概念III --fork函数解析
Halo,这里是Ppeua。平时主要更新C语言,C,数据结构算法…感兴趣就关注我吧!你定不会失望。 本篇导航 0. 创建进程1. 认识fork函数2.使用Fork函数3.关于fork的为什么3.1 一个函数如何返回两次?fork究竟在干什么?3.2 为什么要给子…...
关闭 Android SplashScreen(闪屏)
SplashScreen在Android 12上是强制的,如果你什么都不做,你的App在Android 12上就会自动拥有SplashScreen界面 但是这个SplashScreen界面太局限了能改的地方太少了 其实也没什么他主要作用是为了在App启动初始化的时候避免让用户在一个空白界面等待过长时…...
react_16
主页 import {DownCircleOutlined,MenuFoldOutlined,VerticalAlignTopOutlined, } from "ant-design/icons"; import { Button, Layout, Menu } from "antd"; import { ItemType } from "antd/es/menu/hooks/useItems"; import { Link, Navigat…...
前端性能分析工具
前段时间在工作中,需要判断模块bundle size缩减对页面的哪些性能产生了影响, 因此需要了解前端的性能指标如何定义的,以及前端有哪些性能分析工具, 于是顺便整理了一篇笔记, 以供前端小白对性能这块知识点做一个入门级的了解. 页面渲染 在了解性能指标和分析工具之前,有必要先…...
根据Aurora发送时序,造Aurora 数据包,从而进行AXIS接口数据位宽转换仿真
首先Aurora采用AXIS接口 由于后续需要进行AXIS接口 不同时钟域的数据位宽转换(64bit和256bit之间的转换),因此分两次走。 第一种方法:采用AXIS数据位宽转换IP AXIS跨时钟域IP 第二种方法:逻辑完成 下面记录逻辑…...
java后端响应结果Result
目录 一、Result1-1、响应代码1-2、调用响应1-3、在前端vue页面使用方法 一、Result 1-1、响应代码 package com.aaa.common;import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;Data AllArgsConstructor NoArgsConstructor public cla…...
react_11
MobX 介绍 需求,组件0 改变了数据,其它组件也想获得改变后的数据,如图所示 这种多个组件之间要共享状态数据,useState 就不够用了,useContext 也不好用了 能够和 react 配合使用的状态管理库有 MobX Redux 其中…...
AI:52-基于深度学习的垃圾分类
🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…...
[shell,hive] 在shell脚本中将hiveSQL分离出去
将Hive SQL语句写在单独的.hql文件中, 然后在shell脚本中调用这些文件来执行Hive查询。 这样可以将SQL语句与shell脚本分离,使代码更加清晰和易于维护。 基本用法 以下是一个示例,展示如何在shell脚本中使用.hql文件执行Hive查询…...
求两个(法)向量之间的rpy夹角
主要使用Eigen库实现: 1. 四元素到欧拉角的转换 #include <array> #include <Eigen/Geometry>template <typename T> inline Eigen::Matrix<typename std::remove_reference<T>::type::Scalar, 3, 1> eulerAnglesZYX(T q_in) {typedef typenam…...
[100天算法】-每个元音包含偶数次的最长子字符串(day 53)
题目描述 给你一个字符串 s ,请你返回满足以下条件的最长子字符串的长度:每个元音字母,即 a,e,i,o,u ,在子字符串中都恰好出现了偶数次。示例 1:输入:s &qu…...
从科幻走向现实,LLM Agent 做到哪一步了?
LLM 洪流滚滚,AI 浪潮席卷全球,在这不断冲击行业认知的一年中,Agent 以冉冉新星之态引起开发者侧目。OpenAI 科学家 Andrej Karpathy 曾言“OpenAI 在大模型领域快人一步,但在 Agent 领域,却是和大家处在同一起跑线上。…...
[笔记] 数据类型
整形 一字节(Byte,也就是平时KB、MB里面的B)就是八个二进制位(bit) 整形——int——4B无符号整形——unsigned int——4B短整形——short——2B长整型——long——4B双长整型——long long——8B 浮点型 参考博客:C 语言的浮点类型…...
QT学习之QT概述
1.1 什么是QT? Qt是一个跨平台的C图形用户界面应用程序框架。 QT特点: 跨平台,几乎支持所有的平台接口简单,容易上手,学习QT框架对学习其他框架有参考意义。一定程度上简化了内存回收机制开发效率高,能够…...
springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...
Frozen-Flask :将 Flask 应用“冻结”为静态文件
Frozen-Flask 是一个用于将 Flask 应用“冻结”为静态文件的 Python 扩展。它的核心用途是:将一个 Flask Web 应用生成成纯静态 HTML 文件,从而可以部署到静态网站托管服务上,如 GitHub Pages、Netlify 或任何支持静态文件的网站服务器。 &am…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
C# SqlSugar:依赖注入与仓储模式实践
C# SqlSugar:依赖注入与仓储模式实践 在 C# 的应用开发中,数据库操作是必不可少的环节。为了让数据访问层更加简洁、高效且易于维护,许多开发者会选择成熟的 ORM(对象关系映射)框架,SqlSugar 就是其中备受…...
QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
解决:Android studio 编译后报错\app\src\main\cpp\CMakeLists.txt‘ to exist
现象: android studio报错: [CXX1409] D:\GitLab\xxxxx\app.cxx\Debug\3f3w4y1i\arm64-v8a\android_gradle_build.json : expected buildFiles file ‘D:\GitLab\xxxxx\app\src\main\cpp\CMakeLists.txt’ to exist 解决: 不要动CMakeLists.…...
