| 逝者如斯 |
|
|
| 网志分类 |
|
| 最新评论 |
|
| 搜索本站 |
|
| 友情链接 |
|
0251269
|
|
|
对于每日的交易额,要做截止到每日的累计和,这样的需求也很常见。在sql cookbook中有介绍,
select e.ename, e.sal,
(select sum(d.sal) from emp d
where d.empno <= e.empno) as running_total
from emp e
对于同样一个表,采用不同的别名e, d来处理,以empno为条件,保证截止到当前的记录号。
而对于我的应用,又更复杂了一些,必须按照每日累计,因为要照顾到可能有日期没有交易额的情况发生。为此,
select r.lxrq, coalesce(t.lze, 0.00) as lze from (
select cast('20081001' as date) + s.a as lxrq
from generate_series(0,
cast('20081015' as date)
-cast('20081001' as date),1)
as s(a) ) as r
left join (
select yxn, jyr, sum(pdysk+zdysk) as lze from nps_card
where yxn=2009
and jyr>='20081001' and jyr<='20081015'
group by yxn, jyr
order by yxn, jyr asc
) as t
on r.lxrq=t.jyr
这是确保每日都有记录,即使是没有交易记录的情况。下面就是累计求和
select e.lxrq, e.lze, (select sum(d.lze) from (
select r.lxrq, coalesce(t.lze, 0.00) as lze from (
select cast('20080901' as date) + s.a as lxrq
from generate_series(0,
cast('20080930' as date)
-cast('20080901' as date),1)
as s(a) ) as r
left join (
select yxn, jyr, sum(pdysk+zdysk) as lze from nps_card
where yxn=2009
and jyr>='20080901' and jyr<='20080930'
group by yxn, jyr
order by yxn, jyr asc
) as t
on r.lxrq=t.jyr ) as d where d.lxrq<=e.lxrq ) as ljlze
from (
select r.lxrq, coalesce(t.lze, 0.00) as lze from (
select cast('20080901' as date) + s.a as lxrq
from generate_series(0,
cast('20080930' as date)
-cast('20080901' as date),1)
as s(a) ) as r
left join (
select yxn, jyr, sum(pdysk+zdysk) as lze from nps_card
where yxn=2009
and jyr>='20080901' and jyr<='20080930'
group by yxn, jyr
order by yxn, jyr asc
) as t
on r.lxrq=t.jyr) as e
看过去是有些复杂,不过有一些重复动态生成的表。
还有复杂的,这是当年度的,同时要生成上年度的同期累计求和,sql又要拉得很长了。pgsql赋予了sql的灵活性,确实很方便,自己也是乱用,有无更好的用法,请朋友们提出,呵呵。
|
上次谈到的几个技巧,一直没写下来。今天抽空记录一下。
对于连续日期的数据统计。因为指定时间段中有些日期是没有交易额的,所以必须补齐,我使用的postgresql数据库,查找了文档,可以用postgresql机制解决之。
pgsql的文档中有这样的例子:
select current_date + s.a as dates from generate_series(0,14,7) as s(a);
按照这个例子,我的实例是:
select cast('20081001' as date) + s.a as lxrq
from generate_series(0,
cast('20081031' as date)-cast('20081001' as date),1) as s(a)
cast是转换类型,generate_series这个函数是关键,是序列号生成函数
|
这两天重新调整一些原来做的py程序,想让它自动一些,省得多动手。尤其想多运用fabric,这个是类似ruby社区capitrano的部署工具。fabric从0.9版本,更换了一个维护着,在结构上做了很多调整,在windows下使用也还算顺当,只要paramiko的版本1.7.4就可以了,1.7.5似乎在连接远程主机上有问题。还是看自己做的fabfile.py文件吧。
from __future__ import with_statement
from fabric.api import run, put, hosts, env, local
import os, os.path, glob
import conv_xls
def staging():
env.hosts = ['172.16.7.13']
env.user = 'hzg'
env.password = '1234'
env.datadir = 'd:\nps_data\'
env.remotedir = '/home/hzg/work/source/'
def year2009():
env.year = '2009'
def year2010():
env.year = '2010'
def conv():
conv_xls.conv_xls_files("%s%s"%(env.datadir, env.year,))
def send():
put("%s%s\*.XLS"%(env.datadir, env.year,),
"%s%s/"%(env.remotedir, env.year,))
def move():
if len(glob.glob('%s%s\*.XLS'%(env.datadir, env.year,))) != 0:
local("copy %s%s\*.XLS "%(env.datadir, env.year,)+
"%s%s_down\"%(env.datadir, env.year,))
local("del %s%s\*.XLS"%(env.datadir, env.year,))
else:
print 'no xls files'
def remote_import():
run(". /home/hzg/mypy/bin/activate;
cd /home/hzg/work/fx_analysis/nps;
python parseexcel_db.py -y %s" % (env.year,))
0.9版本的fabric,可以直接采用py的模块,这点上很方便。使用的方式上,task之间可以连续的递进的方式来进行。对于独立写task的组合,很是方便的。
fab staging year2009 conv
fab staging year2010 send
这样就可以连续组合使用env的各种变量和任务了。
|
这两天弄统计图,是两年数据的同期对比曲线,
在作东西的过程中,碰到了以下一些问题,都一一化解了。
1)连续日期的数据统计。因为指定时间段中有些日期是没有交易额的,所以必须补齐,用postgresql机制解决之。
2)累计和的处理。找到一篇文章,是《sql cookbook》里的一招,相当简练
3)统计曲线的制作。原先使用的xml/swf chart,没有文档,光看例子难以琢磨。只好改换门庭,尝试了一下open flash chart。
没有饭否的日子,只能慢慢回归到blog。呵呵,人就是这么怪,需要留下一些东西才心甘。上面一些具体的处理,有空我列出来。有一点,接近100行的sql语句让人抓狂。
|
备注:看到了BeepBeep,感觉挺好,正好能看看erlang,就翻译了一下它的readme,算是一个入门。
BeepBeep a simple web application for Erlang
受 rails和merb的启示,BeepBeep是一个支持erlang语言的简单的web应用框架。它遵循“约定胜于配置”的原则--意味着如果在构建你的应用时,遵循代码的结构设计和一些规则,对你来说,是会有好处的,它并不要求有额外的事情处理从url请求到控制器和视图(Controller and Views)的映射。
BeepBeep是构建在MochiWeb和erlyDTL之上的,提供了超快的web服务器,以及用django模板语言来定义模板的能力。
特征
* 生成新的web应用的脚本(基于mochiweb的方法)
* 保存你的应用状态的会话服务器(Session Server)
* 在你的控制器处理之前有一个过滤器(Before filter),比如权限之类
* 视图使用django模板
起步
1. 下载代码
2. 进入beepbeep目录
3. 运行make
4. 运行“./script/new_beep.erl 应用名 "目标目录" ”,生成一个新的web应用。
这会创建一个web应用程序需要的一切,包括一个示例控制器(main_controller.erl)。
运行这个示例:
1. 进入这个你创建的新程序目录
2. 运行make编译这个新项目
3. 运行服务: ./start-server.sh
4. 打开浏览器,访问“http://localhost:8000”
运行原理
你写一个控制器(controller),是用一组方法来处理的,就像:
handle_request(Action, Params)
Action是一个匹配url请求的字符串。Params是一个可选参数的数组,它会被传入给你的controller的变量。
BeepBeep自动处理url请求与控制器和函数(或动作)的映射。举个例子,一个请求"/hello/show",将会映射"hello_controller",激活"handle_request("show", [])"这个函数。
这里是一个例子:
%% hello_controller.erl
-module(hello_controller).
-export([handle_request/2, before_filter/0]).
%% Maps to http://localhost:8000/hello/show
handle_request("show",[]) ->
{render, "hello/show.html",[{name,"BeepBeep"}]};
%% Maps to http://localhost:8000/hello/feed/2007
handle_request("feed",[Year]) ->
%% Now Year contains the value 2007
{render,"hello/feed.html",[{year,Year}]}.
%% Callback filter hook
before_filter(Params) ->
ok.
从"handle_request",我们返回一个元组,告诉框架使用哪个视图。视图位于views目录下。在我们这个例子,我们的视图位于"hello"
子目录下,有一个文件"show.html"。这里是"show.html"模板内容:
<h2>Hello from {{ name }} </h2>
它会产生:
<h2>Hello from BeepBeep</h2>
在controller中设置的"name"会传入到模板,这个模板系统使用django的格式,是由erlyDTL编写。
这个方法提供了一个controller中的erlang逻辑和模板中的html代码之间清晰的分离。在匹配"handler_request"调用之前,可以用before_filter来检查请求。filter过滤器只是简单的返回原子ok,否则他们应返回请求的响应,诸如{render...}或者{redirect...之类。
更多的信息,要参见文档和包含在源代码中的blog例子。在wiki上有一个简短的入门指南。
|
最近网友农夫三拳给我一个pro django电子书,我看了一下,相当令人惊奇,很有深度,对了解python内部一些高级特性很有帮助,对django内部运用这些python高级特性的原理作了很好的介绍。尤其是第2章,标题就是django is python,就像当年说j2ee is java一样,对pythoner,尤其是django fans都是一个很好引导。我已经把这一章作了一个快速的翻译,因为本章英语很多地方较难理解,自己水平也就一般,不太好翻译的,有的用意译,有的是直译,翻译不好的,请热心人多多指正。
|
应用技巧
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这些高级特性。
|
自省
许多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()提供了一个很有用的可选方式。
|
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章有讨论)。
|
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,这两个装饰器接受的,作为位置参数来传递,并没有通常用的星号。然后,当把它们传递给被包装的函数,星号必须带上,确保函数正确接受,而不用知道装饰器是如何工作的。
|
带参数的函数
除了标准的声明和调用,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的。
|
文件
正如先前提到的,文件是存取信息的常见方式,而且许多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就使用以前字典描述过的相同的两个方法。这允许一个序列定制个别值怎样来存取,或者限制设置新值到序列,让它只读。
|
|
|
|
|
|