Pro Django阅读笔记: 第二章 第一部分

2009-11-21 19:43

Pro Django是由Marty Alchin写的一本关于Django高级技巧的技术书籍. 由Apress出版, 我手头的是09年的第一版. 书籍相关信息及链接如下:

这本书不适合Django初学者学习, 但是对于已经入门的又需要进一步学习的开发者却很有针对性. 我准备在这儿写一下这本书的读书笔记, 也逼着自己能够继续看完这本书...第一章基本是闲扯, 虽然概念上很重要, 但是没有提供任何直接的技巧, 就略过了...这篇文章主要是关于第二章: Django中的python技巧.

Python中类的创建

动态类创建:使用type函数

要创建一个类, 比较大众化的方法是酱紫的:

>>> class NormalClass(object):
...     print 'Loading NormalClass...',
...     spam = 'eggs'
...     print "done"
...
Loading NormalClass... done
>>> NormalClass
<class '__main__.NormalClass'>
>>> NormalClass.spam
'eggs'

除了这种方法之外, 还可以用另外一种方式来创建类:

>>> DynamicClass = type('DynamicClass', (object,), {'spam': 'eggs'})
>>> DynamicClass
<class '__main__.DynamicClass'>
>>> DynamicClass.spam
'eggs'

上面这种方面里面用到了type函数. 一般来说, 我们是将一个对象作为参数传递给type函数, 这种情况下type函数的作用是给出这个对象的类型. 而当type接受三个参数时, type函数可视作是class语句的动态形式. 此时, 三个参数依次为新类名, 基类和包含新类属性的字典. 用type来动态构建类是很方便的.

用元类(metaclass)来改变类的属性

type实际上是一个元类, 元类是能够创建其它类的类. 元类编程所要实现的目的是在代码运行时而不是在程序编写时创建和改变代码. 例如上面的类定义的例子, 类的名字是代码运行时的一个变量. Python对元类编程的支持是通过允许在类定义里面包括一个元类而实现的. 如果一个类的定义里面包含了对__metaclass__属性的定义, 那么我们不会用默认的type方法来创建这个类, 而是用这个__metaclass__来创建我们定义的类:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print 'Defining %s' % cls
...         print 'Name: %s' % name
...         print 'Bases: %s' % (bases, )
...         print 'Attributes:'
...         for (name, value) in attrs.items():
...             print '    %s: %r' % (name, value)
...
>>> class RealClass(object):
...     __metaclass__ = MetaClass
...     spam = 'eggs'
...
Defining <class '__main__.RealClass'>
Name: RealClass
Bases: (<type 'object'>,)
Attributes:
__module__: '__main__'
__metaclass__: <class '__main__.MetaClass'>
spam: 'eggs'
>>> RealClass
<class '__main__.RealClass'>

注意哦, 这个类没有实例化, 我们在创建这个类的过程中就执行了MetaClass中的代码. 在这个例子中, MetaClass定义了一个__init__方法, 而在Django内部代码里面, 更多的元类编程是在__new__方法中完成的. 另外, 关于python的元类编程(metaprogramming), 下面有三篇文档, 包括一篇wikipedia上的介绍和IBM DevWorks上两篇不错的文章:

实际的元类编程例子: Django中的模型定义

下面是一段典型的Django创建模型的代码:

class Ox(models.Model):
horn_length = models.IntegerField()

class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"

现在我们一起来体会下这段代码执行时到底干了什么. 首先需要说明的是, Ox这个类的基类是models.Model, 相关代码在django/db/models/base.py. 而Model这个类是由ModelBase这个元类生成的:

class ModelBase(type):
"""
    Metaclass for all models.
    """
def __new__(cls, name, bases, attrs):
+---170 lines: super_new = super(ModelBase, cls).__new__------------------

class Model(object):
__metaclass__ = ModelBase
"""some more code"""

这种布局, 或者说代码结构的好处, 在于在Ox模型的定义过程中隐藏了元类声明, 因为这理应是比较低层的内容, 不应该让普通用户过多干涉. 另一个比较显著的好处在于Ox模型能够方便的继承models.Model里定义的各种方法. 当然, 这样布局后, Ox模型的定义也很清晰易懂, 更pythonic. 读完上面的代码, 会发现类的定义里面还有一个子类, 叫Meta. 简单看了下代码, 虽然名字一样, 但是这段似乎和元类编程没有直接的关系. 尽管Meta里面定义的内容是在ModelBase的__new__方法里面处理而作为_meta来保存的.

关于Django的ORM的进一步讨论会在第三章里面进行~

各种数据类型的通用方法

注意下, 这些是约定俗称的通用方法, 而不是确定一定以及肯定有的方法. 对于Django来说, 这些类型都是具有这些方法的, 对于较大的Django的库来说也是这样. 但是不要太想当然...

可调用的类型

此处, 可调用的类型包括函数, 类和类方法都是可调用的. 另外, 如果一个类定义了__call__方法, 那么这个类的实例也是可调用的:

>>> class Multiplier(object):
...     def __init__(self, factor):
...         self.factor = factor
...     def __call__(self, value):
...         return value * self.factor
...
>>> times2 = Multiplier(2)
>>> times2(5)
10
>>> times3 = Multiplier(3)
>>> times3(10)
30

在这一段代码中, 我们首先定义了一个乘法器的类, 这个类初始化时接受一个参数, 乘数因子. 这个类的实例能够被调用, 接受一个参数, 被乘数. 例如, 在第7行, 括号中的2是类初始化参数. 初始化后times2是一个Multiplier类的一个实例. 之后我们调用times2(5)是将5作为参数交给Multiplier类中的__call__方法. 另外提一句, Python还提供了一个callable函数来判断一个对象是不是可以被调用的.

字典

一般字典类型或者类似字典类型都会提供下面三种方法: __contains__(self, key), __getitem__(self, key), __setitem__(self, key, value). 其中__contains__方法一般通常是通过in运算符使用.

可迭代的类型

可迭代的类型是指能够送进for循环的类型. 列表, 元组, 字典等都是可迭代的. 如果你的类想要变成一个可迭代的类型, 那么你需要定义自己的__iter__方法:

>>> class Fibonacci(object):
...     def __init__(self, count):
...         self.count = count
...     def __iter__(self):
...         a, b = 0, 1
...         for x in range(self.count):
...             if x < 2:
...                 yield x
...             else:
...                 c = a + b
...                 yield c
...                 a, b = b, c
...
>>> for x in Fibonacci(5):
...     print x,
...
0 1 1 2 3
>>> for x in Fibonacci(10):
...     print x,
...
0 1 1 2 3 5 8 13 21 34

今天就酱紫吧, 明天有时间继续.