123
 123

Fri 03 April, 2009

Click here to bookmark this link.Channel Image22:47 This Week in Edge Rails» Riding Rails - home

March 28, 2009 – April 3, 2009

Things are still fairly quiet out on the edge: 5 commits this week. The work for Rails 3.0 hasn’t been merged back to the master branch yet, so if you need any of the post-2.3 patches you can still just update to edge without worrying about major upheaval.

Changes

  • When are 24 hours not 24 hours? When you add them across a DST boundary in Rails – at least, until one of this week’s commits. You can read more details of what was going on over at the Lighthouse ticket.
  • Looked at your development mode /rails/info/properties view lately? Look again after this commit and it will show all of the installed Rack middleware pieces in your application.

add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image15:02 gem Browser» Suave's Blog
看 gem 文档除了 gem_server 发现了一个 bdoc, 还不错. 安装简单: git clone github.com/manalang/bdoc.git gem build bdoc.gemspec gem install bdoc-0.2.0.gem 完成 使用只要在 term 里执行 bdoc 即可...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl

Wed 01 April, 2009

Click here to bookmark this link.Channel Image02:14 Kindle 2 Review» Lovable Lyle
I’ve had my Kindle for about three weeks now, and so I feel like I’ve used it enough at this point to be able to write an objective review. I’ll try to skip the things that you presumably already know from more general reviews and focus on my experience with it. The Kindle Itself For starters, let’s [...]
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl

Tue 31 March, 2009

Click here to bookmark this link.Channel Image22:59 敏捷,从装修开始» 梦想风暴

很多人和我们聊敏捷,话里话外,都是在问,敏捷从哪干起对原有的开发模式影响最小。持续集成?没有测试起不到足够的作用;TDD?耦合大了,不好测;结对编程?让人觉得浪费。……

我有一个答案:装修。

我们现在合作的团队分成两个部份,在两个不同的楼里面。Darwin在春节之前,从一个楼到另外一个楼去工作,结果状态下降很快,每天感到特别疲惫;春节之后,我也到了那个楼里面,结果状态也直线下降,同样疲惫不堪。最初我们以为是出差的时间长了,导致的状态下降。我们的生力军——亮亮——春节之后才加入到这个项目,不久前,也到了那个楼里面,结果,他的反应比我们还剧烈,第二天疲惫得差点没有起来床。三个人坐在一起发着牢骚,突然我们意识到,也许是环境的问题。

原来,这个开发大厅是不通风的,所以,大厅里的空气很浑浊,所以,在里面工作的时间稍微长一点,大脑就开始进入浑沌的状态。意识到这一点,我们建议把窗户打开,考虑到我们咨询师的身份,他们采纳了我们的建议。当一阵清风吹入大厅,让人感觉突然充满了力量。接下来的几天,我们的工作状态明显回升了许多。

很多时侯,我们都在考虑这个实践会给我们带来怎样的价值,那个时间到底可以起到什么样的作用。很多人却忽略了环境的价值。我曾经的一个领导认为,就应该把程序员关在一个不分昼夜的小屋里面,这样,他们的才能全力以赴的工作。以我现在的想法来看,这是典型的混帐逻辑。当你不把人当人,还指望人家为你做出最大贡献,什么逻辑。

很多人到我们公司参观过,特别是在我们公司承办了Open Party之后。很多人都表达过对这样工作环境的一种喜爱。有人专门向我们学习装修。当我刚刚来到客户这边,我对和我们公司一样的玻璃白板表示出浓厚的兴趣,后来听说,就是从我们那里学来的。不仅仅是白板,程序员们也开始围坐在一起,以方便大家的交流。此外,我还还听说,客户新建的基地,内部装修就参考了我们公司的风格。

装修,用不着动代码,也不用加测试,应该说,它对现有开发模式的影响微乎其微,但却可以让程序员更高效的工作。不见得高效的装修都是一致的,有像我们一样的开放环境,也有公司为每个程序员提供一个单独的房间,选择一种适合自己程序员发挥是最合适的。

装修,不一定都是大兴土木,也许,就只是打开一扇窗!






add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image21:48 Doing a startup in the real world» Suave's Blog
DHH 的一个 speech VIDEO: David Heinemeier Hansson - FOWA Dublin 2009 from Carsonified on Vimeo. SLIDE: David Heinemeir Hanson View more presentations from Carsonified Team. ...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image15:03 关于pro django» 动态感觉 静观其变
最近网友农夫三拳给我一个pro django电子书,我看了一下,相当令人惊奇,很有深度,对了解python内部一些高级特性很有帮助,对django内部运用这些python高级特性的原理作了很好的介绍。尤其是第2章,标题就是django is python,就像当年说j2ee is java一样,对pythoner,尤其是django fans都是一个很好引导。我已经把这一章作了一个快速的翻译,因为本章英语很多地方较难理解,自己水平也就一般,不太好翻译的,有的用意译,有的是直译,翻译不好的,请热心人多多指正。...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (7)» 动态感觉 静观其变
Decorators

改变一个函数的行为另一种常见的方式是用另一个函数来装饰(decorate)它。这也经常称之为“包装”一个函数,因为装饰器是被设计成在要调用原生函数之前或之后运行附加的代码。

装饰器背后的关键原则是他们接受可调用的内容,返回一个新的可调用的内容。由装饰器返回的函数是在被装饰的函数被调用时执行。要注意的是,确保原生函数在处理过程不遗漏,因为没有办法在没有重载模块的情况下get it back。

装 饰器可以采用多个方法,或者应用在你直接定义的函数上,或是其他地方定义的函数。到python 2.4时,对一个新定义的函数,装饰器可以使用一个特殊的语法。之前的python版本,有些稍微不同的语法,但是这两种情况,代码(的效果)是一样的。 不同的地方就是作用于函数上的装饰器的语法。

>>> def decorate(func):
...     print 'Decorating %s...' % func.__name__,
...     def wrapped(*args, **kwargs):
...         print "Called wrapped function with args:", args
...         return func(*args, **kwargs)
...     print 'done!'
...     return wrapped
...

#Syntax for python 2.4 and higher

>>> @decorate
... def test(a, b):
...     return a + b
...
Decorating test... done!
>>> test(13, 72)
Called wrapped function with args: (13, 72)
85
>>> #Syntax for python 2.3
...
>>> def test(a, b):
...     return a + b
...
>>> test = decorate(test)
Decorating test... done!
>>> test(13, 72)
Called wrapped function with args: (13, 72)
85

这 个例子中的老式语法是装饰函数的另一种技巧,可以用在那些@不能用的地方。适用于一个函数在其他地方已经声明过了,而想利用一下装饰器。这样一个函数就能 被传入给一个装饰器了,然后返回一个新函数,所有的东西都在里面包装起来了。使用这个技巧,任何可调用的东西,不管来自哪里或作什么,都能被任何装饰器包 装。

使用过量参数装饰

有时,一个装饰器需要一些附加的信息来决定应该怎么处理接受的函数。使用老式的装饰语法,或装饰任何函数,这个任务都是很容易处理的。简单的声明装饰器,接受附加的参数,要求的信息就能配置到要包装的函数中去。

>>> def decorate(func, prefix='Decorated'):
...     def wrapped(*args, **kwargs):
...         return '%s: %s' % (prefix, func(*args, **kwargs))
...     return wrapped
...
>>> simple = decorate(test)
>>> customized = decorate(test, prefix='Custom')
>>> simple(30, 5)
Called wrapped function with args: (30, 5)
'Decorated: 35'
>>> customized(27, 15)
Called wrapped function with args: (27, 15)
'Custom: 42'

然而,python 2.4的装饰器语法就麻烦了。当使用新式语法的时候,装饰器总是只接受一个参数:被包装的函数。有一个方法把附加的参数放入装饰器中,但是我们要先停一下,先讨论一下"partials"。

函数的partial应用

典型的,函数在执行时,要带上所有必要的参数进行调用。然后,有时参数可以在函数被调用之前提前获知。这种情况下,一个函数有一个或多个参数预先就能用上,以便函数能用更少的参数进行调用。

出于这个目的,python 2.5包括了partiall对象,作为functools模块的一部分。它接受一个可调用的(),可以包含任意个数附加参数,返回一个新的可调用的(),行为就像原生的一样,只是不要指定那些预加载的参数。

>>> import functools
>>> def add(a, b):
...     return a + b
...
>>> add(4, 2)
6
>>> plus3 = functools.partial(add, 3)
>>> plus5 = functools.partial(add, 5)
>>> plus3(4)
7
>>> plus3(7)
10
>>> plus5(10)
15

对于python 2.5之前的版本,django提供了它自己partial实现,curry函数,在django.utils.functional模块中。这个函数可用在python 2.3和之后的版本。

返回装饰器的遗留问题

前 面提到了,装饰器使用python 2.4的语法,在接受附加参数时,会有一个问题,因为语法上只能对函数提供单一的参数。使用partial技巧后,就有可能在一个装饰器上预先加载参数。 前面给的装饰器,下面用curry(第9章有详细说明)来提供给采用python 2.4语法的装饰器参数。

>>> from django.utils.functional import curry
>>> @curry(decorate, prefix='Curried')
... def test(a, b):
...     return a + b
...
>>> test(30, 5)
'Curried: 35'
>>> test(27, 15)
'Curried: 42'
>>>

这还是相当不方便,因为每次用来装饰另一个函数时函数需要通过curry来运行。更好的方法是把这个函数直接装配到装饰器自身里。这当然在装饰器上要写更多的代码,但是包含这些代码使得用起来容易一些。

这个技巧是在另一个函数中定义一个装饰器,它接受参数的。这个新的函数,返回的是装饰器,然后它可以由python标准装饰器处理。反过来,这个装饰器返回的函数,是在装饰之后,有剩余的代码来使用。

说起来相当抽象,考虑下面的代码,它提供了前面例子相同的功能,但是不依赖curry,处理起来很容易。

>>> def decorate(prefix='Decorated'):
...     # The prefix passed in here will be
...     # available t all the inner functions
...     def decorator(func):
...         # This is called with func being the
...         # actual function being decorated
...         def wrapper(*args, **kwargs):
...             # This will be called each time
...             # the real function is excuted
...             return '%s: %s' % (prefix, func(*args, **kwargs))
...         # Send the wrapped function
...         return wrapper
...     # Provide the decorator for Python to use
...     return decorator
...
>>> @decorate('Easy')
... def test(a, b):
...     return a + b
...
>>> test(13, 17)
'Easy: 30'
>>> test(89, 121)
'Easy: 210'

这个技巧对于已知的参数(arguments are expected)非常有意义。如果装饰器用在没有任何参数的地方,为了正常使用括号是必备的。

>>> @decorate()
... def test(a, b):
...     return a + b
...
>>> test(13, 17)
'Decorated: 30'
>>> test(89, 121)
'Decorated: 210'
>>> @decorate
... def test(a, b):
...     return a + b
...
>>> test(13, 17)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: decorator() takes exactly 1 argument (2 given)

第2个例子失败是因为我们没有先调用decorate。因此,所有随后对test的调用把参数都发送给了decorator,而不是test。因为没有匹配成功,python就抛出了一个错误。这种情况调试起来有点困难,因为抛出的异常是依赖于被包装的函数。

带或不带参数的装饰器

装饰器的另一个选择是只提供单一的装饰器作用于前面的两个例子:带参数和不带参数,这有点复杂,但值得探索。

这 样作的目标是允许装饰器带参数或不带参数调用,因此假定所有的参数都是可选的;任何要求参数的装饰器不能使用这个技巧。记住,一个基本的原则,在列表的开 头添加一个附加的可选参数,它是被装饰的函数。然后就是装饰器的代码结构上包括必要的逻辑来判断它是否增加参数来调用,或是否装饰目标函数。

>>> def decorate(func=None, prefix='Decorated'):
...     def  decorated(func):
...         # This returns the final, decorated
...         # function, regardless of how it was called
...         def wrapper(*args, **kwargs):
...             return '%s: %s' % (prefix, func(*args, **kwargs))
...         return wrapper
...     if func is None:
...         # The decorator was called with arguments
...         def decorator(func):
...             return decorated(func)
...         return decorator
...     # The decorator was called without arguments
...     return decorated(func)
...
>>> @decorate
... def test(a, b):
...     return a + b
...
>>> test(13, 17)
'Decorated: 30'
>>> @decorate(prefix='Arguments')
... def test(a, b):
...     return a + b
...
>>> test(13, 17)
'Arguments: 30'

这要求所有传递给装饰器的参数作为一个关键字参数来传递,通常这也有增加了代码的可读性。一个不足就是采用这个方法对每个装饰器来说,许多“boilerplate”不断要重复。

像python里大多数"boilerplate",可能可以提炼出公共部分作为复用的形式,因此新的装饰器更容易定义,作用于另一个装饰器。下面的函数就是被用来装饰其他的函数,接受参数或不带参数从而提供所有必要的功能。

>>> def optional_arguments_decorator(real_decorator):
...     def decorator(func=None, **kwargs):
...         # This is the decorator that will be
...         # exposed to the rest of your program
...         def decorated(func):
...             # This returns the final, decorated
...             # function, regardless of hwo it was called
...             def wrapper(*a, **kw):
...                 return real_decorator(func, a, kw, **kwargs)
...             return wrapper
...         if func is None:
...             # The decorator was called with arguments
...             def decorator(func):
...                 return decorated(func)
...             return decorator
...         # The decorator was called without arguments
...         return decorated(func)
...     return decorator
...
>>> @optional_arguments_decorator
... def decorate(func, args, kwargs, prefix='Decorated'):
...     return '%s: %s' % (prefix, func(*args, **kwargs))
...
>>> @decorate
... def test(a, b):
...     return a + b
...
>>> test(13, 17)
'Decorated: 30'
>>> test = decorate(test, prefix='Decorated again')
>>> test(13, 17)
'Decorated again: Decorated: 30'

这个例子使得装饰器的定义更简单,更直接。这种装饰器的结果和前面的例子行为一样,但是它能带或者不带参数。最值得注意的变化就是新的技巧,被定义的真正的装饰器,接受的是下面3个值:
1)func -- 使用新生成的装饰器产生的被装饰的函数
2)args -- 传递给函数的包含位置参数的元组
3)kwargs -- 传递给函数的包含关键字参数的字典

然后,有一个重要的方面,args和kwargs,这两个装饰器接受的,作为位置参数来传递,并没有通常用的星号。然后,当把它们传递给被包装的函数,星号必须带上,确保函数正确接受,而不用知道装饰器是如何工作的。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (8)» 动态感觉 静观其变
Descriptor

引用一个对象的属性是是直接访问属性,没有什么复杂的。直接获取和设置属性影响到名字空间里的值。有时当访问这些值时,还有些事要处理的。

1)从一个复杂的出处获取数据,诸如数据库或配置文件
2)把一个简单的值转换成一个复杂的对象或数据结构
3)为对象定制值
4)在保存到数据库中,把值转变成保存前的格式。

在 有些编程语言里,这些行为是通过创建一些附加的实例方法来访问需要的属性。while functional, 这种方法会带来一些问题的。对于starter,这些行为相比于他们附属的实例的某些方面,对存储在属性里的数据结构关联度更紧。通过要求对象配备访问这 个数据的方法,包含这个行为的对象必须在实例方法中提供必要的代码。

另一个比较大的问题是当本来是简单使用的属性突然需要更多的行为特征怎么办?从一个简单的属性改变成方法,所有属性的引用都要发生改变。为了避免这个问题,这些语言的编程者,采纳了一些标准的实践来创建属性访问的方法以便底层实现的改变不影响已经存在的代码。

为 了一个属性访问的改变,而去查看你的很多代码,这永远不是招人喜欢的事。因此python提供了一个方法来解决这个问题。宁可要求对象负责属性的访问,也 不要属性自己来提供这种行为。Descriptor就是这样特殊类型的对象,当依附于一个类,就能介入什么时候访问属性,提供任何更多的行为。

>>> import datetime
>>> class CurrentDate(object):
...     def __get__(self, instance, owner):
...         return datetime.date.today()
...     def __set__(self, instance, value):
...         raise NotImplementedError("Can't change the current date.")
...
>>> class Example(object):
...     date = CurrentDate()
...
>>> e = Example()
>>> e.date
datetime.date(2009, 3, 27)
>>> e.date = datetime.date.today()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in __set__
NotImplementedError: Can't change the current date.

创建一个Descriptor,就像创建一个新式的类(从object继承)一样简单。最少指定下面要说的方法。descriptor类还能包括其他的属性或方法来执行负责的任务。下列的方法组成了激活这种行为的约定。

__get__(self, instance, owner)

当提取一个属性的值时,value = obj.attr,这个方法就调用了。允许descriptor在返回值之前作一些额外的事。除了self代表descriptor对象自身,这个getter方法接受2个参数。

1)instance

这是要引用的包含属性的实例对象,如果这个属性是类属性而不是实例属性的话,这个参数就是None

2)owner

要分配descriptor的类,它总是一个类对象。

instance参数是用来决定descriptor是否从一个对象或它的类中被访问。如果instance是None,这个属性就是从类中被访问,而不是从实例访问,如果descriptor没有按本应该的方式来访问,就抛出异常。

同时,通过定义这个方法,你用descriptor来负责提取和返回一个值给请求值的代码。失败的话就强制python返回一个默认的返回值None。

注意,默认情况下,当声明一个属性时,descriptor不知道他们被给定的是什么名称。django model提供了一个方法来得到名称,这会在第3章有描述,但是除了这个,descriptor只知道它们的值,而不是它们的名称。

__set__(self, instance, value)

当设置一个值给一个descriptor(obj.attr = value),这个方法就被调用以便能进行更多的处理。像__get__一样,这个方法接受除了标准的self,还有2个参数。

1)instance -- 实例对象,包含要引用的属性,永远不能是None。

2)value -- 被分配的值。

应 该注意descriptor的__set__方法只在属性分配到一个对象时才被调用,分配给一个类的属性(此时descriptor开始分配) 时,__set__是不被调用的。这个行为通过设计,禁止descriptor完全控制它的访问。外部的代码还可以分配一个值给第一次赋值的类,来替换 descriptor。

同时注意,从__set__返回的值是不相关的,这个方法只是负责正确的保存被配置的值。

跟踪实例数据

因为descriptor能简化属性的存取,你需要注意什么时候给附属的对象设置值。你不能只是简单的用settattr来设置对象的值。这样作的后果,再次调用descriptor会导致无穷的循环。

python 提供了另外一种方法访问一个对象的名字空间,__dict__属性。python所有的对象上都有__dict__,它是一个字典,存放对象名字空间所有 值。直接访问这个字典,就可以绕开不用python标准处理属性的方式,包括descriptor。使用这个技巧,descriptor可以设置一个对象 的值,不用触发它自己。考虑下面的例子。

>>> class Descriptor(object):
...     def __init__(self, name):
...         self.name = name
...     def __get__(self, instance, owner):
...         return instance.__dict__[self.name]
...     def __set__(self, instance, value):
...         instance.__dict__[self.name] = value
...
>>> class TestObject(object):
...     attr = Descriptor('attr')
...
>>> test = TestObject()
>>> test.attr = 6
>>> test.attr
6

不足的是,这种技巧要求显式把属性名称指明给descriptor。你能用一些metaclass技巧来处理,Django的model系统展示了这种技法(第3章有讨论)。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (5)» 动态感觉 静观其变
文件

正如先前提到的,文件是存取信息的常见方式,而且许多python库 提供了"file-like"类文件对象,以便能使用文件相关的函数。一个类文件对象不需要配备下面所有的方法,只要那些需要的正确函数。至于文件的约 定,对象自由实现读,写或者读写。不是所有这里列出的方法,只需要最常用的即可。文件方法的完整列表参见python 标准库文档,详情可到文档中查看。

read(self, [size])

从对象或者它的信息源获取数据。选项size参数是获取的字节数,如果省略,这个方法就返回尽可能多的字节数。(通常是整个文件,如果可能的话,或者网络接口所容许的字节数)

write(self, str)

把指定的str,写到对象或者它的信息源。

close(self)

关闭文件,然后文件就不再被访问了。用来释放已经被分配的内存,提交对象的内容到磁盘上,或者仅仅是满足约定。即使这个方法不提供任何特殊功能,它也要提供的,防止不必要的错误。

** 一个非常松散的约定 **

类 文件(File-like)对象有很多不同的形式,因为这个约定是python中定义最松散的。有一些特性,从缓存输出到随机存取数据,有时是没有相对应 的,因此这种情况下对象将不只实现这些对应的方法。比如,django的HttpResponse对象,第7章要描述的,只允许按序列写,因此它不要实 现,read(), seek()或tell(),当使用时用这些特定的文件操作库时,会引发错误。

****

迭代

如果把一个对象传入内置函数iter(),返回一个迭代器的话,这个对象就是可迭代的(iterable),iter()通常是隐式被调用,比如在for循环中使用。所有的列表,元组和字典都是可迭代的,任何采用新的类定义方式的类都可以成为可迭代的,只要定义了下列的方法

__iter__(self)

这个方法是由iter()隐式调用的,它负责返回一个迭代器,python用迭代器从一个对象中获取数据明细。通过一个生成器函数这样的定义方法,返回的迭代器是隐藏的,生成器看下面的“Generation”部分。

>>> 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

迭代器

当伴随一个对象iter()被调用时,是期待返回一个迭代器,然后为这个对象从序列中获取明细。迭代器是一个简单的方法,它是单向的游历可用的明细,一次只返回一个,直到没有更多的可用。对更大的集合,一个个访问明细比一次把他们放入一个列表中更有效。

next(self)

迭代器只要求有这个方法,它返回一个单一的明细。这个明细怎样获取依赖于迭代器的设计,但是它必须只返回一个明细项。这个明细项被调用迭代器的代码处理过之后,要获取下一个明细项时,next()再次被调用。

一 旦没有明细项返回,next()也负责告诉python停止使用这个迭代器,并从循环中移出。这是由抛出StopIteration异常来完成。 python将继续调用next(),直到异常抛出。这是一个无限循环。StopIteration能优雅的停止循环或者用另外一个异常来表明更严重的问 题。

class FibonacciIterator(object):
    def __init__(self, count):
        self.a = 0
        self.b = 1
        self.count = count
        self.current = 0

    def next(self):
        self.current += 1
        if self.current > self.count:
            raise StopIteration
        if self.current < 3:
            return self.current - 1
        c = self.a + self.b
        self.a = self.b
        self.b = c
        return c

    def __iter__(self):
        # Since it's already an iterator, this can return itself.
        return self

class Fibonacci(object):
    def __init__(self, count):
        self.count = count

    def __iter__(self):
        return FibonacciIterator(self.count)

注意,迭代器不需要为了正确的使用而去明显地定义__iter__(),但是要包含那个方法,允许迭代器在循环中正确地使用。

生成器

正如在Fibonacci例子演示的,生成器是一个方便的快捷方式,不必定义一个单独的类就可以用来创建一个迭代器。python使用yield语句,来标识一个函数作为生成器,它的行为和其他的函数是有所不同的。

当 调用一个生成器的函数时,python不会立即执行一点代码。相反,它返回一个迭代器,next()才会开始调用函数的内容,直到第一个yield发生的 地方。yield语句给出的表达式,是被用作next()方法的返回值,从而不管什么代码调用生成器,都能得到一个值。

下一次 next()在迭代器中被调用,python继续在生成器离开的位置执行这个生成器函数,同时原封不动地保留所有变量。只要python遇到yield语 句就重复上述过程,典型的情况就是使用一个循环函数来保持yielding变量。当这个函数不再生成一个值时,就算结束,迭代器自动抛出 StopIteration,表明循环应该结束,代码的其他部分继续执行。

序列

可 迭代简单地描述一个一次获取一个值的对象,(这段很怪,意译了)(有一种数据类型)他们的值通常是通过一个单一对象提前获知与收集的。这就是序列。最常见 的类型是列表和元组。作为可迭代的类型,序列也是用__iter__()方法来一个一个返回他们的值,但是这些值是提前获知的,有些附加的特性是可用的。

__len__(self)

因为所有的值是可用的,序列有一个特定的长度,是使用内置函数len()来决定。这背后,len()检查这个给定的对象是有__len__()方法,并获取这个序列的长度值。为了完成这个任务,__len__()应该返回一个整数,这个序列的明细项的总数。

从技术上,__len__()不要求所有的值都提前知晓,只要至少知道有多少值就可以。既然不可能是部分明细项--它要么存在,要么不存在--__len__()应该总是返回一个整数。如果不这样,len()将强制生成一个整数。

>>> class FibonacciLength(Fibonacci):
...     def __len__(self):
...         return self.count
...
>>> len(FibonacciLength(10))
10
>>> len(FibonacciLength(2048))
2048

__getitem__()和__setitem__()

一个序列所有的值都已经排序的,因此用索引存取序列的每个值是可能的。既然这个存取类型的语法和字典类型的键是一致的,python就使用以前字典描述过的相同的两个方法。这允许一个序列定制个别值怎样来存取,或者限制设置新值到序列,让它只读。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (3)» 动态感觉 静观其变
Declarative语法

一些Django很突出的便利特性,其中有“declarative syntax(声明性语法)”,从读写,理解上都很简单的。这种语法设计是最小化重复的样板语法,并提供优雅,可读性的代码。举例来说,这里有一个典型的Django model:

class Contact(models.Model):
    """
    Contact information provided when sending messages to the owner of the site.
    """
    name = models.CharField(max_length=255)
    email = models.EmailField()

这 个declarative语法已经成为django代码里的标志性特性,因此许多提供框架的第三方应用都和django类似采用这种语法。这有助于开发人 员通过代码的内聚性容易理解和处理新的代码。一旦你理解了怎样使用declarative语法来创建一个类,你就能很容易的创建采用django特性的 类,包括官方和社区提供的。

看看declarative语法示例,用这种模式为django创建一个完全崭新的框架,将变得容易起来。在你自己的代码中使用declarative语法将会帮助你和同事更容易失陪代码,确保高生产率。总之,开发人员的效率是django, python首要目标。

下一部分内容描述的是declarative语法的通常用法,例子来自django的ORM,细节见第3章。

存取中心点

典型的,一个包将提供一个单一的模块,从这里应用程序可以存取所有必要的工具。这个模块可以把位于自己树下其他的地方的类和函数都拉过来,但他们将只在一个中心点上集中起来处理。

from django.db import models

一旦导入,这个模块提供一个类作为一个base class,供所有基于框架的子类使用。任何保留在类中的东西都用作新的子类属性。这些对象将会结合在一起来控制新类的运转。

基类(Base class)

每 一个特性都伴随着至少一个base class开始,可能会有多个,依赖于框架的需要,但为了让这个特性起作用至少是要有一个base class的。没有它的话,每一个类的定义中都不得不显式地包括一个__metaclass__属性,这是一个实现的细节,大多数用户本不应该知道它。

class Contact(models.Model):

除了检查已经定义的属性外,这个base class将提供一组方法和属性,子类会自动继承。像其他的类一样,它可简单,也可以复杂,要看框架要求提供什么特性。

属性类(Attribute Classes)

带有基类(base class)的模块也将提供一组被实例化的类,通常是带有可选的参数,来定制行为特征,这些实例化的类是作为新类的属性。

class Contact(models.Model):
    name = models.CharField(max_length=255)
    email = models.EmailField()


这些对象提供的特性会随着框架变化非常大,有些可能和标准属性完全不一致。通常他们和metaclass结合起来,一起提供一些额外的,超出分配属性范围隐藏在后面的功能。当创建这个额外的功能时,这些属性的选项也通常被metaclass读取。

举 例来说,Django的Model使用字段属性的名字和选项来描述底层数据库表,然后自动在数据库中创建。字段名是来存取表中的列,而属性类和选项把原生 的python数据类型自动转换成正确的数据库值。关于django处理model类和field字段的更多信息参见下一章。

对类属性排序

当 使用declarative语法时,有一个潜在的疑惑就是python的字典类型是不排序的,它不关心分配值的顺序。通常这也不是什么问题,但是当要检查 一个名字空间的字典时,想看看声明关键字的顺序的时候,那就不可能办到了。如果一个框架需要通过一些特殊的属性进行迭代,或者把这些显示给用户或编程者, 那么按照已经定义好的相同顺序来存取属性就很有用处了。这就让编程者能对属性的顺序进行控制,而不是由编程语言决定的任意顺序。

这个问题的一个简单的解决方法是让属性跟踪实例化序列;然后metaclass对它们进行相应排序。这个处理过程是先把所有的属性类继承自一个特殊的基类,由它来计算这个类被实例化多少次,为每个实例分配一个值。

class BaseAttribute(object):
    creation_counter = 1
    def __init__(self):
        self.creation_counter = BaseAttribute.creation_counter
        BaseAttribute.creation_counter += 1

对象实例与类相比有不同的名字空间,因此这个类的所有实例都有一个creation_counter,它用来按被实例化的次序进行排序。这就是django对model和form的字段进行排序的原理。

类声明

用 一个模块中所有的类来创建一个应用类,就像和定义一个子类和属性一样简单。不同的框架有不同的属性类的名字,类的要求也不同,使用的组合情况也不同。甚至 可能还有保留的名字,如果你用保留名定义一个属性,就会引起冲突,但是这样的问题很少,而且当使用这类语法来开发新的框架时,保留名是建议不使用的。通常 的规则是允许开发人员尽可能自取所需,而没有框架的羁绊。

from django.db import module

class Contact(models.Model):
    """
    Contact information provided when sending messages to the owner of the site.
    """
    name = models.CharField(max_length=255)
    email = modles.EmailField()

这段简单的代码想要表达的意思已经足够了,允许框架让新类带有一个健壮的附加功能,而不要求程序员人工介入处理。同时也要注意所有的属性类是怎样由同一个基础模块提供的,以及当分配model时,怎样被实例化的。

对于只是框架提供的特性,一个类的声明是没有限制的。既然任何有效的python代码都允许,你的类也可以包含不同的方法和属性,和框架提供的特性混杂在一起。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (6)» 动态感觉 静观其变
带参数的函数

除了标准的声明和调用,python提供了选项允许你用有趣的方式来激活函数。Django使用了这个技术以便代码有效地复用。你也可以在你的应用中使用相同的技术;它们是python标准的一部分。

过量的参数

在 运行时知道一个函数有什么参数,通常是不可能的。在django里,这种情况经常发生,(这里不好翻译)类方法(class method)在开始,甚至在子类被定制之前就已经定义了。另一个情况是一个函数能操作很多对象。更有甚者,调用自身的函数变成一种api提供给可用的应 用。

对于这些情况,python提供了两种特别的方法来定义函数的参数,允许函数接受过量的参数,不用显式声明参数。这些“额外”的参数下一步再解释。

注意args和kwargs只是python的约定。任何函数参数,你可以自己喜欢的方式命名,但是最好和python标准的惯用法一致,以便你的代码,其他的程序员也能轻松读懂。

位置参数


在参数名之前使用一个星号,就是让函数接受任意多的位置参数。

>>> def multiply(*args):
...     total = 1
...     for arg in args:
...         total *= arg
...     return total
...
>>> multiply(2, 3)
6
>>> multiply(2, 3, 4, 5, 6)
720

python把参数收集到一个元组中,作为变量args。显式声明的参数之外如果没有位置参数,这个参数就作为一个空元组。

关键字参数

python在参数名之前使用2个星号来支持任意多的关键字参数。

>>> def accept(**kwargs):
...     for keyword, value in kwargs.items():
...         print "%s => %r" % (keyword, value)
...
>>> accept(foo='bar', spam='eggs')
foo => 'bar'
spam => 'eggs'

注意:kwargs是一个正常的python字典类型,包含参数名和值。如果没有更多的关键字参数,kwargs就是一个空字典。

混合参数类型

任意的位置参数和关键字参数可以和其他标准的参数声明一起使用。混合使用时要加些小心,因为python中他们的次序是重要的。参数归为4类,不是所有的类别都需要。他们必须按下面的次序定义,不用的可以跳过。

1)必须的参数
2)可选的参数
3)过量的位置参数
4)过量的关键字参数

def complex_function(a, b=None, *c, **d):

这个次序是必须的,因为*args和**kwargs只接受那些没有放进来的其他任何参数。没有这个次序,当你调用一个带有位置参数的函数,python就不知道哪个值是已声明参数想要的,也不知道哪个被作为过量参数对待。

也要注意的是,当函数能接受许多必须的参数和可选的参数,那它只要定义一个过量的参数类型即可。

传递参数集合

除了函数能接受任意参数集合,python代码也可以调用带有任意多数量的函数,像前面说过的用星号。这种方式传递的参数由python扩展成为参数列表。以便被调用的函数
不需要为了这样调用而去使用过量参数。python中任何可调用的,都能用这种技法来调用。并且用相同的次序规则和标准参数一起使用。

>>> def add(a, b, c):
...     return a + b + c
...
>>> add(1, 2, 3)
6
>>> add(a=4, b=5, c=6)
15
>>> args = (2, 3)
>>> add(1, *args)
6
>>> kwargs={'b': 8, 'c': 9}
>>> add(a=7, **kwargs)
24
>>> add(a=7, *args)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() got multiple values for keyword argument 'a'
>>> add(1, 2, a=7)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() got multiple values for keyword argument 'a'


注意这个例子的最后几行,特别留意当传递一个元组作为过量的位置参数时,是否要显式的传递关键字参数。因为python使用次序规则来扩展过量的参数,那位置参数要放在前面。这个例子中,最后两个调用是相同的,python不能决定那个值是给a的。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (4)» 动态感觉 静观其变
常用的 Duck-Typing 约定

你可能听说过一个古老的谚语,“if it walk like a duck, and talks like a duck, it's a duck”“如果走路像鸭子,说话像鸭子,它就是一个鸭子”莎士比亚在他写“罗密欧与朱丽叶”时,按照这个意思排演过有点浪漫的戏剧。。。。(待翻译)

在 python中,和其他的一些语言中,这个概念被扩展,用来指向对象类型。不是依赖一个对象已经定义好的一些基类或接口,他只要简单的实现期望表现的必要 属性和方法。在python中常见的例子就是“file-like object”(文件类对象),这种对象,只要至少实现和python file对象相同的方法即可。按照这个方法,许多库都可以返回他们自己的对象,这些对象可以传递到其他有能力操作文件对象的函数中去,比如读取,压缩,加 密,从internet下载或其他的操作。

同时,像其他语言的接口,python对象一次可以扮演几个duck类型。这是不寻常的做法, 举例来说,有一个对象,某些方面可以像字典的行为,而其他的时候行为又像列表。Django的HttpResponse对象就是这样的展现这两个行为,像 模仿了一个打开的文件对象。

在Django里,许多特性利用duck-typing,不提供特殊的基类,相反,每个特性定义一个排序的约定,一组对象要正确处理的必备的方法和属性。许多这种约定在django官方文档中都有展示。本书也会多次涉及。你会看到许多使用这种技巧而展现出来的能力。

下面的部分描述一些常见的python约定,在django中贯穿始终,在任何大一些的python库都的确存在。

Callable

python允许代码可以有许多途径被运行,任何东西都能像典型的函数标识可调用的一样,按相同的方式执行。所有函数,类,方法都是自动可调用的,像期望的一样,但是任意对象类实例也能被标识为可调用,只要提供一个单一方法。

__calll__(self[, ...])

当实例化对象像函数一样调用时,这个方法就开始执行。它和其他的成员函数很象,不同的地方就是被调用的方式。

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

python也提供了一个内置函数来判定可调用(callable)的对象。 callable()函数只有一个参数,返回True或False。判断对象是否像函数一样可调用。

>>> class Basic(object):
...     pass
...
>>> class Callable(object):
...     def __call__(self):
...         return "Executed!"
...
>>> b = Basic()
>>> callable(b)
False
>>> c = Callable()
>>> callable(c)
True

字典

字 典是单一对象中的键值映射。大多数编程语言都有相同形式的字典类型,其他语言称之为“hashes", "maps"或"associative arrays(关联数组)",除了简单通过指定键来存取值,python的字典提供了许多方法,一边更好的操作底层的映射。为了像一个真正的字典的表现行 为,一个对象需要提供一些方法,文档参见python library reference。

__contains__(self, key)

通过使用in操作符, 如果指定的键存在于底层的映射中,则返回True,否则返回False。它从不返回异常。

__getitem__(self, key)

如果存在的华,则返回被指定键引用的值。如果键不在底层的映射中,则抛出一个异常KeyError。

__setitem__(self, key, value)

通过指定的键来保存指定的值,保存之后准备引用它。如果有一个映射存在,可用相同的键覆盖已经存在的值。

>>> class CaseInsensitiveDict(dict):
...     def __init__(self, **kwargs):
...         for key, value in kwargs.items():
...             self[key.lower()] = value
...     def __contains__(self, key):
...         return super(CaseInsensitiveDict, self).__contains__(key.lower())
...     def __getitem__(self, key):
...         return super(CaseInsensitiveDict, self).__getitem__(key.lower())
...     def __setitem__(self, key, value):
...         super(CaseInsensitiveDict, self).__setitem__(key.lower(), value)
...
>>> d = CaseInsensitiveDict(SpAm='eggs')
>>> 'spam' in d
True
>>> d['SPAM']
'eggs'
>>> d['sPaM'] = 'burger'
>>> d['SpAm']
'burger

字典也可以用作迭代使用,当代码循环字典的内容时,键用作列表。更多信息参见即将介绍的"Iterables"(迭代)。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (9)» 动态感觉 静观其变
自省

许多python对象在运行的代码之外还带有metadata(元数据)。这些信息在框架的使用或写自己的代码中相当有用。

当试图开发复用的程序时,python的自省工具很有帮助的。因为他们允许python代码提取编程者所写的信息,编程者不用多次重复写的东西。

这小节里描述的一些特性依赖于强大的python标准库inspect,inspect模块提供了方便的函数来执行高级的自省。

inspect的很多用法这里只列出了一部分,因为这些用法对于用django编写的应用提供了很有价值的东西。这个模块的许多其他细节,查阅python的标准库文档。

**

这个小节的内容全部采用的是新的类定义方式,这一点本章前面已经描述过了。它和老的类定义方式不同,尤其是自省方面。这种全部的差别已经超过本书要讲的内容,因为现在强烈推荐只采用新的类定义方式。

如果你的代码和这里说的不同,请确保所有的类都继承自object,采用正确的新的类定义方式。

****

常见的类和函数属性

所有的类和函数提供了一些常见的属性,用来标识它们。

1)__name__ -- 用来声明类和函数的名称
2)__doc__ -- 为函数声明使用的文档说明
3)__module__ -- 导入模块的路径,模块里有类和函数的声明。

另外,所有对象都包含一个特殊的属性,__class__,它是用来创建对象的实际类对象。这个属性有很多用途,诸如测试一下类中是否提供一个特殊的属性或者它是否设置在对象上。

>>> class ValueClass(object):
...     source = 'The class'
...
>>> value_instance = ValueClass()
>>> value_instance.source = 'The instance'
>>> value_instance.__class__
<class '__main__.ValueClass'>
>>> value_instance.source
'The instance'
>>> value_instance.__class__.source
'The class'



标识对象类型

因为python使用动态类型,任何变量都可以任意类型的对象。duck-typing的原则,推荐的用法是测试一下支持的约定(难翻译),这用来标识你要处理的对象很有用处。这里有几个方法处理这个

获得任意对象的类型

使用早先介绍的内置函数type来判断任何python对象是轻而易举。调用type,只带一个参数,返回一个type对象,通常是一个类,被实例化来生成对象。

>>> type('this is a string')
<type 'str'>
>>> type(42)
<type 'int'>
>>> class TestClass(object):
...     pass
...
>>> type(TestClass)
<type 'type'>
>>> obj = TestClass()
>>> type(obj)
<class '__main__.TestClass'>

这个方法通常不是最好的办法去判定一个对象的类型,尤其是在通过对象类型来决定执行的分支的情况下。它只是告诉你一个从来不使用的特定类,即使为同一个执行分支的子类。(难翻译)相反这个方法适用于对象类型不是条件,而仅仅是输出信息,可能输出给用户或者输出到日志文件。

举个例子,如果报告异常,包括异常类型和值是非常有用处的。在这种情况,type用来返回一个类对象,它的__name__属性也包括进日志文件,容易表示异常类型。

检查特定类型

更多的情况,你需要检查一个特定类型,检查是否是派生于它或是否是一个实例。这是比使用type更有效的解决方案。因为是成功或失败,它考虑了类继承的因素,

python为此提供了2个内置的函数:

1)issubclass(cls, base) -- 如果cls和base是同一类型,或者cls是从base继承而来,返回True,
2)isinstance(obj, base) -- 测试obj是否是base的实例或它的父类对象的实例。

>>> class CustomDict(dict):
...     pass
...
>>> issubclass(CustomDict, dict)
True
>>> issubclass(CustomDict, CustomDict)
True
>>> my_dict = CustomDict()
>>> isinstance(my_dict, dict)
True
>>> isinstance(my_dict, CustomDict)
True

在issubclass和isinstance之间存在一个明显的关系,isinstance(obj, SomeClass)等同于insubclass(obj.__class__, SomeClass)。

函数签名

正如本章前面描述的,python 函数用多种方式来声明,在你的代码中,函数的直接访问的声明信息是相当有用的。

检查函数最重要的一个函数是inspect.getargspec(),它返回的信息是关于函数接受的参数。它只接受一个参数,被检查的函数对象,返回一个元组,有以下的值:

1)args -- 所有参数名称的列表,如果函数不接受任何参数,这就是一个空列表
2)varargs -- 过量位置参数中的变量名称,就像先前说的。如果函数不接受过量位置参数,它就为None
3)varkwargs -- 过量关键字参数的变量名称,前面也说过。如果函数不接受过量关键字参数,它就为None
4)defaults -- 函数参数中默认值的元组,如果没有参数的默认值,就是None,而不是空元组

这些值代表了任何方式去调用函数的所有的必要信息。当接受一个函数,然后用合适的参数来调用它,这样处理时是上面的值非常有用。

>>> def test(a, b, c=True, d=False, *e, **f):
...     pass
...
>>> import inspect
>>> inspect.getargspec(test)
(['a', 'b', 'c', 'd'], 'e', 'f', (True, False))


处理默认值

前面的例子表明,默认值是放在一个单独列表中返回的,因此怎样告诉你哪个参数对应哪个默认值,可能看起来不是很明显。然后,有一个相对简单的方法来处理这个情况,它基于前面讨论过量参数的一个小细节:必须的参数总是要在任何可选参数前被声明。

这是关键,因为它意味着参数和它的默认值按他们在函数中的声明次序来指定的。因此前面的例子中,有2个默认值,意味着最后两个参数是可选的,默认值按次序排列。下面的代码可用来创建一个字典,它将可选的参数名称和声明的默认值一一对应。

>>> def get_defaults(func):
...     args, varargs, varkwargs, defaults = inspect.getargspec(func)
...     index = len(args) - len(defaults) # Index of the first optional argument
...     return dict(zip(args[index:], defaults))
...
>>> get_defaults(test)
{'c': True, 'd': False}

文档字符串

前面提到了,类和函数都有一个特殊的__doc__属性,它包含了一个字符串作为代码的文档说明。不过,它的格式完全就像它在源码文件中一样,包括换行符和不必要的缩进。

为了用更可读的方式来格式化文档字符串,python的inspect模块提供了另一个有用的函数,getdoc()。它删除了不必要的换行符,和任何缩进符,缩进对于文档字符串的书写格式有一些副作用的。

缩进的删除要作一点解释,getdoc()找到字符串最左边非空格的字符,计算出这个字符和行开头的位置之间所有的空白,并且删除文档字符串其他行的所有空白。这样处理之后,最后的结果,字符串的左边作了调整,但也保留一些缩进,这些缩进是已经存在格式化文档中的。

>>> def func(arg):
...     """
...     Performs a function on an argument and return the result.
...
...     arg
...         The argument to be processed
...     """
...     pass
...
>>> print func.__doc__

    Performs a function on an argument and return the result.

    arg
        The argument to be processed
    
>>> print inspect.getdoc(func)
Performs a function on an argument and return the result.

arg
    The argument to be processed

那些文档字符串要显示给用户的时候,比如自动文档或帮助系统,对于原始的文档字符串,getdoc()提供了一个很有用的可选方式。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image14:03 django is python (10)» 动态感觉 静观其变
应用技巧

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这些高级特性。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image11:26 How to upgrade to the latest version of Rails» O'Reilly News:Ruby
I have upgraded several Rails 1.2.x programs to 2.x. This can be quite a leap, and some of the steps are counterintuitive, so this post attempts to put everything together, like a recipe. I'd also like to hear more stories about upgrading platforms; such stories may indeed emend my suggested hacks and tweaks. Yet the point of unit tests, and TDD, is to make the smallest changes possible, and relentlessly test each change. Upgrading a major version tick is a big change, so you must force the upgrade to work incrementally, as a series of small changes.
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image11:26 Choosing your application's version of Rails» O'Reilly News:Ruby
Rails updates versions frequently. There are a few different ways to make sure your application is running the version of Rails you think it should be, and to make sure you can run it under the version it expects.
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image11:26 American Idol Switcheroo on iTunes» Joey Gibson's Blog
If you watched Idol last week, you were treated to a bunch of mediocre renditions of classic Motown songs, plus a stunningly beautiful re-imagining of Smokey Robinson’s “Tracks of My Tears.” Adam Lambert is the one who pulled off that coup. He sang with an acoustic guitar, upright bass and slapboard-drum-type-thing, and it was so [...]
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image09:03 django is python (1)» 动态感觉 静观其变
Django,像其他的框架一样,它是由一门基础的编程语言来构建的,而django是由python来完成的。许多人对django,甚至对 python还刚刚开始学习,python自然的语法结构和django简洁(energy-saving: 节能,译为简洁更好些)的特性结合起来,使得django看起来它使用了一些元语言的特征一样,尽管它还不完全算是元语言(which isn't the case)。

关于在Django里能做些什么,要获得正确的理解必须先要有一个基本的概念,那就是Django是简单的python程序,就像你的应用一样。在python中使用的任何东西,同样也能在django中使用,几乎没有任何其他的限制。

这也意味着django app不仅能使用python全部的标准库,而且还能使用更加广大的第三方的库和工具。他们其中有一些的接口,django提供了,对于更多情况,已经存在的代码和文档也能直接使用,从而能快速的构建程序并运行起来。

这本书的后面章节,会涉及到一些附加的工具,会谈到如何把它们集成到django应用中去。在本书中可以看到,是没有任何问题的。可以感觉到使用python工具可以很方便地支持你的商业计划,而且在本书中提到的技巧也可以集成到你的应用当中去。

尽管学习python超出了本书的范围,但是django使用了一些python高级的特性。在本章节,我将讨论许多这种高级特性,它们将有助于你来理解运用python将会让每个人实现自己的目标变得更加容易。

python怎样构建Class

Django依赖的一些python高级特性都和python创建class有关。大多数开发人员都认识到class是(常用的机制)(意译),而且也应该是这样。但是既然它是django核心,它就是我们这里开始探险的基础。

当 python解释器遇到一个class定义,它只是读取内容,就像其他代码一样。然后python为这个class创建一个新的名字空间,执行它里面的所 有代码,把任何已经分配的变量写入那个新的名字空间。class定义通常包含变量,方法和其他的class,所有这些是这个class名字空间中的基本分 配单元。但是,这里的任何有效的代码都是允许的,包括打印到控制台的输出,写日志文件,甚至激活GUI界面。

一旦内容执行完毕,python将有一个class object(类对象),它位于已经定义的名字空间中(通常是模块中的全局名字空间),然后它就能被传入或者调用去创建这个类的实例。

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

正如你看到的,代码在类定义中就开始执行,一旦这个Class准备好,分配的变量,如类属性,就可以显示出来。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image09:03 django is python (2)» 动态感觉 静观其变
用编程的方式创建Class

在接下来的部分描述的东西,可用在任何 source-declared class,但是python采用的方式,提供了更有趣的一些特征。在这个场景的背后,class 声明的细节都给发送到内置的type对象中去,由它来为这个class创建一个合适的python对象。对每一个class来说,当它完成解析class 声明的内容之后,都是立即自动进行处理的。

type的构建,接受3个参数,代表了完整的class声明。

1)name -- 类名,字符串类型。
2)bases -- 一个元组,可能为空,类继承链上的类
3)attrs -- 类名字空间上的字典类型。

** 类定义的新方式和老方式 **

这 里描述的都是python新的类定义方式,是在python 2.2中引入的。老的类定义方式不再推荐使用,而且在python 3.0中完全取消。因此这个部分的内容集中讨论新的类定义方式。为了强制使用新的类定义方式,只要简单的确保类继承内置的object类型就可以。

django提供的所有类,都是从object派生而来。因此任何派生的类型都自动是新的类定义方式,不要你增加额外的处理。还有,记住这种不同是重要的,以便你自己程序的任何自定义类能更好地扩展。

****

像任何python对象一样,在任何时候,任何代码块中,一个新的type都能实例化。也就是说,你的代码能在运行时构建一个新类,只要满足已知的条件。下面的代码就举例说明了一个在运行时声明一个类,功能上相当于前面提供的例子里的类。

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

** 关于type()的注意事项 **

手工使用type(),很容易创建带有重复的names的类出来,甚至模块的位置能被定制,这是在attrs参数中,使用字典中的一个__module__关键字来处理。尽管这些特性很有用,正如在本书后面演示的,但它们也会引发自省的问题。

你有可能有两个不同的类,但名字和模块相同,你的代码将不能区分它们之间的差别。在有些情况下这不是问题,但有时确是问题。

****

Metaclass 改变一切

type 实际上是一个"metaclass"(元类)-- 创建其他类的类 -- 我们一直提到的,称之为“metaprogramming”。从本质上,metaprogramming(元编程)是在运行时创建或改变代码,而不是在编 程时。通过允许一个类定义不同的metaclass(元类),python允许你来自由定制这个处理过程。

如果一个类定义包含一个独立的 类,它有__metaclass__属性,metaclass将会被调用来创建这个类,而不是使用内置的type对象了。这就允许你的代码能读取,改变甚 至完全替换声明的类,以便进一步定制它的功能。__metaclass__属性从技术上可以指定成任何有效的python callable,但是大多数metaclass类都是作为type的子类。metaclass接受第一个参数作为新类,并提供类的声明细节来存取类对 象。

为了有助于说明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的执行。注意:属性列表中__module__,这个属性是所有python类的标准部分。

这个例子使用了__init__方法执行了一个特殊的处理,新创建类。还有另外一个,__new__,更有用的方法,可以处理更多不同的可能性。正如在后面的章节中介绍的,在配置许多类时,Django使用__new__。

使用带有metaclass的Base class

Metaclass 是相当有用的,但是 __metaclass__ 变量是一个实现细节,当定义类时,它不应该成为处理的一部分。另一个问题是当每一个类需要处理metaclass时,他们不从任何具体类继承。也就是说任 何附加的功能,比如通常的方法和属性,为了能使用起来,必须在metaclass处理过程中提供。

稍加注意一下,一个具体的python 类可以使用metaclass解决这两个问题。因为子类从它的父类那继承了属性,父类中定义的__metaclass__变量也会自动被所有的子类获得, 所以就不必要求每个类都要定义 __metaclass__ 属性。看一下前面的例子, 当我们派生一个ReadClass子类,会发生什么事。

>>> class SubClass(RealClass):
...     pass
...
Defining <class '__main__.SubClass'>
Name: SubClass
Bases: (<class '__main__.RealClass'>,)
Attributes:
    __module__: '__main__'
>>>

注意:这里的子类不用担心背后metaclass是怎样使用的。只要指定一个base class,它就继承了所有的东西。Django采用了这种机制,实现了下一部分要描述的最突出的一个特性。
...
add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image03:11 Upcoming talks» chris blogs

It’s kind of conference season again! You can see me speaking at these events:

RailsConf 2009

I’m looking forward to meet you at these events.

All material will be posted on my talks page of course.

NP: De los Muertos—This Changes Everything


add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl

Mon 30 March, 2009

Click here to bookmark this link.Channel Image06:45 The world is not comprehens...» Projectionist
The world is not comprehensible, but it is embraceable: through the embracing of one of its beings.

Martin Buber


add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl

Sun 29 March, 2009

Click here to bookmark this link.Channel Image09:34 RailsConf interviews, presenter posts, and keynotes» Riding Rails - home

RailsConf 2009 is drawing ever closer and it’s going to be one hell of a show worthy of Vegas glitz and glamour. To let you all get more familiar with who’s going to be talking and what they’ll be talking about, Chad Fowler has been doing a series of presenter interviews:

In addition, Nicke Plante is talking about his Rumble Panel and Marty Andrews about his automated code quality check talk.

We’ve also announced some of the keynoters. In addition to yours truly, we’ll have Bob Martin of ObjectMentor, Chris Wanstrath of Github, and Timothy Ferris of 4-hour work week and lifestyle designs rock it out. We’re also going to have a Rails Core panel of some sort.

It’s going to be a great time. You can follow along with even more announcements from the conference on Twitter by subscribing to @railsconf. See you in Vegas? Of course I will!


add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl

Sat 28 March, 2009

Click here to bookmark this link.Channel Image09:55 So I bought a Mac ...» James Britt - Code, Content, Caffiene

A Mac Mini, actually. For occasional development work.

Many things are quite nice and well done. Others, annoying. It looks very nice, but I was amazed that my choice of simple desktop background colors was limited to one of 8 shades of melancholy. There’s a difference between subdued and drab, and while I would really want a gradient background (as I have on my Kubuntu laptop), even a solid shade of a vibrant rust or golden green would be nice.

Likewise for “spaces”, the Mac version of multiple desktops. KDE3 lets me give each a unique color—makes it easy for me to place myself. Puzzling that the Mac, which seems to otherwise excel in UI goodness, falls down here. And one more “why isn’t a Mac more like <foo>” complaint: To resize a window you have to use the One True Corner. You can’t (as far as I can see) simply grab any edge or corner and adjust freely. Makes for more work.

When I switched from XP to Kubuntu a few years ago, I had a similar experience. I liked XP as a general OS, and there were a number of apps that did not have suitable counterparts in Linuxistan, but one BSOD too many made me decide that, being a developer, I needed the stability more than the pleasantries. I had been using Linux via VMware for a while so it wasn’t a sudden radical leap, but making it my daily OS (with KDE3 as the desktop manager) was jarring.

I first tried the stock Ubuntu install, which uses Gnome, but I was not able to tweak the UI as I liked. I could not, for example, give each desktop a unique background, and the menu system seemed hard to customize. KDE was more to my liking, but it, too, had a fair share of quirks.

I think KDE3 gives you Dolphin as the default file manger. Dolphin seemed heavy on white space and icons and weak on more useful information, and I ended up using Konqueror instead since it seemed to have more of the better features of the XP file manager. While spending time getting things “just so” I was thinking about the little things that worked or didn’t. Changing your OS + desktop manager seemed to entail swapping annoyances more than a clear move to better or worse. What was annoying depended on what you were used to. I figured someone moving from KDE to XP would be finding the same number of nits to pick.

Over time I just got used to how things worked. I also found things that were so much better on KDE. For example, to move a window, you hold Alt and click anywhere, and drag. Very nice. I sometimes do this on my Vista box with amusing effects. (I tried this on my Mac; no luck.)

I need now to get acquainted with various Mac tools and keyboard shortcuts. I imagine that over time I’ll stop gnashing my teeth over funky Mac UI decisions, get used to the Mac Way, and be enjoying the good parts while ignoring the bad.

I have Synergy running, so I can use the Kubuntu laptop as my base and switch over to the Mac or the Vista boxen as needed. Very handy. Now I just need to install all those extra dev things …


add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Click here to bookmark this link.Channel Image06:32 This Week in Edge Rails» Riding Rails - home

March 21, 2009 – March 27, 2009

It’s been a quiet week for Rails on the surface: just half a dozen commits over the course of the week. Behind the scenes, though, the work is going on to merge initial Rails 3.0 work into the master branch of the git repository. We’ll have more news on that as it happens.

Fixes

Meanwhile, a few small issues in the 2.3 release have been cleared up in edge:

  • Tests with multiple POST requests in the same test block now properly handle parameters. commit
  • render :file with absolute paths now works on Windows systems. commit
  • Template extension parsing is now more robust (if you’ve been having problems with files like show.erb.orig, this one is for you). commit

Just a reminder, if you’re having any issues with Rails 2.3, we’d love to know about them – and we’d love it even more if you’d supply a failing test or a patch. Check out the details in the Contributing to Rails guide.


add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl

Wed 25 March, 2009

Click here to bookmark this link.Channel Image23:04 从敏捷开始» 梦想风暴

我们的跨项目组持续集成方案要用到Hg,我们专门介绍了Hg。之后,我们搜集到了一大堆问题。下面就是一个:
* Hg解决不了我们代码的耦合

没错,Hg解决不了,事实上,没有工具可以解决。因为版本管理和代码耦合是两件独立的事。

但是,为什么这样的问题会提出来呢?

Hg只是我们实施用到的一个工具,这个方案对客户的开发人员而言,是一套全新的工作方式,正是这套工作方式的引入,他们原本一切安稳的工作方式受到了挑战,那些原本不是问题的问题也就暴露出来。

你或许已经想到了,引入我们的方案,对他们而言,绝不是仅仅把工具安上这么简单,甚至不仅仅是工作方式的改变,也许更大的调整在等待着他们。

无独有偶,这不是我们遇到第一次类似的情况。

和我们讨论TDD的时侯,有人和我们说,我承认TDD很好,但是,我们的代码做不了TDD,因为我们的代码耦合太大。

继续讨论下去,就会发现,他们对现状有很多不满,而根源往往就是这种耦合。既然大家都认为这是个问题,那就应该解决它。面对这种耦合,他们通常表现出的是一种无能为力,认为这是历史遗留问题,无法解决。我们所经历过的实际情况是,还没有代码真正糟糕到让人无能为力的地步,只是他们缺少有效的工作方法而已。经过一些历练的我们,面对遗留代码时,会多几份从容。

敏捷,给了我们一个新的思考角度,由此,我们得以发现很多的问题。这不是敏捷的问题,而是既有工作方式存在的问题,只不过,敏捷让它暴露出来而已。借由敏捷的引入,努力消除这些问题,让软件开发向着良性的方向前进。

敏捷,只是一个开始,随着敏捷的深入,我们可以发现更多原本可以做得更好的地方。






add to del.icio.us add to del.icio.us. look up in del.icio.us.   add to furl.net add to furl
Sources