分类 设计模式 下的文章

使用修饰符来简化代码

常见的使用修饰符的场景是每个函数都有一段相同的逻辑,提取出来作为修饰符,那个比较常见,下面的例子是另外一个比较常见的使用修饰符的场景,可以提高代码可维护性。

有多种消息类型,每个类型对应一个消息类,新增消息类怎么添加到原先的代码逻辑呢,使用一堆if么

    message_type = message["type"]
    if message_type == "text":
        TextMessage(message).handle()
    elif message_type == "voice":
        VoiceMessage(message).handle()
    else:
        UnknownMessage(message).handle()

这样肯定可以,但是降低了代码可维护性,每次都要修改,太麻烦了。

这里我们可以使用修饰符了~

# coding=utf-8
MESSAGE_CLASSES = {}


def handle_for_message_types(message_type):
    def register(cls):
        MESSAGE_CLASSES[message_type] = cls
        return cls

    return register


class BaseMessage(object):
    def __init__(self, message):
        self.message = message


@handle_for_message_types("text")
class TextMessage(BaseMessage):
    def handle(self):
        print "text messsage"


@handle_for_message_types("voice")
class VoiceMessage(BaseMessage):
    def handle(self):
        print "voice message"


@handle_for_message_types("unknown")
class UnknownMessage(BaseMessage):
    def handle(self):
        print "unknown message"


def parse_message(message):
    cls = MESSAGE_CLASSES.get(message["type"], UnknownMessage)
    cls(message).handle()


parse_message({"type": "text", "content": "Hello world"})
parse_message({"type": "voice", "content": "http://xxx.com/voice.mp3", "length": 10})
parse_message({"type": "new_type", "content": "balabala"})

每次只增加代码就可以了,不用修改老代码~

里面的类修饰符相当于

@addID
class Foo:
    pass

class Foo:
    pass

Foo = addID(Foo)

To be lazy

平时人们做事的时候谁说lazy也是不好的,但是在程序设计的时候,lazy是一种有效的提供性能的方法,大致的思想就是需要的时候再计算,尽量的缓存计算结果。

copy on write

linux上传统的fork()函数直接把父进程所有的资源复制给新创建的进程。这种实现过于简单并且效率低下,因为它拷贝的数据也许并不共享,更糟的情况是,如果新进程打算立即执行一个新的映像,那么所有的拷贝都将前功尽弃。

Linux的fork()使用写时拷贝(copy-on-write)页实现。写时拷贝是一种可以推迟甚至免除拷贝数据的技术。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种技术使地址空间上的页的拷贝被推迟到实际发生写入的时候。在页根本不会被写入的情况下。举例来说,fork()后立即调用exec(),它们就无需复制了。

fork()的实际开销就是复制父进程的页表以及给子进程创建惟一的进程描述符。在一般情况下,进程创建后都会马上运行一个可执行的文件,这种优化可以避免拷贝大量根本就不会被使用的数据(地址空间里常常包含数十兆的数据)。这样就大大提高了性能。

Django的queryset

Django的文档说了,一个类似users = User.objects.filter(name="jack")的语句并不会立即去查询数据库,而是在以下操作的时候才回去查询数据库

  • 迭代
  • 切片,比如users[0:10]
  • 序列化(pickling)和缓存(caching)
  • repr
  • len() 返回数据条数,建议使用count()替代
  • list() 转换为列表
  • bool() 建议使用exists()替代

上面的uers变量,如果你没有对它进行这些操作,也就说那个查询数据库的操作永远不会进行。

而且queryset是支持链式调用的,比如说

>>> q = Entry.objects.filter(headline__startswith="What")
>>> q = q.filter(pub_date__lte=datetime.date.today())
>>> q = q.exclude(body_text__icontains="food")
>>> print(q)

最终只会执行一条sql语句,而不是三条。

Python的生成器

def generator():                      
    l = [1, 2, 3, 4]                  
    for i in l:                       
        print "in generator", i
        yield i * i                   

yield 是一个类似 return 的关键字,只是这个函数返回的是个生成器。

为了理解yield,你必须要理解:当你调用这个函数的时候,函数内部的代码并不立马执行,这个函数只是返回一个生成器对象。当你使用for进行迭代的时候,才会执行真正的代码。

第一次迭代中你的函数会执行,从开始到达 yield 关键字,然后返回 yield 后的值作为第一次迭代的返回值. 然后,每次执行这个函数都会继续执行你在函数内部定义的那个循环的下一次,再返回那个值,直到没有可以返回的。

直接print generator(),发现只有一个<generator object generator at 0x106b79e60>,而使用这样的代码,

for i in generator():       
    print "in for", i
    print i                 

你就会发现打印的是

in generator 1 
in for 1 
1

in generator 2 
in for 4 
4

in generator 3 
in for 9 
9

in generator 4 
in for 16 
16

Lazy evaluation

class Person:
    def __init__(self, name, occupation):
        self.name = name
        self.occupation = occupation
        self.relatives = self._get_all_relatives()

    def _get_all_relatives(self):
        # 非常耗时的计算
        sleep(10)
        ...

我们可以将这个耗时的操作推迟,在需要的时候才进行,然后缓存结果

class Person:
    def __init__(self, name, occupation):
        self.name = name
        self.occupation = occupation
        self._relatives = []

    @property
    def relatives(self):
        if not self._relatives:
            self._relatives = self._get_all_relatives()
        return self._relatives

    def _get_all_relatives(self):
        # 非常耗时的计算
        sleep(10)
        ...

更加pythonic的方法是使用修饰符

def lazy_property(fn):
    '''Decorator that makes a property lazy-evaluated.
    '''
    attr_name = '_lazy_' + fn.__name__

    @property
    def _lazy_property(self):
        if not hasattr(self, attr_name):
            setattr(self, attr_name, fn(self))
        return getattr(self, attr_name)
    return _lazy_property

class Person:
    def __init__(self, name, occupation):
        self.name = name
        self.occupation = occupation

    @lazy_property
    def relatives(self):
        # Get all relatives
        relatives = ...
        return relatives

参考 http://stevenloria.com/lazy-evaluated-properties-in-python/

Django的信号和观察者模式

今天想到给以前写的东西增加缓存支持,每次数据库发生变化之后主动的去修改缓存。当然最笨的方法就是在每次更新数据库的代码后面写一段更新缓存的代码,我们能不能在数据库被更新之后对外发一个信号呢,更新缓存的函数收到这个信号就知道数据库发生了变化。

这个在django中其实已经有了实现,就是siginal,用法大致的这样的

def my_callback(sender, **kwargs):
    print("Request finished!")

from django.core.signals import request_finished

request_finished.connect(my_callback)

这个其实就是经典的观察者模式的实现。

http://dongweiming.github.io/python-observer.html 有一段代码,我认为很好,直接贴过来了(其实后来发现原版在 https://github.com/faif/python-patterns/blob/master/observer.py )

# 这个是观察者基类
class Subject(object):
    def __init__(self):
        self._observers = []

    # 添加依赖的对象
    def attach(self, observer):
        if not observer in self._observers:
            self._observers.append(observer)

    # 取消添加
    def detach(self, observer):
        try:
            self._observers.remove(observer)
        except ValueError:
            pass

    # 这里只是通知上面注册的依赖对象新的变化
    def notify(self, modifier=None):
        for observer in self._observers:
            # 可以设置过滤条件,对不符合过滤条件的更新
            if modifier != observer:
                observer.update(self)


# 观察者类
class Data(Subject):
    def __init__(self, name=''):
        super(Data, self).__init__()
        self.name = name
        self._data = 0

    # python2.6新增的写法,获取属性为property,设置属性为(假设属性名字为x)@x.setter,删除为@x.deleter
    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, value):
        self._data = value
        self.notify()

# 这里有2个被观察者,也就是依赖的对象,每次Data有改变,这2个view都会变动
class HexViewer(object):
    def update(self, subject):
        print 'HexViewer: Subject %s has data 0x%x' % (subject.name, subject.data)

class DecimalViewer(object):
    def update(self, subject):
        print 'DecimalViewer: Subject %s has data %d' % (subject.name, subject.data)


if __name__ == '__main__':

    data1 = Data('Data 1')
    data2 = Data('Data 2')
    view1 = DecimalViewer()
    view2 = HexViewer()
    data1.attach(view1)
    data1.attach(view2)
    data2.attach(view2)
    data2.attach(view1)

    print "Setting Data 1 = 10"
    data1.data = 10
    print "Setting Data 2 = 15"
    data2.data = 15
    print "Setting Data 1 = 3"
    data1.data = 3
    print "Setting Data 2 = 5"
    data2.data = 5
    print "Update data1's view2 Because view1 is be filtered"
    data1.notify(modifier=view1)  
    print "Detach HexViewer from data1 and data2."
    data1.detach(view2)
    data2.detach(view2)
    print "Setting Data 1 = 10"
    data1.data = 10
    print "Setting Data 2 = 15"
    data2.data = 15

输出结果是

Setting Data 1 = 10
DecimalViewer: Subject Data 1 has data 10
HexViewer: Subject Data 1 has data 0xa
Setting Data 2 = 15
HexViewer: Subject Data 2 has data 0xf
DecimalViewer: Subject Data 2 has data 15
Setting Data 1 = 3
DecimalViewer: Subject Data 1 has data 3
HexViewer: Subject Data 1 has data 0x3
Setting Data 2 = 5
HexViewer: Subject Data 2 has data 0x5
DecimalViewer: Subject Data 2 has data 5
Update data1's view2 Because view1 is be filtered
HexViewer: Subject Data 1 has data 0x3
Detach HexViewer from data1 and data2.
Setting Data 1 = 10
DecimalViewer: Subject Data 1 has data 10
Setting Data 2 = 15
DecimalViewer: Subject Data 2 has data 15
[Finished in 0.1s]

如果要像django那样直接传递一个类而不是类实例,我们可以使用classmethod或者staticmethod,仿照http://www.the5fire.com/python-observer.html 我自己写了一个

#event.py
class Event(object):
    _observers = []

    def __init__(self, subject):
        self.subject = subject

    @classmethod
    def register(cls, observer):
        if observer not in cls._observers:
            cls._observers.append(observer)

    @classmethod
    def unregister(cls, observer):
        if observer in cls._observers:
            cls._observers.remove(observer)

    @classmethod
    def notify(cls, subject):
        event = cls(subject)
        for observer in cls._observers:
            observer(event)

#process.py
from event import Event


class Data(object):
    def __init__(self, value):
        self.value = value
        Event.notify("set value")


#main.py
from event import Event
from process import Data


def log(event):
    print "log", event.subject


def log1(event):
    print "log1", event.subject


Event.register(log)
Event.register(log1)
Data(123)

Event.unregister(log1)
Data(456)

输出是

log set value
log1 set value
log set value

是不是更像django里面的用法了呢~