python装饰器原理详解,python lambda菜鸟教程
Yyds干货库存
Python以看起来像魔术而闻名,这可能部分是因为函数可以采取多种形式:lambda、decorator、closure等等。一个好的函数调用不用写类也能做出惊人的事情!
你可能会说函数是神奇的。
重新理解函数我们已经接触过数据类型和不变性中的函数。如果你还没看过那篇文章,我建议你现在就回去看看。
让我们看一个简单的函数例子,以确保我们在同一个维度上。
def cheer(音量=无):
如果音量为零:
打印(“耶。”)
elif volume==大声点!:
打印(“耶!”)
elif volume==大声点!:
打印( *深呼吸* )
打印(“耶!”)
cheer() #打印“耶。”
欢呼(‘大声点!’)#打印“耶!”
欢呼(‘大声点!’)#打印 *深呼吸*.“耶!”这没什么好惊讶的。函数cheer()接受一个参数volume。如果我们不为volume传递参数,它将默认为None。
什么是函数式编程?我们这些来自面向对象编程语言的人已经学会了从类和对象的角度考虑一切。数据被组织成对象和负责访问和修改数据的函数。有时这是可行的,但有时,类开始感觉像太多的模板。
函数式编程几乎相反。我们围绕函数进行组织,并通过这些函数传递数据。我们必须遵守一些规则:
函数应该只接受输入,只产生输出。功能不应该有副作用;他们不应该修改任何外部的东西。函数应该(理想地)总是为相同的输入产生相同的输出。函数内部不应该有会破坏这种模式的状态。现在,在你把所有Python代码重写成纯函数之前,打住!不要忘记Python是一种多范式语言。你不必选择一个范式并坚持下去;你可以在你的代码中混合搭配,这是最好的。
事实上,我们已经这样做了!迭代器和生成器都是从函数式编程中引用的,它们与对象配合得很好。也可以随意组合lambda、decorator、closure。关键在于为工作选择最好的工具。
在实践中,我们很少同时避免副作用。在将函数式编程的概念应用于Python代码时,您应该更加关注并仔细考虑副作用,而不是完全避免它们。它们仅限于没有更好的方法来解决问题的情况。这里没有硬性的规则可以依赖。你需要培养自己的洞察力。
递归当一个函数调用自己时,这叫做递归。当我们需要重复函数的整个逻辑,但循环不合适(或感觉太混乱)时,这是很有帮助的。
注意:我下面的例子被简化以突出递归本身。事实上,递归并不是最好的方法。当需要对不同的数据块重复调用复杂的语言时,比如遍历树结构时,递归更好。
随机导入
随机种子()
阶级反派:
def __init__(self):
self.defeated=False
def对抗(自我):
#掷骰子。
如果random.randint(0,10)==10:
self.defeated=True
def战败(反派):
恶棍.对抗()
如果恶棍被打败了:
打印(“耶!”)
返回True
否则:
打印(继续尝试.)
败退(反派)
星光=反派()
胜利=失败(星光)
如果胜利:
打印(“耶!”)和random有关的东西可能看起来很新。和这个题目其实没什么关系,总之我们可以在程序开始的时候用随机数生成器生成一个随机数(random.seed()),然后调用random.randint(min,max)。函数中的最小值和最大值定义了可能值的范围。
这里逻辑的重要部分是defeat()函数。只要小人没有被打败,函数就会调用自己,传递小人变量,直到其中一个函数调用返回值。在这种情况下,值在递归调用堆栈中返回,并最终存储在victory中。
无论需要多长时间,我们最终都会打败那个恶棍。
注意,无限递归可以是一个强大的工具,但它也带来了一个问题:如果我们不能停止呢?
def mirror_pool(旁观者):
反射=[]
对于旁观者来说:
reflections.append(旁观者)
旁观者.追加(倒影)
我们有{len(lookers) - 1}个副本。)
返回镜像池(旁观者)
duplicates=mirror _ pool([ pinkie pie ])显然,这将永远运行下去!一些语言没有提供一种干净的方式来处理这个问题。——函数只会递归,直到它崩溃。
Python更优雅地阻止了这种疯狂。一旦达到设定的递归深度(通常是997-1000次),它将停止整个程序并引发一个错误:
RecursionError:调用Python对象时超过了最大递归深度。
像所有的错误一样,我们可以在事情失去控制之前发现它:
尝试:
duplicates=mirror_pool([小马碧琪])
除了RecursionError:
打印(“该看油漆干了。”)谢天谢地,由于我编写这段代码的方式,我实际上不需要做任何特殊的事情来清理997个重复项。递归函数从不返回,所以在这种情况下,重复项保持未定义状态。
然而,我们可能希望用另一种方式控制递归,所以我们不必使用try-除非是为了防止灾难。在我们的递归函数中,我们可以通过添加一个参数来跟踪它被调用的次数,当它变得太大时立即停止它。
def mirror_pool(lookers,calls=0):
呼叫数=1
反射=[]
对于旁观者来说:
reflections.append(旁观者)
旁观者.追加(倒影)
我们有{len(lookers) - 1}个副本。)
如果呼叫20:
lookers=mirror_pool(lookers,calls)
回看者
duplicates=mirror_pool([小马碧琪])
print(f 总计:{len(duplicates)} Pinkie Pies!)我们还需要弄清楚如何在不丢失原始数据的情况下删除20个副本,但至少程序没有崩溃。
注意:您可以使用sys.setrecursionlimit(n)来重写最大递归级别,其中n是您需要的最大值。
嵌套函数有时,我们可能希望在一个函数中重用一段逻辑,但我们不想通过创建另一个函数来搞乱我们的代码。
def use_elements(目标):
elements=[诚实,善良,笑声,
慷慨,忠诚,魔法]
定义使用(元素,目标):
使用{target}上{element}的元素打印(f。)
对于元素中的元素:
使用(元素,目标)
Use_elements(梦魇月)当然,这个简单例子的问题是它的用处并不明显。当我们想把很多逻辑抽象成函数来实现复用,但又不想在主函数之外定义的时候,嵌套函数就会变得非常有帮助。如果use()函数复杂得多,而且可能不止是一个循环调用,那么这样的设计就比较合理了。
尽管如此,这个例子的简单性仍然反映了基本概念。这也带来了另一个困难。你会注意到,每次我们调用()的时候,我们都是在把target传递给内部函数,这感觉毫无意义。我们不能只使用已经在局部范围内的目标变量吗?
事实上,我们可以:
def use_elements(目标):
elements=[诚实,善良,笑声,
慷慨,忠诚,魔法]
定义用途(元素):
使用{target}上{element}的元素打印(f。)
对于元素中的元素:
使用(元素)
Use_elements(梦魇月)然而,一旦我们试图修改这个变量,我们就陷入了困境:
def use_elements(目标):
elements=[诚实,善良,笑声,
慷慨,忠诚,魔法]
定义用途(元素):
使用{target}上{element}的元素打印(f。)
target=Luna
对于元素中的元素:
使用(元素)
打印(目标)
use _ elements( nightman Moon )运行这段代码会引发一个错误:
UnboundLocalError:在赋值前引用了局部变量“target”
显然,它不再知道我们的局部变量目标。这是因为默认情况下,封闭范围内任何现有的相同变量在被赋值给变量时都会被覆盖。因此,行target==Luna 试图创建一个新的变量,该变量被限制在use_elements()的封闭范围内,并隐藏已有的目标变量。我在Python中看到了这一点,并假设因为我们在use()函数中定义了target,所以对这个变量的所有引用都与这个本地名称相关。这不是我们想要的!
关键字nonlocal允许我们告诉内部函数,我们正在使用封闭局部作用域中的目标变量。
def use_elements(目标):
elements=[诚实,善良,笑声,
慷慨,忠诚,魔法]
定义用途(元素):
非局部目标
使用{target}上{element}的元素打印(f。)
target=Luna
对于元素中的元素:
使用(元素)
打印(目标)
use _ elements( nightman Moon )现在,运行结果,我们看到打印的值Luna。我们在这里的工作结束了!
注意:如果希望函数能够修改定义为全局范围的变量(除了所有函数),请使用global关键字而不是nonlocal。
闭包基于嵌套函数的思想。我们可以创建一个实际构建并返回另一个函数的函数(称为闭包)。
def收割机(小马):
total_trees=0
def applebucking(树):
外地小马,总数_棵
total_trees=树
打印到目前为止从{total_trees}棵树上收获的f { pony })
返回苹果巴克
苹果杰克=收割机(苹果杰克)
big_mac=harvester(大麦金塔)
Apple _ Bloom=harvester( Apple Bloom )
北果园=120
小心果蝠
东果园=135
南方果园=95
附近的房子=20
苹果杰克(西果园)
巨无霸(东果园)
苹果_bloom(近_屋)
巨无霸(北果园)
Apple_jack(south_orchard)在这个例子中,applebucking()是一个闭包,因为它封闭了非局部变量pony和total_trees。即使在外部函数终止后,闭包仍然保留对这些变量的引用。
闭包从harvester()函数返回,可以像任何其他对象一样存储在变量中。是因为它“封闭”了一个非局部变量,这使得它本身就是一个闭包。否则,它只是一个函数。
在这个例子中,我使用闭包来有效地创建具有状态的对象。换句话说,每个采集者都记得他或她采集了多少棵树。这种特殊用法并不严格符合函数式编程,但是如果您不想创建整个类来存储函数的状态,它会非常有用!
Apple_jack、big_macintosh、apple_bloom现在是三个不同的功能,各有独立的状态;他们每个人都有不同的名字,还记得他们砍了多少棵树。在一个封闭状态下发生的事情对其他封闭状态没有影响,它们都是独立的个体。
当我们运行代码时,我们会看到这种状态:
到目前为止,苹果杰克已经收获了80棵树。
到目前为止,大麦金塔已经收获了135棵树。
迄今为止从20棵树上收获了苹果花。
到目前为止,大麦金塔收获了255棵树。
到目前为止,苹果杰克收获了175棵树。
闭包闭包的问题本质上是“隐式类”,因为它们把函数和它们的持久信息(状态)放在同一个对象中。然而,闭包有几个独特的缺点:
你不能访问“成员变量”。在我们的例子中,我永远不能访问闭包apple_jack上的变量total_trees!我只能在关闭我自己的代码的上下文中使用这个变量。关闭状态是完全不透明的。除非你知道闭包是怎么写的,否则你不知道它记录了什么信息。因为前面两点,无法直接知道一个闭包什么时候有什么状态。当使用闭包时,您需要准备好处理这些问题以及它们带来的所有调试困难。我建议只在需要在调用之间存储少量私有状态时使用单个函数,并且只在代码中的有限时间段内使用,这样编写整个类是不合理的。(还有,别忘了生成器和协进程,可能更适合很多这样的场景。)
闭包仍然是Python的有用部分,只要你非常小心地使用它们。
Lambdaslambda是由单个表达式组成的匿名函数(没有名字)。
光是这个定义,就是很多程序员无法想象自己为什么需要定义的原因。写一个没有名字的函数,基本上让重用完全不切实际,有什么意义?当然,你可以把lambda赋给一个变量,但此时,你不应该刚刚写了一个函数吗?
为了理解这一点,我们先来看一个没有lambdas的例子:
类元素:
def __init__(self,element,color,pony):
self.element=元素
self.color=颜色
小马=小马
def __repr__(self):
return f“将{self.element} ({self.color})的元素调整为{self.pony}”
元素=[
元素(诚实,橘子,苹果杰克),
元素(“善良”、“粉红”、“小蝶”),
元素(笑声,蓝色,小马碧琪),
元素(慷慨,紫罗兰,稀有),
元素(忠诚,红色,彩虹虚线),
元素(“魔法”、“紫色”、“紫悦”)
]
def sort_by_color(元素):
返回element.color
元素=已排序(元素,关键字=sort_by_color)
Print(Elements)我想让大家注意的主要是sort_by_color()函数,我要写这个函数来显式地按照颜色对列表中的元素对象进行排序。其实有点烦,因为我已经不需要那个功能了。
这就是兰姆达斯的用武之地。我可以删除整个函数,改变元素=sorted(.)行到:
Elements=sorted (elements,key=lambda e: e.color)使用lambda允许我在使用它的地方准确地描述我的逻辑,而不是其他任何地方。(这个key=part只是表示我把lambda传递给了sorted()函数的键。)
lambda具有结构lambaparameters:返回表达式。它可以收集任意数量的用逗号分隔的参数,但是它只能有一个表达式,其值是隐式返回的。
注意:与常规函数不同,Lambda不支持类型注释(类型提示)。
如果我想重写lambda以根据元素名称而不是颜色进行排序,我只需要更改表达式部分:
Elements=sorted (elements,key=lambda e: e.name)就是这么简单。
类似地,当你需要将一个函数用一个表达式传递给另一个函数时,lambda非常有用。这是另一个例子,这次在lambda上使用了更多的参数。
要设置这个例子,让我们从Flyer的类开始,它存储名字和最大速度,并返回Flyer的随机速度。
随机导入
随机种子()
课程传单:
def __init__(self,name,top_speed):
self.name=name
self.top_speed=最高速度
def get_speed(自身):
return random . randint(self.top _ speed//2,self . top _ speed)我们希望能够让任何给定的飞行器对象执行任何飞行技能,但是将所有这些逻辑放入类本身是不切实际的.飞行技巧和变异可能有上千种!
Lambdas是定义这些技能的一种方式。我们将首先在这个类中添加一个函数,它可以接受函数作为参数。我们假设这个函数总是有一个参数:执行技能的速度。
定义执行(自我、技巧):
performed=trick(self.get_speed())
打印(f”{ self。name }执行了一个{ performed } ")要使用它,我们创建一个传单对象,然后将函数传递给它的执行()方法。
rd=传单("彩虹破折号",780)
以{s}英里/小时的速度翻滚。)
以{s}英里/小时的速度进行翻转。)因为希腊字母的第11个的逻辑在函数调用中,所以更容易看到发生了什么。
回想一下,你可以将匿名函数存储在变量中。当你希望代码如此简短但需要一些可重用性时,这实际上会很有帮助。例如,假设我们有另一个传单,我们希望他们两个都进行桶装卷。
喷火=飞行器("喷火",650)
barrelroll=lambda s: f 以{s}英里/小时的速度翻滚。
喷火表演(桶滚)
研发执行(桶滚)当然,我们可以将桶形辊写成一个适当的单行函数,但是通过这种方式,我们为自己节省了一些样板文件。而且,由于在这段代码之后我们不会再次使用该逻辑,因此没有必要再使用一个成熟的函数。
再一次,可读性很重要非常适合用于简短、清晰的逻辑片段,但如果你有更复杂的事情,你还是应该编写一个合适的函数。
装饰器假设我们想要修改任何函数的行为,而不实际更改函数本身。
让我们从一个相当基本的函数开始:
def partial _ transformation(target,combine_with):
result=f " { target }-{ combine _ with } "
打印(将{目标}转换为{结果}。)
回送结果
目标=青蛙
target=partial _ transformation(target, orange )
打印(f 目标现在是{目标}。)运行给我们:
把青蛙变成橙色。
目标现在是一只橙色的青蛙。很简单,对吧。但是,如果我们想为此添加一些额外的宣传呢?如你所知,我们真的不应该将这种逻辑放在我们的部分变形函数中。
这就是装饰器的用武之地。装饰器"包装"了函数周围的附加逻辑,这样我们实际上不会修改原始函数本身。这使得代码更易于维护。
让我们从为大张旗鼓创建一个装饰器开始。这里的语法一开始可能看起来有点过于复杂,但请放心,我会详细介绍。
导入功能工具
def党_大炮(函数):
@functools.wraps(func)
极好的包装(*args,**kwargs):
打印(‘等着瞧吧……’)
r=func(*args,**kwargs)
打印(呀!*方炮爆炸* )
返回r
返回包装你可能已经认识到包装()它实际上是一个闭包,它是由我们的party_cannon()函数创建并返回的。我们传递我们正在"装饰"的函数,func。
然而,我们真的对我们装饰的函数一无所知!它可能有也可能没有参数。闭包的参数列表(*args,**kwargs)实际上可以接受任何数量的参数,从零到无穷大。调用函数()时,我们以相同的方式将这些参数传递给func()。
当然,如果函数()上的参数列表与通过装饰器传递给它的参数之间存在某种不匹配,则会引发通常和预期的错误(这显然是一件好事)。
在包装()内部,我们可以随时随地调用我们的函数func()。我选择在打印我的两条消息之间这样做。
我不想丢弃函数()返回的值,所以我将返回的值分配给r,并确保在装饰器的末尾用返回r .
请注意,对于在装饰器中调用函数的方式,或者即使调用它或调用多少次,实际上并没有硬性规定。你还可以以任何你认为合适的方式处理参数和返回值。关键是要确保装饰器实际上不会以某种意想不到的方式破坏它装饰的函数。
装饰器前面的奇数行@functools.wraps(func)实际上是一个装饰器本身。没有它,修饰函数本质上混淆了自己的身份,混淆了我们对__doc__(文档字符串)和__name__(名称)的理解。这个特殊的装饰器确保这种情况不会发生;包装后的函数保留了它的身份,并且可以从函数外部以所有常见的方式进行访问。(要使用这个特殊的装饰器,我们必须首先导入functools。)
现在我们已经编写了party_cannon装饰器,我们可以用它来添加我们想要的partial _ transfiguration()函数。这很简单:
@党_炮
def partial _ transformation(target,combine_with):
result=f“{ target }-{ combine _ with }”
打印(将{目标}转换为{结果}。)
第一行结果,@party_cannon是我们唯一做的改动!现在修饰了partial _ transfiguration函数。
注意:你甚至可以将多个装饰器堆叠在一起,一个在另一个之上。只要确保每个装饰器都紧接在它所包装的函数或装饰器之前。
我们之前的用法一点都没变:
目标=青蛙
target=partial _ transformation(target, orange )
打印(f 目标现在是{目标}。)但是,输出会发生变化:
等着瞧吧.
把青蛙变成橙色。
耶!*派对大炮爆炸*
目标现在是一只橙色的青蛙。
总结我们已经介绍了Python中函数“魔力”的四个方面。让我们花点时间来回顾一下。
递归是函数调用自己。谨防“无限递归”;Python不会让递归栈的深度超过大约一千次递归调用。嵌套函数是在另一个函数中定义的函数。嵌套函数可以读取其封闭范围内的变量,但不能修改它们,除非您将变量指定为嵌套函数中的第一个非局部变量。闭包是一个嵌套函数,它关闭一个或多个非局部变量,然后由一个关闭的函数返回。Lambda是一个匿名(未命名)函数,由单个表达式组成,并返回其值。Lambda可以像任何其他对象一样被传递和赋给变量。Decorator“包装”另一个函数来扩展其行为,而不直接修改要包装的函数。有关这些主题的更多信息,请阅读文档。(你会注意到,官方文档中很少提到嵌套函数和闭包;它们是设计模式,而不是正式定义的语言结构。)
原创作品来自程,
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。