高级 Python:函数

news2024/11/26 22:35:16
照片由 <a href=“https://unsplash.com/@iam_os?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText”>iam_os</a> 在 <a href=“https://unsplash.com/photos/jst9zmbCD58?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText”>Unsplash</a> 上拍摄
伊利亚·拉扎列维奇·

一、说明

        读完标题后,你可能会问自己一些类似的事情,“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 name

def 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 John

print(shout('John', decorator=dash_decorator))
# Hey! My name is J-o-h-n

        现在,默认的装饰函数是 lambda并按原样返回参数的值(幂等)。在这里,它是匿名的,因为它没有附加名称。

        请注意, print 也是一个函数,我们在其中传递一个函数作为参数。本质上,我们是链接函数。这可以引导我们走向函数式编程范式,这是你可以在 Python 中选择的路径。我将尝试专门针对这个主题写另一篇博文,因为它对我来说非常有趣。现在,我们将保持过程式编程范例;也就是说,我们将继续我们迄今为止所做的事情。

        如前所述,函数可以分配给变量,作为参数传递给另一个函数,并从该函数返回。我已经向您展示了前两种情况的一些简单示例,但是从函数返回函数怎么样?起初我想让它非常简单,但话又说回来,这是一个高级 Python!

三、中级或高级零件

        这绝不是 Python 中函数和函数高级概念的指南。有很多很棒的材料,我将在这篇文章的最后留下。但是,我想谈谈我发现非常有趣的几个有趣的方面。

        Python 中的函数是对象。我们怎样才能弄清楚这一点? Python 中的每个对象都是一个类的实例,该类最终继承自一个称为 type 的特定类。其细节很复杂,但为了能够了解这与函数有什么关系,这里有一个示例:

type(shout)
# function

type(type(shout))
# type

        当您在 Python 中定义一个类时,它会自动继承该类。继承哪个类?objectobject

type(object)
# type

        我应该告诉你 Python 中的类也是对象吗?事实上,这对初学者来说是令人难以置信的。但正如吴恩达所说,这并不那么重要;别担心。

        好的,所以函数是对象。当然,函数应该有一些神奇的方法,对吧?

shout.__class__
# function

shout.__name__
# shout

shout.__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 fun

my_function()
# 45

my_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 fun

my_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

        在此示例中,也有一些更新,因此让我们回顾一下它们:

  1. 我们希望能够将参数传递给 my_function。
  2. 我们希望能够装饰任何函数,而不仅仅是 my_function。因为我们不知道未来函数的参数的确切数量,所以我们必须尽可能保持通用性,这就是我们使用 *args 和 **kwargs 的原因。
  3. 最重要的是,我们定义了 _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)) + m

print(my_function(5))
# my_function is being called!
# 10

print(my_unordinary_function(5, 1))
# my_unordinary_function is being called!
# 11

        在我们的示例中,装饰器函数只接受它装饰的函数。但是,如果要传递其他参数并动态更改装饰器行为,该怎么办?假设您要调整记录器装饰器的详细程度。到目前为止,我们的装饰器函数已经接受了一个参数:它装饰的函数。但是,当装饰器函数有自己的参数时,这些参数将首先传递给它。然后,装饰器函数必须返回一个接受修饰函数的函数。从本质上讲,事情变得越来越复杂。还记得电影《盗梦空间》的参考资料吗?

        下面是一个示例:

from enum import IntEnum, auto
from datetime import datetime
from functools import wraps

class 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_decorator

    return _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)) + m

print(my_function(10))
# LOG: Verbosity level: LOW
# LOG: my_function is being called!
# 45

print(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

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

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

相关文章

3.21每日一题(区间在现求定积分)

当发现一个定积分&#xff0c;原函数根本找不出来时&#xff0c;可以用变量代换&#xff1a;区间再现&#xff01;&#xff01;&#xff01;

nodejs+vue高校食堂物流管理系统-毕业设计

1.本课题的研发内容是&#xff1a;研制开发一个基于 的高校食堂物流管理系统&#xff0c;使用B/S架构&#xff0c;使用MVC模式开发&#xff0c;实现对高校食堂物流各流程的统一管理。 2.课题的研发要求&#xff1a;实现一个基于 的高校食堂物流管理系统的基本功能和主要流程&am…

【STL】:list的模拟实现

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家解读一下有关list的模拟实现&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据…

【报错-已解决】Resource tagsets not found.

最近在接触nltk&#xff0c;已经在电脑里download nltk.data了&#xff0c;但程序还是报错Resource tagsets not found. 在电脑里查找tagset&#xff0c;发现tagset文件没有解压。 对tagset压缩包进行解压&#xff0c;并确认解压后的文件夹文件路径没有重复(tagsets)&#xf…

深入了解 CPU 的型号、代际架构与微架构

大家好&#xff0c;我是飞哥&#xff01; 在 10 月 16 号的时候&#xff0c;Intel 正式发布了第 14 代的酷睿处理器。但还有很多同学看不懂这种发布会上发布的各种 CPU 参数。借着这个时机&#xff0c;我给大家深入地讲讲 CPU 的型号规则、代际架构与微架构方面的知识。 CPU 在…

redis教程 一 redis中的常用命令

文章目录 redis常见命令Redis数据结构介绍redis通用命令String类型String的常见命令Key结构 Hash类型List类型Set类型SortedSet类型 redis常见命令 Redis数据结构介绍 Redis是一个key-value的数据库&#xff0c;key一般是String类型&#xff0c;不过value的类型多种多样&…

IIS中间件漏洞----DotNetScan 工具

用DotNetScan 工具把目标网站IP&#xff0c;端口填写下。找到IIS的网站后就可进行操作上传。 也可以使用IISPutScanner工具 上传文件 利用解析漏洞 利用漏洞的前提条件&#xff1a;网站根目录可写入

教培行业的创新:微信CRM系统带来高效管理

在当今快速发展的科技环境下&#xff0c;教育培训行业面临着越来越多的挑战和机遇。如何提高管理效率、扩大市场份额、提升客户满意度&#xff0c;成为了教培行业亟待解决的问题。而微信CRM系统的出现&#xff0c;为教培行业带来了创新性的解决方案。 一些教育培训行业存在的问…

布雷斯悖论和借贷式拥塞控制

先看布雷斯悖论&#xff0c;新增一条路不但没减少交通延滞&#xff0c;反而降低了服务水准&#xff0c;下面一个简单的例子&#xff1a; 关于布雷斯悖论的讨论已经太多&#xff0c;我给出个新解释&#xff0c;这和我引出 借贷式拥塞控制 (差论证和编码)有关。 看一个不严谨但更…

在SpringBoot 中集成Swagger

前提&#xff1a;我的SpringBoot 项目的版本是 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.18-SNAPSHOT</version><relativePath /> <!-- l…

使用Bazel构建高效的Android应用程序

使用Bazel构建高效的Android应用程序 1. 引言 在现代软件开发中&#xff0c;构建工具是至关重要的一环。Bazel是一个开源的构建工具&#xff0c;由Google开发并用于其内部项目多年。它旨在提供高效、可扩展和可靠的构建解决方案。Bazel最初专注于构建大规模软件项目&#xff…

约数之和 (普通快速幂求逆元做法)

假设现在有两个自然数 A 和 B&#xff0c;S 是 AB 的所有约数之和。 请你求出 Smod9901 的值是多少。 输入格式 在一行中输入用空格隔开的两个整数 A 和 B 。 输出格式 输出一个整数&#xff0c;代表 Smod9901 的值。 数据范围 0≤A,B≤5107 输入样例&#xff1a; …

月报总结|Moonbeam 10月份大事一览

万圣节快乐&#xff01;时间一晃眼&#xff0c;10月已经迈入尾声&#xff0c;也即将迎来寒冷的冬天。但与季节相反&#xff0c;加密产业近期的发展可以说是高潮起伏&#xff0c;热度不断攀升。Moonbeam在10月中也发布了许多重大的更新&#xff0c;如Uniswap V3前段上线、众贷DO…

找人做软件,过程该怎么控制?

我们找外包公司或者程序员兼职做软件&#xff0c;我们该怎么做过程控制呢&#xff0c; 做过程控制的目的是确保最终交付的软件是我们需要的&#xff0c; 虽然在开发前我们已经敲定了功能列表和原型&#xff0c;但这两者只是一个框架&#xff0c;就好比要建一栋房子&#xff0…

前端 | (十四)canvas基本用法 | 尚硅谷前端HTML5教程(html5入门经典)

文章目录 &#x1f4da;canvas基本用法&#x1f407;什么是canvas(画布)&#x1f407;替换内容&#x1f407;canvas标签的两个属性&#x1f407;渲染上下文 &#x1f4da;绘制矩形&#x1f407;绘制矩形&#x1f407;strokeRect时&#xff0c;边框像素渲染问题&#x1f407;添加…

【Redis】String字符串类型-常用命令

文章目录 String字符串类型常用命令setgetMGETMSET 计数命令INCRINCRBYDECRDECRBYINCRBYFLOAT 其它命令APPENDGETRANGESETRANGESTRLEN 命令总结 String字符串类型 1&#xff09;⾸先Redis中所有的键的类型都是字符串类型 2&#xff09;字符串类型的值&#xff08;value&#…

鉴源实验室 | 自动驾驶传感器攻击研究

作者 | 付海涛 上海控安可信软件创新研究院汽车网络安全组 来源 | 鉴源实验室 社群 | 添加微信号“TICPShanghai”加入“上海控安51fusa安全社区” 01 自动驾驶汽车的脆弱性 自2015年以来&#xff0c;汽车的信息安全问题受到国内外的广泛关注。而随着汽车的智能化与网联化的…

C语言scanf()函数读取包含空格的字符串

scanf()函数读取输入时&#xff0c;遇到空格或者回车结束读取&#xff1b; 所以输入的字符串中有空格&#xff0c;到空格就中断了&#xff1b; 根据资料&#xff0c;使用 "%[^\n]" 这样的格式化符遇到空格继续读取&#xff0c;直到敲了回车&#xff1b; 看一下&am…

微信小程序UI自动化测试实践:Minium+PageObject

小程序架构上分为渲染层和逻辑层&#xff0c;尽管各平台的运行环境十分相似&#xff0c;但是还是有些许的区别&#xff08;如下图&#xff09;&#xff0c;比如说JavaScript 语法和 API 支持不一致&#xff0c;WXSS 渲染表现也有不同&#xff0c;所以不论是手工测试&#xff0c…

Javaweb之HTML,CSS的详细解析

2. HTML & CSS 1). 什么是HTML ? HTML: HyperText Markup Language&#xff0c;超文本标记语言。 超文本&#xff1a;超越了文本的限制&#xff0c;比普通文本更强大。除了文字信息&#xff0c;还可以定义图片、音频、视频等内容。 标记语言&#xff1a;由标签构成的语言…