应用技巧

python特性无数种组合起来,可以完成很多很多事情,因此这里展示的一些,肯定不会考虑到python许多特性组合的完整列表。然而在django中,有一些用到的,是贯穿于本书中其他技巧的一个基础。

跟踪子类

考虑一个应用,在任何时候,访问一个特定类的所有子类列表。metaclass是一个非常好的处理手段,但是存在一个问题。记住,每一个带有__metaclass__属性的类都要处理,包括新的基类,他们是不需要被注册的(只有它的子类要被注册)。

要处理好这个问题,就要作些额外的处理,但这样作也是很直接了当的,同时也是很有益处的。

>>> class SubclassTracker(type):
...     def __init__(cls, name, bases, attrs):
...         try:
...             if TrackedClass not in bases:
...                 return
...         except NameError:
...             return
...         TrackedClass._registry.append(cls)
>>> class TrackedClass(object):
...     __metaclass__ = SubclassTracker
...     _registry = []
...
>>> class ClassOne(TrackedClass):
...     pass
...
>>> TrackedClass._registry
[<class '__main__.ClassOne'>]
>>> class ClassTwo(TrackedClass):
...     pass
...
>>> TrackedClass._registry
[<class '__main__.ClassOne'>, <class '__main__.ClassTwo'>]

这 个metaclass执行了两个函数。首先,try块确保父类,TrackedClass,已经定义好了。如果没有的话,就抛出NameError异常, 这个过程就表明metaclass当前正处理TrackedClass。TrackedClass那还能处理更多的东西,但是这个例子为了简单,忽略掉 了,只要通过注册就行了。

另外,if语句确保另一个类不再显式指定SubclassTracker作为它的__metaclass__属性。这个程序只是想注册TrackedClass的子类,其他不适合这个程序要求的类不注册。

任何程序开发人员,想使用类似django的declarative语法的,都能使用这个技巧来编写基类,然后通过它来创建特定的类。django使用这个处理了model和form两个机制,以便它的declarative语法能在框架中保持一致。

如 果python让这些无障碍地通过测试,那么类就加入到注册表中,所有TrackedClass的子类能在任何时候从注册表中提取。 TrackedClass的任何子类都将出现在这个注册表中,不管子类在哪里定义的。执行这个类定义的过程就开始注册它,应用程序能导入任何有这些类和 metaclass的模块。

尽管注册表与简单的列表相比提供了更多的特性,django还是用这个技巧做了扩展来注册model,因为model必须扩展最基本的基类。

一个简单的插件结构

在复用的程序方面,通常鼓励通过插件的机制,定义良好的核心组件特性,扩展这些特性,两者结合起来(增加软件的弹性)。要求插件结构库有良好的扩展性,这看起来有些困难,但你的代码编写起来相当的容易和简单。总而言之,一个成功的,松耦合的插件结构只要提供下面3样东西。

1)清晰,可读性好,声明一个插件,需要使用它的代码,容易使用。
2)能简单访问所有被声明的插件
3)在插件和使用插件的代码之间,有一个中间部分需要定义,插件在这里注册和访问。

只要符合上面这个3个条件和对python的能力上有良好的理解,编写一些简单的代码就能满足这些要求。

class PluginMount(type):
    def __init__(cls, name, bases, attrs):
        if not hasattr(cls, 'plugins'):
            # This branch only executes when processing the mount point itself.
            # So, since this is a new plugin type, not an implementation, this
            # class shouldn't be registered as a plugin. Instead, it sets up a
            # list where plugins can be registered later.
            cls.plugins = []
        else:
            # This must be a plugin implementation, which should be registered.
            # Simply appending it to the list is all that's needed to keep
            # track of it later.
            cls.plugins.append(cls)

全部要作的就是,跟踪登记过的插件,把他们保存到一个列表 -- plugins属性。剩下的事情就是考虑怎样完成前面列出的每一点。下面的例子,我们将创建一个应用,验证用户口令的强壮性。

第一步是一个中间访问点,我称之为“挂载点”(mount point),from which each side of the equation can access the other。正如前面提到的,它依赖于metaclass,因此这是一个很好的开始。

class PasswordValidator(object):
    """
    Plugins extending this class will be used to validate passwords.
    Valid plugins must provide the following method.

    validate(self, password)
        Receiveds a password to test, and either finished silently or raises a
        ValueError if the password was invalid. The exception may be displayed
        to the user, so make sure it adequately describes waht's wrong.
    """
    __metaclass__ = PluginMount

如 果你想作,你还能加更多的东西进来,但是这里只是让这个能正确处理起来就可以了。当要增加更多的内容时,只要知道各插件进行子类化,继承你在这个类中定义 的一切。在这个类中,提供更多的属性和所有插件都能用上的帮助方法是非常方便的,各个插件任何时候都能覆盖它们,因此没有什么是固化下来,一成不变的。

同时也要注意插件的挂载点应该包含有关插件怎样使用(和有什么内容)的文档,当然这不是特别要求的,而是一个好的编程实践,因为这样作有助于其他人实现这个插件接口更容易。如果所有注册的插件和指定的约定相一致,系统就能正常运转,必须确保要指定这个约定。

下一步,建立你的代码来访问注册过的任何插件,任何方式使用他们对程序来说很有意义。既然挂载点已经维护它拥有的已知插件,它所作的就是遍历插件,使用合适的方法和属性完成手头上相应的任务。

def is_valid_password(pasword):
    """
    Return True if the password was fine, False if where was a problem.
    """
    for plugin in PasswordValidator.plugins:
        try:
            plugin().validate(password)
        except ValueError:
            return False
    return True

def get_password_errors(password):
    """
    Return a list of messages indicating any problems that were found
    with the password. If it was fine, this returns an empty list.
    """
    errors = []
    for plugin in PasswordValidator.plugins:
        try:
            plugin().validate(password)
        except ValueError, e:
            errors.append(str(e))
    return errors

这些例子的代码有点复杂,因为它们要处理错误,但是还有一个非常简单的东西要作,在列表上进行迭代将遍历每个插件。剩下的就是构建插件,处理验证行为。

class MinimumLength(PasswordValidator):
    def validate(self, password):
        "Raises ValueError if the password is too short."
        if len(password) < 6:
            raise ValueError('Passwords must be at least 6 characters.')

class SpecialCharacters(PasswordValidator):
    def validate(self, password):
        "Raises ValueError if the password doesn't contain any special characters."
        if password.isalnum():
            raise ValueError('Passwords must contain on special character.')

是的,的确是很容易!下面是怎样使用这些插件。

for password in ('pass', 'password', 'p@ssword!'):
    print ('Checking %r ...' % password),
    if is_valid_password(password):
        print 'valid!'
    else:
        print # Force a new line
        for error in get_password_errors(password):
            print ' %s' % error

Checking 'pass' ...
 Passwords must be at least 6 characters.
 Passwords must contain on special character.
Checking 'password' ...
 Passwords must contain on special character.
Checking 'p@ssword!' ... valid!

现在要怎么办?

对 python提供的特性有了一个扎实深入的理解,你就可以随时准备进入django,去了解django运用许多这些特性以及你怎样把这些技巧用到自己的 代码编程中去。django model构成了django大多数程序的基础,它充分利用了python这些高级特性。