python3 装饰器_Python3设计模式一 :装饰器模式

装饰器模式可以将一个函数或者类进行“装饰”,使其行为进行一致化的修订,目的是为了增强对象的响应能力,或者为了增加对象的多种不同行为的能力。

装饰器模式有点类似于继承,关于使用装饰器还是继承的选择,原则是当需要更多的行为时候,装饰器通常会更简练,而在需要根据情况动态修改对象的行为时,就必须使用装饰器。装饰器具有相同的接口名,因此可以为目标对象创建多个装饰器。本文通过一个简单的socket通信程序,来介绍通过装饰器增强socket的send()的行为。下面是服务器核心功能代码:

import socket

def respond(client):

response = input("Enter a value: ")

client.send(bytes(response, 'utf8'))

client.close()

if __name__ == "__main__":

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server.bind(('localhost', 2401))

server.listen(1)

try:

while True:

client, addr = server.accept()

respond(LogSocket(client))

finally:

server.close()

从上面的server代码可以看出,核心respond()函数只需要调用客户端接口的两个方法:send和close,下面是客户端代码:

import socket

if __name__ == "__main__":

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

client.connect(('localhost', 2401))

print("Received: {0}".format(client.recv(1024)))

client.close()

整个服务端程序一直在循环监听2401端口,一旦有客户端接入,服务器窗口显示Enter a value:输入通信的消息后,客户端接受消息,并打印输出。随着程序的演进,我们发现需要增强socket的两个功能:增加服务端日志记录功能

对传输数据进行压缩,提高发送效率

下面的UML图中,装饰器通过组合的方式访问需要wrap的接口函数,实现了两个装饰器LogSocket以及GzipSocket,来对socket中的send()函数进行功能增强。装饰器对核心函数send装饰的UML图

装饰器1,增强日志功能

LogSocket装饰器目标是在发送之前输出数据到服务器的控制台,具体代码如下:

class LogSocket:

def __init__(self, socket):

self.socket = socket

def send(self, data):

print("Sending {0} to {1}".format(

data, self.socket.getpeername()[0]))

self.socket.send(data)

def close(self):

self.socket.close()

在send被调用之前,装饰器将信息输出到屏幕上。而使用此装饰器非常方便,只需要修改server端中的一行代码即可:

respond(LogSocket(client))

装饰器2,压缩send数据

下面是第二个装饰器来压缩send的数据:

import gzip

from io import BytesIO

class GzipSocket:

def __init__(self, socket):

self.socket = socket

def send(self, data):

buf = BytesIO()

zipfile = gzip.GzipFile(fileobj=buf, mode="w")

zipfile.write(data)

zipfile.close()

self.socket.send(buf.getvalue())

def close(self):

self.socket.close()

这一版的send将输入数据进行压缩,然后发送给客户端。当我们就有多个装饰器时,可以动态的切换使用。

client, addr = server.accept()

if log_send:

client = LoggingSocket(client)

if client.getpeername()[0] in compress_hosts:

client = GzipSocket(client)

respond(client)

上面的代码,通过一个配置变量log_send来决定是时候装饰socket。类似的检查客户端时候接收压缩数据来决定是否增加压缩装饰器。

Python中的装饰器

装饰器在python中非常有用,但也要其他的替代选项。例如,我们可以使用monkey-patching获得类似的效果;单继承,可以将操作放入一个操作中。

在Python中,对函数进行装饰非常常用,因为函数也是对象。事实上,对函数的装饰非常常用,以至于Python提供一个特殊的语法使其很容易应用装饰器到函数中。

例如,我们可以寻找一种更加通用的方式来记日志, 不用logging,只需要在sockets上发送调用即可。下面的例子实现了一个装饰器:

import time

def log_calls(func):

def wrapper(*args, **kwargs):

now = time.time()

print("Calling {0} with {1} and {2}".format(

func.__name__, args, kwargs))

return_value = func(*args, **kwargs)

print("Executed {0} in {1}ms".format(

func.__name__, time.time() - now))

return return_value

return wrapper

def test1(a, b, c):

print("\ttest1 called")

def test2(a, b):

print("\ttest2 called")

def test3(a, b):

print("\ttest3 called")

time.sleep(1)

test1 = log_calls(test1)

test2 = log_calls(test2)

test3 = log_calls(test3)

test1(1, 2, 3)

test2(4, b=5)

test3(6, 7)

装饰器函数非常类似于前面的例子,在前面的例子中,装饰器讲一个socket对象装饰成另外一个socket对象,这次,装饰器将一个函数对象并且返回一个函数对象,代码有三个不同的任务组成:log_calls函数,接受另外一个函数

这个函数内部定义了一个新的函数,会增加新的工作,在调用原来的函数之前

新函数会返回

以上通过三个函数来展示装饰器的使用,我们把每个函数都放入装饰器当中,并返回一个新的函数,返回的函数与原来的函数名相同,有效替换了原函数。

这个语法允许动态构建函数对象,就像前面的socket例子;如果我们不替换名称,我们可以保留两个版本的函数对象。通常的情况下,需要永久性修改不同的函数。在这种情况下,Python支持一种特殊的语法,给函数定义的同时进行装饰。

@log_calls

def test1(a, b, c):

print("\ttest1 called")

这种方式的好处是,可以很容易看到函数在被定义的同时被装饰了;坏处是只能给自己定义的的函数进行装饰,如果给第三方库进行装饰,我们必须使用之前的语法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值