Python实战教程:修饰器精讲!(2019下半年篇)

是不是很多伙伴都认为Python的语法简单,作为入门语言学起来非常简单?

很多伙伴说Python写出来的代码只要符合逻辑,不需要太多的学习即可,即可从一门其他语言跳来用Python写(当然这样是好事,谁都希望入门简单)。

于是我便记录一下,如果要学Python的话,到底有什么好学的。记录一下Python有什么值得学的,对比其他语言有什么特别的地方,有什么样的代码写出来更Pythonic。一路回味,一路学习。

什么是修饰器,为什么叫修饰器

修饰器英文是Decorator,

我们假设这样一种场景:古老的代码中有几个很是复杂的函数F1、F2、F3…,复杂到看都不想看,反正我们就是不想改这些函数,但是我们需要改造加功能,在这个函数的前后加功能,这个时候我们很容易就实现这个需求:

def hi():
 """hi func,假装是很复杂的函数"""
 return 'hi'
def aop(func):
 """aop func"""
 print('before func')
 print(func())
 print('after func')
 
if __name__ == '__main__':
 aop(hi)

Python实战教程:修饰器精讲!(2019下半年篇)
以上是很是简单的实现,利用Python参数可以传函数引用的特性,就可以实现了这种类似AOP的效果。

这段代码目前没有什么问题,接下来煎鱼加需求:需求为几十个函数都加上这样的前后的功能,而所有调用这些函数地方也要相应地升级。

看起来这个需求比较扯,偏偏这个需求却是较为广泛:在调用函数的前后加上log输出、在调用函数的前后计算调用时间、在调用函数的前后占用和释放资源等等。

一种比较笨的方法就是,为这几十个函数逐一添加一个入口函数,针对a函数添加一个a_aop函数,针对b函数添加一个b_aop函数…如此这样。问题也很明显:

  • 工作量大
  • 代码变得臃肿复杂
  • 原代码有多处调用了这些函数,可以会升级不完全

于是接下来有请修饰器出场,修饰器可以统一地给这些函数加这样的功能:

def aop(func):
 """aop func"""
 def wrapper():
 """wrapper func"""
 print('before func')
 func()
 print('after func')
 return wrapper
@aop
def hi():
 """hi func"""
 print('hi')
 
@aop
def hello():
 """hello func"""
 print('hello')
if __name__ == '__main__':
 hi()
 hello()

Python实战教程:修饰器精讲!(2019下半年篇)
以上aop函数就是修饰器的函数,使用该修饰器时只要在待加函数上一行加@修饰器函数名即可,如实例代码中就是@aop。

加上了@aop后,调用新功能的hi函数就喝原来的调用一样:就是hi()而不是aop(hi),也意味着所有调用这些函数的地方不需要修改就可以升级。

简单地来说,大概修饰器就是以上的这样子。

@是个什么

对于新手来说,上面例子中,@就是一样奇怪的东西:为什么这样子用就可以实现需求的功能了。

其实我们还可以不用@,这里换一种写法:

def hi():
 """hi func"""
 print('hi')
def aop(func):
 """aop func"""
 def wrapper():
 """wrapper func"""
 print('before func')
 func()
 print('after func')
 return wrapper
if __name__ == '__main__':
 hi()
 print('')
 hi = aop(hi)
 hi()

Python实战教程:修饰器精讲!(2019下半年篇)
上面的例子中的aop函数就是之前说过的修饰器函数。

如例子main函数中第一次调用hi函数时,由于hi函数没叫修饰器,因此我们可以从输出结果中看到程序只输出了一个hi而没有前后功能。

然后加了一个hi = aop(hi)后再调用hi函数,得到的输出结果和加修饰器的一样,换言之:

@aop 等效于hi = aop(hi)

因此,我们对于@,可以理解是,它通过闭包的方式把新函数的引用赋值给了原来函数的引用。

有点拗口。aop(hi)是新函数的引用,至于返回了引用的原因是aop函数中运用闭包返回了函数引用。而hi这个函数的引用,本来是指向旧函数的,通过hi = aop(hi)赋值后,就指向新函数了。

被调函数加参数

以上的例子中,我们都假设被调函数是无参的,如hi、hello函数都是无参的,我们再看一眼煎鱼刚才的写的修饰器函数:

def aop(func):
 """aop func"""
 def wrapper():
 """wrapper func"""
 print('before func')
 func()
 print('after func')
 return wrapper

很明显,闭包函数wrapper中,调用被调函数用的是func(),是无参的。同时就意味着,如果func是一个带参数的函数,再用这个修饰器就会报错。

@aop
def hi_with_deco(a):
 """hi func"""
 print('hi' + str(a))
if __name__ == '__main__':
 # hi()
 hi_with_deco(1)

Python实战教程:修饰器精讲!(2019下半年篇)
就是参数的问题。这个时候,我们把修饰器函数改得通用一点即可,其中import了一个函数(也是修饰器函数):

from functools import wraps
def aop(func):
 """aop func"""
 @wraps(func)
 def wrap(*args, **kwargs):
 print('before')
 func(*args, **kwargs)
 print('after')
 return wrap
@aop
def hi(a, b, c):
 """hi func"""
 print('test hi: %s, %s, %s' % (a, b, c))
@aop
def hello(a, b):
 """hello func"""
 print('test hello: %s, %s' % (a, b))
if __name__ == '__main__':
 hi(1, 2, 3)
 hello('a', 'b')

Python实战教程:修饰器精讲!(2019下半年篇)
这是一种很奇妙的东西,就是在写修饰器函数的时候,还用了别的修饰器函数。那也没什么,毕竟修饰器函数也是函数啊,有什么所谓。

带参数的修饰器

思路到了这里,煎鱼不禁思考一个问题:修饰器函数也是函数,那函数也是应该能传参的。函数传参的话,不同的参数可以输出不同的结果,那么,修饰器函数传参的话,不同的参数会怎么样呢?

其实很简单,修饰器函数不同的参数,能生成不同的修饰器啊。

如,我这次用这个修饰器是把时间日志打到test.log,而下次用修饰器的时候煎鱼希望是能打到test2.log。这样的需求,除了写两个修饰器函数外,还可以给修饰器加参数选项:

from functools import wraps
def aop_with_param(aop_test_str):
 def aop(func):
 """aop func"""
 @wraps(func)
 def wrap(*args, **kwargs):
 print('before ' + str(aop_test_str))
 func(*args, **kwargs)
 print('after ' + str(aop_test_str))
 return wrap
 return aop
@aop_with_param('abc')
def hi(a, b, c):
 """hi func"""
 print('test hi: %s, %s, %s' % (a, b, c))
@aop_with_param('pppppp')
def hi2(a, b, c):
 """hi func"""
 print('test hi: %s, %s, %s' % (a, b, c))
if __name__ == '__main__':
 hi(1, 2, 3)
 print('')
 hi2(2, 3, 4)

Python实战教程:修饰器精讲!(2019下半年篇)
同样的,可以加一个参数,也可以加多个参数,这里就不说了。

修饰器类

大道同归,逻辑复杂了之后,人们都喜欢将函数的思维层面抽象上升到对象的层面。原因往往是对象能拥有多个函数,对象往往能管理更复杂的业务逻辑。

显然,修饰器函数也有对应的修饰器类。写起来也没什么难度,和之前的生成器一样简单:

from functools import wraps
class aop(object):
 def __init__(self, aop_test_str):
 self.aop_test_str = aop_test_str
 def __call__(self, func):
 @wraps(func)
 def wrapper(*args, **kwargs):
 print('before ' + self.aop_test_str)
 func()
 print('after ' + self.aop_test_str)
 return wrapper
 
@aop('pppppp')
def hi():
 print('hi')

看得出来,这个修饰器类也不过是多了个__call__函数,而这个__call__函数的内容和之前写的修饰器函数一个样!而使用这个修饰器的方法,和之前也一样,一样的如例子中的@aop(‘pppppp’)。

过于无聊,还试了一下继承的修饰器类:

class sub_aop(aop):
 def __init__(self, sub_aop_str, *args, **kwargs):
 self.sub_aop_str = sub_aop_str
 super(sub_aop, self).__init__(*args, **kwargs)
 def __call__(self, func):
 @wraps(func)
 def wrapper(*args, **kwargs):
 print('before ' + self.sub_aop_str)
 super(sub_aop, self).__call__(func)()
 print('after ' + self.sub_aop_str)
 return wrapper
 
@sub_aop('ssssss', 'pppppp')
def hello():
 print('hello')
 
if __name__ == '__main__':
 hello()

大家可以大胆猜测一下结果会怎样。。。

深度学习是机学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成和一个判别,它们相互竞争,生成生成数据,判别评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值