显示标签为“Python”的博文。显示所有博文
显示标签为“Python”的博文。显示所有博文

星期三, 八月 08, 2007

浅尝Python Web 框架:Django

1.介绍

Django 是一个高级 Python web framework,遵守BSD版权,它鼓励快速开发和干净的、MVC 设计,包括一个模板系统,对象相关的映射和用于动态创建管理界面的框架。使用 Django,我们在几分钟之内就可以创建高品质、易维护、数据库驱动的应用程序。

Django 的名字是从一位比利时爵士音乐家来的。这是从 Django 的 FAQ 中看到的。这位音乐家名叫: Django Reinhardt ,他是一个吉普赛人,主要以演奏吉它为主,还演奏过小提琴等。在1930年到1950年初的这段日子里,他被认为是最好的吉它演奏家。根据百科全书的说 明,django的发音为: zhane-go (’a’发长音)

Django 项目是一个定制框架,它源自一个在线新闻 Web 站点,于 2005 年以开源的形式被释放出来。Django 框架的核心组件有:

  • 用于创建模型的对象关系映射
  • 为最终用户设计的完美管理界面
  • 一流的 URL 设计
  • 设计者友好的模板语言
  • 缓存系统


2.安装Django

Django 当前最新的官方版本是0.96。单击这里下载。然后解压,执行以下使命来安装:

tar xzvf Django-0.96.tar.gz
cd Django-0.96
sudo python setup.py install

3.Django 的管理工具

在安装Django 之后,就有一个可用的Django 管理工具:django-admin.py。在终端或者命令行下,输入命令:django-admin.py,会显示如下的帮助信息:

Usage: django-admin.py action [options]
actions:
adminindex [appname …]
Prints the admin-index template snippet for the given app name(s).

createcachetable [tablename]
Creates the table needed to use the SQL cache backend
dbshell
Runs the command-line client for the current DATABASE_ENGINE

….


4.1 创建Django 项目和应用程序

要新建一个Web 项目, 先在命令行的模式下,进入你想建立项目的目录,执行以下命令:django-admin startproject,例如:

~/Python$ django-admin startproject mysite

上面的命令将会在~/Python 下创建mysite 文件夹,其中包含了运行Django 项目的基本配置文件:


__init__.py
manage.py
settings.py
urls.py


现在,在项目上建立一个应用程序(aplication),以职位公告板”jobs” 为例子:

~/Python/mysite$ python manage.py startapp jobs

上面将建立一个应用程序的骨架,jobs 目录如下:


__init__.py
models.py
views.py


为了使项目知道新的应用程序存在,需要在settings.py 文件中的INSTALLED_APPS 添加一个条目(‘mysite.jobs’):

INSTALLED_APPS = (
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.sites’,
‘mysite.jobs’,
)

4.2 创建一个模型(Model)

Django 提供了自己的对象关系型数据映射组件(object-relational mapper,ORM)库,它可以通过 Python 对象接口支持动态数据库访问。这个 Python 接口非常有用,功能十分强大,但如果需要,也可以灵活地不使用这个接口,而是直接使用 SQL。

ORM 目前提供了对 PostgreSQL、MySQL、SQLite 和 Microsoft® SQL 数据库的支持。

在新建模型之前,需要对settings.py 文件进行数据库的配置。本例以SQLite3 为例:

DATABASE_ENGINE = ’sqlite3′
DATABASE_NAME = ‘~/Sqlite/test.db’ DATABASE_USER = ”
DATABASE_PASSWORD = ”
DATABASE_HOST = ”
DATABASE_PORT = ”

这个职位公告板应用程序有两种类型的对象:Location 和 Job。Location 包含 city、state(可选)和 country 字段。Job 包含 location、title、description 和 publish date 字段。jobs/models.py 文件内容如下:

from django.db import models

# Create your models here.
class Location(models.Model):
city = models.CharField(maxlength=50)
state = models.CharField(maxlength=50, null=True, blank=True)
country = models.CharField(maxlength=50)

def __str__(self):
if self.state:
return “%s, %s, %s” % (self.city, self.state, self.country)
else:
return “%s, %s” % (self.city, self.country)

class Job(models.Model):
pub_date = models.DateField()
job_title = models.CharField(maxlength=50)
job_description = models.TextField()
location = models.ForeignKey(Location)

def __str__(self):
return “%s (%s)” % (self.job_title, self.location)

__str__ 方法是 Python 中的一个特殊类,它返回对象的字符串表示。Django 在 Admin 工具中显示对象时广泛地使用了这个方法。

执行python manage.py sql jobs 可以看到模型在数据库里的模式:


~/Python/mysite$ python manage.py sql jobs

BEGIN;
CREATE TABLE “jobs_job” (
“id” integer NOT NULL PRIMARY KEY,
“pub_date” date NOT NULL,
“job_title” varchar(50) NOT NULL,
“job_description” text NOT NULL,
“location_id” integer NOT NULL
);
CREATE TABLE “jobs_location” (
“id” integer NOT NULL PRIMARY KEY,
“city” varchar(50) NOT NULL,
“state” varchar(50) NULL,
“country” varchar(50) NOT NULL
);
COMMIT;


运行数据库命令 syncdb 后,将会初始化并安装这个模型:
~/Python/mysite$ python manage.py syncdb

注意syncdb 命令要求我们创建一个超级用户帐号。这是因为 django.contrib.auth 应用程序(提供基本的用户身份验证功能)默认情况下是在 INSTALLED_APPS 设置中提供的。超级用户名和密码用来登录将在下一节介绍的管理工具。记住,这是 Django 的超级用户,而不是系统的超级用户。

4.3 使用带图形界面的Admin 管理工具

Django 的最大卖点之一是其一流的管理界面,为我们的项目提供了很多数据输入工具。实质上,管理工具是也是一个应用程序,不过是Django 内置。与 jobs 应用程序一样,在使用之前也必须进行安装。第一个步骤是将应用程序的模块(django.contrib.admin)添加到 INSTALLED_APPS 设置中。修改 settings.py 的内容如下:

INSTALLED_APPS = (
‘django.contrib.auth’,
‘django.contrib.contenttypes’,
‘django.contrib.sessions’,
‘django.contrib.sites’,
‘djproject.jobs’,
‘django.contrib.admin’,
)

要让该管理工具可以通过 /admin URL 使用必须对URL 进行配置,修改urls.py 的内容如下:

from django.conf.urls.defaults import *

urlpatterns = patterns(”,
(r’^admin/’, include(’django.contrib.admin.urls’)),)

这个管理应用程序有自己的数据库模型,但也需要进行安装。我们可以再次使用 syncdb 命令来完成这个过程:

~/Python/mysite$ python manage.py syncdb

启动Django 内置的测试服务器:

~/Python/mysite$ python manage.py runserver

Validating models…
0 errors found.

Django version 0.96, using settings ‘mysite.settings’
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.

现在可以在浏览器里输入 http://localhost:8000/admin 启动管理工具,并使用前面创建的超级用户帐号进行登录。

要让模型的类可以通过管理工具进行访问,我们需要为其创建一个 Admin 子类。然后可以通过为这个子类添加类属性来定制如何对每个类进行管理。以下是修改后的jobs/models.py 的内容:

from django.db import models

# Create your models here.
class Location(models.Model):
city = models.CharField(maxlength=50)
state = models.CharField(maxlength=50, null=True, blank=True)
country = models.CharField(maxlength=50)

def __str__(self):
if self.state:
return “%s, %s, %s” % (self.city, self.state, self.country)
else:
return “%s, %s” % (self.city, self.country)

class Admin:
list_display = (”city”,”state”,”country”)

class Job(models.Model):
pub_date = models.DateField()
job_title = models.CharField(maxlength=50)
job_description = models.TextField()
location = models.ForeignKey(Location)

def __str__(self):
return “%s (%s)” % (self.job_title, self.location)

class Admin:
list_display = (”job_title”,”location”,”pub_date”)
ordering = [”-pub_date”]
search_fields=(”jab_title”,”job_description”)
list_filter = (”location”,)

现在就可以通过管理界面来创建、更新和删除 Location 和Jobs 记录了。


4.4 配置URL

Django URL 分发系统使用了正则表达式配置模块,它可以将URL 字符串模式映射到特定的views。这个系统允许URL 与底层代码完全脱节,从而实现最大的控制和灵活性。

项目下的urls.py 模块是URL 的默认配置点(也可以通过 settings.py 模块中的ROOT_URLCONF 值重新设置)。URL 配置文件的惟一要求是必须包含一个定义模式 urlpatterns 的对象。

这个职位公告板应用程序会在启动时打开一个索引和一个详细视图,它们可以通过以下的 URL 映射进行访问:

  • /jobs 索引视图:显示最近的 10 个职位
  • /jobs/1 详细视图:显示 ID 为 1 的职位信息

这两个视图(索引视图和详细视图)都是在这个 jobs 应用程序的 views.py 模块中实现的。在项目的 urls.py 文件中实现这种配置看起来如下所示:

from django.conf.urls.defaults import *

urlpatterns = patterns(”,
(r’^admin/’, include(’django.contrib.admin.urls’)),
(r’^jobs/$’, ‘mysite.jobs.views.index’),
(r’^jobs/(?Pd+)/$’, ‘mysite.jobs.views.detail’),
)

注意 部分,这在后面非常重要。

最佳实践是提取出应用程序特有的 URL 模式,并将它们放入应用程序自身中。这样可以取消应用程序与项目的耦合限制,从而更好地实现重用。jobs 使用的应用程序级的 URL 配置文件(jobs/urls.py)如下所示:

from django.conf.urls.defaults import *

urlpatterns = patterns(’mysite.jobs.views’,
(r’^$’, ‘index’),
(r’^(?P
d+)/$’, ‘detail’),
)

注意:由于 view 方法现在都是来自同一个模块,因此第一个参数可以使用这个模块的根名称来指定 djproject.jobs.views,Django 会使用它来查找 index 方法和 detail 方法。

再将项目下的urls.py 更改为以下内容:

from django.conf.urls.defaults import *

urlpatterns = patterns(”,
(r’^admin/’, include(’django.contrib.admin.urls’)),
(r’^jobs/’, include(’mysite.jobs.urls’)),
)

4.5 实现视图

视图是一个简单的 Python 方法,它接受一个请求对象,负责实现:

  • 任何业务逻辑(直接或间接)
  • 上下文字典,它包含模板数据
  • 使用一个上下文来表示模板
  • 响应对象,它将所表示的结果返回到这个框架中

在 Django 中,当一个 URL 被请求时,所调用的 Python 方法称为一个视图(view),这个视图所加载并呈现的页面称为模板(template)。由于这个原因,Django 小组将 Django 称为一个 MVT(model-view-template)框架。

下面将获取最近的 10 个职位,并通过一个模板呈现出来,然后返回响应。jobs/views.py 的内容如下:

from django.template import Context, loader
from django.http import HttpResponsefrom jobs.models import Job

def index(request):
object_list = Job.objects.order_by(’-pub_date’)[:10]
t = loader.get_template(’jobs/job_list.html’)
c = Context({
‘object_list’: object_list,
})
return HttpResponse(t.render(c))

在上面的代码中,模板是由 jobs/job_list.html 字符串进行命名的。该模板是使用名为 object_list 的职位列表的上下文呈现的。所呈现的模板字符串随后被传递到 HTTPResponse 构造器中,后者通过这个框架被发送回请求客户机那里。

加载模板、创建内容以及返回新响应对象的步骤在下面都被 render_to_response 方法取代了。新增内容是详细视图方法使用了一个 get_object_or_404 方法,通过该方法使用所提供的参数获取一个 Job 对象。如果没有找到这个对象,就会触发 404 异常。这两个方法减少了很多 Web 应用程序中的样板代码。修改jobs/views.ps 的内容:

from django.shortcuts import get_object_or_404, render_to_response
from jobs.models import Job

def index(request):
object_list = Job.objects.order_by(’-pub_date’)[:10]
return render_to_response(’job_list.html’,
{’object_list’: object_list})

def detail(request, object_id):
job = get_object_or_404(Job, pk=object_id)
return render_to_response(’job_detail.html’,
{’object’: job})

注意detail 使用 object_id 作为一个参数。这是前面提到过的 jobs/urls.py 文件中 /jobs/ URL 路径后面的数字。它以后会作为参数传递给 get_object_or_404 方法。

4.6 创建模板

Django 提供了一种模板语言,该语言可以快速呈现和易于使用。Django 模板是利用 {{ variables }}{% tags %} 标记嵌入文本来创建的。模板可以用来生成任何基于文本的格式,包括 HTML、XML、CSV 和纯文本。

第一个步骤是定义将模板加载点。为了简便起见,我们需要在mysite 项目 下面创建一个 templates 目录,并将这个路径添加到settings.py 的TEMPLATE_DIRS 条目中:

TEMPLATE_DIRS = (
‘~/Python/mysite/templates/’,
)

Django 模板支持模板继承(template inheritance)的概念,它允许设计人员创建一个统一的外表,而不用替换每个模板的内容。我们可以通过使用块标记定义骨干文档或基础文档来使用继承。这些块标记都是使用一些包含内容的页面模板来填充的。这个例子给出了一个包含称为 titleextraheadcontent 的块的 HTML 骨干。templates/base.html 文件的内容如下:

  “http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>


Company Site: {% block title %}Page{% endblock %}
{% block extrahead %}{% endblock %}


{% block content %}{% endblock %}

为了取消应用程序与项目之间的耦合,我们使用了一个中间基本文件templates/jobs/base.html 作为Jobs 应用程序所有页面文件的基础。对于这个例子来说,为了简便起见,我们将应用程序的CSS 放到这个基本文件中。在实际的应用程序中,需要有一个正确配置的Web 服务器,将这个CSS 提取出来,并将其放到Web 服务器所服务的静态文件中。templates/jobs/base.html 文件的内容如下:

{% extends “base/base.html” %}

{% block extrahead %}

{% endblock %}

现在我们要创建视图所加载并呈现的两个页面模板。templates/jobs/job_list.html 模板简单地循环遍历 object_list,它通过索引视图遍历其内容,并显示一个到每条记录的详细页面的链接。templates/jobs/job_list.html 内容如下:

{% extends “base.html” %}

{% block title %}Job List{% endblock %}

{% block content %}

Job List



{% endblock %}

templates/jobs/job_detail.html 模板页面会显示一条job 的记录。templates/jobs/job_detail.html 文件内容如下:

{% extends “base.html” %}

{% block title %}Job Detail{% endblock %}

{% block content %}

Job Detail





{{ object.job_title }}
-
{{ object.location }}


Posted: {{ object.pub_date|date:”d-M-Y” }}


{{ object.job_description }}


{% endblock %}

项目已经基本完成,可以到浏览器里测试下结果。今天就到此告一段落~~

星期日, 九月 10, 2006

Python Doc Digest [2]

对于链表(lists)来讲,有三个内置函数非常有用:filter(),map(),reduce()。

filter(function, sequence) 返回一个sequence(序列),包括了给定序列中所有调用function(item) 后返回值为true 的元素。(如果可能的话,会返回相同的类型)。如果sequence 是一个string(字符串)或者tuple(元组),返回值必定是同一类型,否则,它总是list。

>>> def f(x): return x % 2 != 0 and x % 3 != 0
...
>>> filter(f, range(2, 25))
[5, 7, 11, 13, 17, 19, 23]


map(function, sequence) 为每一个元素依次调用function(item)并将返回值组成一个链表返回。

>>> def cube(x): return x*x*x
...
>>> map(cube, range(1, 11))
[1, 8, 27, 64, 125, 216, 343, 512, 729, 1000]

也可以传入多个序列,函数也必须要有对应数量的参数,执行时会依次用各序列上对应的元素来调用函数(如果某些序列比其它的短,就用None 来代替缺少的元素)。如果函数为None,则直接返回参数做为替代。

>>> seq = range(8)
>>> def add(x, y): return x+y
...
>>> map(add, seq, seq)
[0, 2, 4, 6, 8, 10, 12, 14]


reduce(func, sequence) 返回一个单值,它是这样构造的:首先以序列的前两个元素调用函数,再以返回值和第三个参数调用,依次执行下去。

>>> def add(x,y): return x+y
...
>>> reduce(add, range(1, 11))
55

如果序列中只有一个元素,就返回它,如果序列是空的,就抛出一个异常。

也可以传入第三个参数做为初始值。如果序列是空的,就返回初始值,否则函数会先接收初始值和序列的第一个元素,然后是返回值和下一个元素,依次类推。

>>> def sum(seq):
... def add(x,y): return x+y
... return reduce(add, seq, 0)
...
>>> sum(range(1, 11))
55
>>> sum([])
0

 

星期四, 八月 31, 2006

Python Doc Digest

输入一个文件结束符(UNIX kbdCtrl+D,Windows上是Ctrl+Z)解释器会以0值退出。如果这没有起作用,你可以输入以下命令退出:‘import sys; sys.exit()’。


启动解释器的第二个方法是‘python -c command [arg] ...’,这种方法可以在命令行中直接执行语句,赞同于Shell的-c 选项。因为Python 语句通常会包括空格之类的特殊字符,所以最好把整个语句用双引号包起来。


有些Python 模块也可以当作脚本使用。它们可以用‘python -m module [arg] ...’调用,这样就会像你在命令行中给出其完整名字一样运行源文件。


注意‘python file’和‘python <file’是有区别的。对于后一种情况,程序中类似于调用input() 、raw_input() 这样的输入请求,来自于确定的文件。因为在解析器开始执行之前,文件已经完全读入,所以程序指向文件尾。在前一种情况(这通常是你需要的)它们来自于任何联接到Python 解释器的标准输入,无论它们是文件还是其它设备。 


使用脚本文件时,经常会运行脚本然后进入交互模式。这也可以通过在脚本之前加上-i 参数来实现。(如果脚本来自标准输入,就不能这样运行,与前一段提到的原因一样。)


调用解释器时,脚本名和附加参数传入一个名为sys.argv 的字符串列表。没有给定脚本和参数时,它至少也有一个元素:sys.argv[0] 此时为空字符串。脚本名指定为‘-’(表示标准输入)时,sys.argv[0] 被设定为‘-’,使用-c 指令时,sys.argv[0] 被设定为’-c’。使用-m module 参数时,sys.agv[0]被设定为指定模块的全名。-c command 或者-m module 之后的参数不会被Python 解释器的选项处理机制所截获,而是留在sys.argv 中,供脚本命令操作。


------------------------------------------------------------------------------------

Python 的源文件可以通过编码使用ASCII 以外的字符集。最好的做法是在#! 行后面用一个特殊的注释来定义字符集。

# -*- coding: encoding -*-

根据这个声明,Python 会尝试将文件中的字符编码转为encoding 编码。并且,它尽可能的将指定的编码直接写成Unicode 文本。在Python 库参考手册中codecs 部分可以找到可用的编码列表(根据经验,推荐使用cp-936 或utf-8 处理中文)

------------------------------------------------------------------------------------

执行附加的启动文件,可以在全局启动文件中加入类似以下的代码:

import os

filename = os.environ.get(’PYTHONSTARTUP’)

if filename and os.path.isfile(filename):

    execfile(filename)

----------------------------------------------------------------------------------- 

循环可以有一个else 子句;它在循环迭代完整个列表(对于for)或执行条件为false (对于while)时执行,但循环被break 中止的情况下不会执行。以下搜索素数的救命程序演示了这个子句:

>>> for n in range(2, 10):
...     for x in range(2, n):
...         if n % x == 0:
...              print n, ’equals’, x, ’ ’, n/x
...              break
...     else:
...          # loop fell through without finding a factor
...          print n, ’is a prime number’
...
2 is a prime number
3 is a prime number
4 equals 2 2
5 is a prime number
6 equals 2 3
7 is a prime number
8 equals 2 4
9 equals 3 3

-----------------------------------------------------------------------------------

执行函数时会为局部变量引入一个新的符号表。所有的局部变量都存储在这个局部符号表中。引用参数时,会先从局部符号表中查找,然后是全局符号表,然后是内置命名表。注意的是,全局参数虽然可以被引用,但它们不能在函数中直接赋值(除非它们用global 语句命名)。

------------------------------------------------------------------------------------

文档字符串的格式:
第一行应该是关于对象用途的简介。简短起见,不用明确的陈述对象名或类型,因为它们可以从别的途径了解到(除非这个名字碰巧就是描述这个函数操作的动词)。这一行应该以大写字母开关,以句号结尾。

如果文档字符串有多行,第二行应该空出来,与接下来的详细描述明确分隔。接下来的文档(从第三行开始)应该有一或多段描述对象的调用约定、边界效应等。

Python 的解释器不会从多行的文档串中去除缩进,所以必要的时候应当自己清除缩进。这符合通常的习惯。第一行之后的第一个非空行决定了整个文档的缩进格式。(我们不用第一行是因为它通常紧靠着起始的引号,缩进格式显示的不清楚。)留白“相当于”是字符串的起始缩进。每一行都不应该有缩进,如果有缩进的话,所有的留白都应该清除掉。留白的长度应当等于扩展制表符的宽度(通常是8 个空格)。

以下是一个多行文档字符串的示例:

>>> def my_function():
...     """Do nothing, but document it.
...
...     No, really, it doesn’t do anything.
...     """
...     pass
...
>>> print my_function.__doc__
Do nothing, but document it.

No, really, it doesn’t do anything.





星期二, 八月 29, 2006

wxPython Digest

Introduction

wxPython is a GUI toolkit for the Python programming language. It allows Python programmers to create programs with a robust, highly functional graphical user interface, simply and easily. It is implemented as a Python extension module (native code) that wraps the popular wxWidgets cross platform GUI library, which is written in C++.

Like Python and wxWidgets, wxPython is Open Source.

wxPython is a cross-platform toolkit.
 

wxWidgets is a C++ framework providing GUI (Graphical User Interface) and other facilities on more than one platform.

wxWidgets was originally developed at the Artificial Intelligence Applications Institute, University of Edinburgh, for internal use, and was first made publicly available in 1992. Version 2 is a vastly improved version written and maintained by Julian Smart, Robert Roebling, Vadim Zeitlin, Vaclav Slavik and many others.

 

Here are some of the benefits with wxPython:

1. Low cost (free, in fact!)
2. You get the source.
3. Available on a variety of popular platforms.
3. Works with almost all popular C++ compilers and Python.
4. Over 50 example programs.
5. Over 1000 pages of printable and on-line documentation.
6. Includes Tex2RTF, to allow you to produce your own documentation in Windows Help, HTML and Word RTF formats.
7. Simple-to-use, object-oriented API.
8. Flexible event system.
9. Graphics calls include lines, rounded rectangles, splines, polylines, etc.
10. Constraint-based and sizer-based layouts.
11. Print/preview and document/view architectures.
12. Toolbar, notebook, tree control, advanced list control classes.
13. PostScript generation under Unix, normal MS Windows printing on the PC.
14. MDI (Multiple Document Interface) support.
15. Can be used to create DLLs under Windows, dynamic libraries on Unix.
16. Common dialogs for file browsing, printing, colour selection, etc.
17. Under MS Windows, support for creating metafiles and copying them to the clipboard.
18. An API for invoking help from applications.
19. Ready-to-use HTML window (supporting a subset of HTML).
20. Dialog Editor for building dialogs.
21. Network support via a family of socket and protocol classes.
22. Support for platform independent image processing.
23. Built-in support for many file formats (BMP, PNG, JPEG, GIF, XPM, PNM, PCX).


wxPython Overview

To set a wxPython application going, you will need to derive an App class and override App.OnInit.

Instances of Dialog can also be used for controls, and they have the advantage of not requiring a separate frame.

You never draw directly onto a window. Instead, you use a device context (DC). DC is the base for ClientDC, PaintDC, PostScriptDC, MemoryDC, MetafileDC and PrinterDC. If your drawing functions have DC as a parameter, you can pass any of these DCs to the function, and thus use the same code to draw to several different devices. You can draw using the member functions of DC, such as DC.DrawLine and DC.DrawText. Control colour on a window (Colour) with brushes (Brush) and pens (Pen).

 


星期六, 八月 12, 2006

Python 连接嵌入式(Embedded)FireBird 数据库

版本————
数据库:Embedded FireBird 2.0 beta 3
驱动:KInterbasDB 3.2 for Win

1、将下载的KInterbasDB 驱动解压,运行其.exe安装程序,默认会安装到Python 的“lib\site-packages\kinterbasdb”目录下。

2、在上面提到的“kinterbasdb”目录下创建一个目录“embedded”,然后解压下载来的Embedded FireBird,复制“fbembed.dll”、“firebird.msg”、“ib_util.dll”3个文件到“embedded”目录下——如果需要用到ASCII 外的字符集的话,同时复制“intl”子目录(里面包含“fbintl.dll”文件)到“embedded”目录下)。

3、将“embedded”目录下的“fbembed.dll”文件改名为“fbclient.dll”。
最终的目录结构如下:
Python 的安装目录\
  Lib\
    site-packages\
      kinterbasdb\
        embedded\
          fbclient.dll
            firebird.msg
            ib_util.dll
            intl\            //如果需要的话
              fbintl.dll


4、完成,运行KInterbasDB-Base 的Python 程序就可以连接目标数据库了。

5、Example

import datetime, decimal, os.path, string, sys

import kinterbasdb
kinterbasdb.init(type_conv=200)               #注[1]
# This program never imports mx.DateTime:
assert 'mx' not in sys.modules

def test():
    dbFilename = r'D:\temp\test-deferred.fdb'
    prepareTestDatabase(dbFilename)

    # Connect with character set UNICODE_FSS, to match the default character
    # set of the test database.

    con = kinterbasdb.connect(dsn=dbFilename,
        user='sysdba', password='masterkey', charset='UNICODE_FSS'
    )
    cur = con.cursor()

    # Create a test table.
    cur.execute("""
        create table test (
            a numeric(18,2),
            b date,
            c time,
            d timestamp,
            e varchar(50), /* Defaults to character set UNICODE_FSS. */
            f varchar(50), /* Defaults to character set UNICODE_FSS. */
            g varchar(50) character set ASCII
        )
    """)
    con.commit()

    # Create an input value for each field in the test table.
    aIn = decimal.Decimal('4.53')
   
    # Notice that the DB API date/time constructors in kinterbasdb generate
    # datetime-based objects instead of mx-based objects because of our earlier
    # call to kinterbasdb.init(type_conv=200).

    bIn = kinterbasdb.Date(2004,1,4)
    assert isinstance(bIn, datetime.date)
    cIn = kinterbasdb.Time(16,27,59)
    assert isinstance(cIn, datetime.time)
    dIn = kinterbasdb.Timestamp(2004,1,4, 16,27,59)
    assert isinstance(dIn, datetime.datetime)

    eIn = u'A unicod\u2211 object stored in a Unicode field.'
    fIn = 'A str object stored in a Unicode field.'
    gIn = 'A str object stored in an ASCII field.'

    print '-' * 70
    inputValues = (aIn, bIn, cIn, dIn, eIn, fIn, gIn)
    reportValues('In', inputValues)
    cur.execute("insert into test values (?,?,?,?,?,?,?)", inputValues)
    print '-' * 70
    cur.execute("select a,b,c,d,e,f,g from test")
    (aOut, bOut, cOut, dOut, eOut, fOut, gOut) = outputValues = cur.fetchone()
    reportValues('Out', outputValues)
    print '-' * 70

    # Notice that all values made the journey to and from the database intact.
    assert inputValues == outputValues

def reportValues(direction, values):
    for (val, c) in zip(values, string.ascii_lowercase[:len(values)]):
    varName = c + direction
    print '%s has type %s, value\n %s' % (varName, type(val), repr(val))

def prepareTestDatabase(dbFilename):
    # Delete the test database if an old copy is already present.
    if os.path.isfile(dbFilename):
        conOld = kinterbasdb.connect(dsn=dbFilename,
            user='sysdba', password='masterkey'
        )
        conOld.drop_database()
    # Create the test database afresh.
    kinterbasdb.create_database("""
        create database '%s'
        user 'sysdba' password 'masterkey'
        default character set UNICODE_FSS
        """ % dbFilename
    )

if __name__ == '__main__':
test()



注[1]:
KInterbasDB 为了向后兼容,默认情况下是使用“mx.DateTime”模块,但“mx.DateTime”被明确地表示是不必的!在Python 标准库里的“datetime”模块会更容易使用,完全可以代替“mx.DateTime”!如果用“datetime”代替默认的mx.DateTime”,只需要简单地修改代码:

    “
import kinterbasdb
替换为
    “import kinterbasdb; kinterbasdb.init(type_conv=200)


有一点值得提下,如果你用的Python 版本是低于2.4的话,你还是需要“mx.DateTime”模块的,可以到以下网址下载:http://www.taniquetil.com.ar/facundo/bdvfiles/get_decimal.html









 

星期五, 八月 11, 2006

[转]Python Is Not Java

原文:

I was recently looking at the source of a wxPython-based GUI application, about 45.5KLOC in size, not counting the libraries used (e.g. Twisted). The code was written by Java developers who are relatively new to Python, and it suffers from some performance issues (like a 30-second startup time). In examining the code, I found that they had done lots of things that make sense in Java, but which suck terribly in Python. Not because "Python is slower than Java", but because there are easier ways to accomplish the same goals in Python, that wouldn't even be possible in Java.

So, the sad thing is that these poor folks worked much, much harder than they needed to, in order to produce much more code than they needed to write, that then performs much more slowly than the equivalent idiomatic Python would. Some examples:

A static method in Java does not translate to a Python classmethod. Oh sure, it results in more or less the same effect, but the goal of a classmethod is actually to do something that's usually not even possible in Java (like inheriting a non-default constructor). The idiomatic translation of a Java static method is usually a module-level function, not a classmethod or staticmethod. (And static final fields should translate to module-level constants.)

This isn't much of a performance issue, but a Python programmer who has to work with Java-idiom code like this will be rather irritated by typing Foo.Foo.someMethod when it should just be Foo.someFunction. But do note that calling a classmethod involves an additional memory allocation that calling a staticmethod or function does not.

Oh, and all those Foo.Bar.Baz attribute chains don't come for free, either. In Java, those dotted names are looked up by the compiler, so at runtime it really doesn't matter how many of them you have. In Python, the lookups occur at runtime, so each dot counts. (Remember that in Python, "Flat is better than nested", although it's more related to "Readability counts" and "Simple is better than complex," than to being about performance.)

Got a switch statement? The Python translation is a hash table, not a bunch of if-then statments. Got a bunch of if-then's that wouldn't be a switch statement in Java because strings are involved? It's still a hash table. The CPython dictionary implementation uses one of the most highly-tuned hashtable implementations in the known universe. No code that you write yourself is going to work better, unless you're the genetically-enhanced love child of Guido, Tim Peters, and Raymond Hettinger.

XML is not the answer. It is not even the question. To paraphrase Jamie Zawinski on regular expressions, "Some people, when confronted with a problem, think "I know, I'll use XML." Now they have two problems."

This is a different situation than in Java, because compared to Java code, XML is agile and flexible. Compared to Python code, XML is a boat anchor, a ball and chain. In Python, XML is something you use for interoperability, not your core functionality, because you simply don't need it for that. In Java, XML can be your savior because it lets you implement domain-specific languages and increase the flexibility of your application "without coding". In Java, avoiding coding is an advantage because coding means recompiling. But in Python, more often than not, code is easier to write than XML. And Python can process code much, much faster than your code can process XML. (Not only that, but you have to write the XML processing code, whereas Python itself is already written for you.)

If you are a Java programmer, do not trust your instincts regarding whether you should use XML as part of your core application in Python. If you're not implementing an existing XML standard for interoperability reasons, creating some kind of import/export format, or creating some kind of XML editor or processing tool, then Just Don't Do It. At all. Ever. Not even just this once. Don't even think about it. Drop that schema and put your hands in the air, now! If your application or platform will be used by Python developers, they will only thank you for not adding the burden of using XML to their workload.

(The only exception to this is if your target audience really really needs XML for some strange reason. Like, they refuse to learn Python and will only pay you if you use XML, or if you plan to give them a nice GUI for editing the XML, and the GUI in question is something that somebody else wrote for editing XML and you get to use it for free. There are also other, very rare, architectural reasons to need XML. Trust me, they don't apply to your app. If in doubt, explain your use case for XML to an experienced Python developer. Or, if you have a thick skin and don't mind being laughed at, try explaining to a Lisp programmer why your application needs XML!)

Getters and setters are evil. Evil, evil, I say! Python objects are not Java beans. Do not write getters and setters. This is what the 'property' built-in is for. And do not take that to mean that you should write getters and setters, and then wrap them in 'property'. That means that until you prove that you need anything more than a simple attribute access, don't write getters and setters. They are a waste of CPU time, but more important, they are a waste of programmer time. Not just for the people writing the code and tests, but for the people who have to read and understand them as well.

In Java, you have to use getters and setters because using public fields gives you no opportunity to go back and change your mind later to using getters and setters. So in Java, you might as well get the chore out of the way up front. In Python, this is silly, because you can start with a normal attribute and change your mind at any time, without affecting any clients of the class. So, don't write getters and setters.

Code duplication is quite often a necessary evil in Java, where you must often write the same method over and over with minor variations (usually because of static typing constraints). It is not necessary or desirable to do this in Python (except in certain rare cases of inlining a few performance-critical functions). If you find yourself writing the same function over and over again with minor variations, it's time to learn about closures. They're really not that scary.

Here's what you do. You write a function that contains a function. The inner function is a template for the functions that you're writing over and over again, but with variables in it for all the things that vary from one case of the function to the next. The outer function takes parameters that have the same names as those variables, and returns the inner function. Then, every place where you'd otherwise be writing yet another function, simply call the outer function, and assign the return value to the name you want the "duplicated" function to appear. Now, if you need to change how the pattern works, you only have to change it in one place: the template.

In the application/platform I looked at, just one highly trivial application of this technique could have cut out hundreds of lines of deadweight code. Actually, since the particular boilerplate has to be used by developers developing plugins for the platform, it will save many, many more hundreds of lines of third-party developer code, while simplifying what those developers have to learn.

This is only the tip of the iceberg for Java->Python mindset migration, and about all I can get into right now without delving into an application's specifics. Essentially, if you've been using Java for a while and are new to Python, do not trust your instincts. Your instincts are tuned to Java, not Python. Take a step back, and above all, stop writing so much code.

To do this, become more demanding of Python. Pretend that Python is a magic wand that will miraculously do whatever you want without you needing to lifting a finger. Ask, "how does Python already solve my problem?" and "What Python language feature most resembles my problem?" You will be absolutely astonished at how often it happens that thing you need is already there in some form. In fact, this phenomenon is so common, even among experienced Python programmers, that the Python community has a name for it. We call it "Guido's time machine", because sometimes it seems as though that's the only way he could've known what we needed, before we knew it ourselves.

So, if you don't feel like you're at least ten times more productive with Python than Java, chances are good that you've been forgetting to use the time machine! (And if you miss your Java IDE, consider the possibility that it's because your Python program is much more complex than it needs to be.)


译:

最近我一直在看一个基于wxPython的GUI应用程序代码,大概45.5KLOC的左右,而且这还不包括它所用到的库(如Twisted)。代码是由那些对Python比较生疏的Java的开发者写的,所以它存在很严重的性能问题(如三十秒的启动时间)。在检查代码的时候,我发现他们写了很多在Java中能讲得通但是对Python来说去却是很难接受的东西。并不是因为“PythonJava慢”,而是因为在Python中有更方便的方法去完成同样的目标,甚至是在Java中不可能的事情。
所以,令人难过的事就是这些家伙事倍功半,写的那些代码比本应合乎用Python语言实现的慢很多。下面,让我们来看一些例子:

Java中的静态方法不能翻译成Python的类方法。哦,当然,他多多少少也能产生同样的效果,但类方法的目的实际上是做一些通常在Java中甚至都不可能的事情(如继承一个非默认的默认函数)。Java静态方法惯用的翻译通常翻译成一个模块级的函数,而不是一个类方法或静态方法。(并且静态常量应该翻译成模块级常量.)
这不是性能上的问题,但是一个Python程序员如果想调用Foo.someMethod,他要是被迫采用像JavaFoo.Foo.someMethod的方式去做的话,那么他就会被逼疯的。有一点一定要注意:调用一个类方法需要一个额外的存储空间,而调用静态方法或函数就不需要这样.
对了,还有就是这些Foo.Bar.Baz的属性链也不是自己就能数出来的.在Java中,这些带点的名称是有编译器来查找的,运行的时候并不会去考虑一共有多少.而在Python中,查找的过程是在运行时进行的,所以要包括每个点.(在Python中,要记住一点,"平铺的结构比嵌套的要好",尽管相对于从性能方面来说,可能它更多涉及的是"可读性"和"简单要比复杂好".)

要使用switch语句吗?Python翻译将是一个哈希表,不是一堆if-then语句。要使用在Java中不是switch语句而且还有字符串参与了的一堆if-then语句吗?它将仍然是一个哈希表。Python字典是用在我们所了解的领域中认为是最佳性能之一的哈希表来实现的。你自己所写的代码也不会比这个再好了,除非你是Guido、Tim Peters和Raymond Hettinger的私生子,而且还是遗传增强了的。

XML不是答案。它也不是一个问题。现在用正则表达式来解释Jamie Zawinski,“一些人,当他遇到一个问题的时候,就会想‘我知道,我要用XML.’那么他们就有两个问题了。”
相对于在Java中来说这是个不同的情况,因为比起Java代码,XML是灵活而且有弹性的。但比起Python的代码来,XML就是一个船锚,一个累赘。在Python中,XML是用来协同工作的,而不是你的核心功能,因为你不需要那么做。在Java中,XML可能是你的救世主,因为它让你实现了特定领域的语言并且“不用编码”就提高你的应用程序的适应性。在Java中,避免编码是一个很大的优势,因为编码意味着重新编译。但在Python中,通常是,写代码比写XML更简单。还有就是Python处理代码要比处理XML快很多很多。(不仅仅是这个,你必须写XML处理代码,同时Python就已经为你写好了.)
如果你是一个Java程序员,你并不能利用本能知觉来考虑你是否要在你的Python核心应用中使用XML作为一部分。如果你不是因为信息交互的原因去实现一个已经存在的XML标准或是建立某种输入、输出格式或者建立某种XML编辑器或处理工具,那么就不要这么做。根本不要去这么做。甚至连想都不要想。现在,丢掉那个XML模式然后把你的手解放出来吧!如果你的应用程序或者平台要被Python开发者使用,他们只会感谢你不要在他们的工作中添加使用XML的负担。
这里唯一的例外是如果你的客户(your target audience)确确实实因为某些原因而需要使用XML。就好像,他们拒绝学习Python但如果你使用XML他们就给你付钱,或者你打算给他们一个很棒的能编辑XML的GUI,还有就是这个XML的GUI是另一个人写的,同时你得到免费使用的权利。还有一些很少见的架构上的原因需要用到XML。相信我,它们不会应用到你的程序中去的。如果有疑问,对一个资深的Python开发员解释你的用例。或者,如果你脸皮厚而且不介意被人嘲笑的话,试试向一个Lisp程序解释你的程序为什么要用XML!

Gettersetter是恶魔。我应该说它是恶魔,是魔鬼!Python对象不是Java Bean。不要写什么gettersetter,而是还把它们内置在“属性”里面。它直到你能证明你需要比一个简单访问复杂一点的功能时才有意义,要不然,不要写gettersetter。它们是CPU时间的浪费,更要紧的是,它们还是程序员宝贵时间的浪费。不仅仅对于写代码和测试的人,对于那些要阅读和理解它们的人也是。
在Java中,你必须使用gettersetter,因为公共字段不允许你以后改变想法再去使用gettersetter。所以,在Java中你最好事先避开这些"家务杂事".在Python中,这样做很傻,因为你可以以一个普通特性开始并可以在任何时间改变你的想法,而不用影响到这个类的任何客户。所以不要写gettersetter方法。

代码重复在Java中通常来说就是一场不可避免的灾祸,你必须经常反复地写同一个方法而只有一点点的变化(通常是这是因为静态类型约束)。在Python中这样做是没有必要的也是不值得的(除了极少数一些特定的场合需要内联一些要求性能的函数)。如果你发现自己一遍一遍在写同样的代码而且变化很少,你就需要去学一下闭包。他们实际不并是那么可怕。
这就是你要做的。你写了一个包含了函数的函数。这里内部的函数就是你要一遍遍写的函数的模版,但是在里面加入了针对不同情况的函数要使用变量。外部的函数需要刚刚提高的那种变量作为参数,并且将内部的函数作为结果返回。然后,每次你要写另一种略微不同的函数的时候,你只要调用这个外部的函数,并且把返回值赋给你要让“重复”函数出现的名字。现在,如果你需要改变这个工作方式,你只需要改变一个地方:这个模版。
在我所看过的应用程序/平台中,只有一个很微不足道的程序使用了这个技术,它去掉了数百行重负的代码。实际上,因为开发者使用了特别的样板文件来为这个平台开发插件,所以这会节省很多很多第三方开发人员的代码,同时也使那些程序员要学习的东西变得简单了。

这只是Java->Python思维方式转变的冰山一角而已,现在我能正确的转变而不用去钻研程序的细节。本质上,如果你曾经用过一段时间Java,而且对Python比较陌生,那么你不要太相信自己的本能。你的本能已经被Java调节,而不是Python。向后退一步来说,最重要的是不要再写这么多代码了。
为了这样做,让自己觉得更加需要Python。假装好像Python是可以做任何你想做的魔棒,而你无须出一点力。问一下,“Python怎样解决我的问题?”还有“Python语言的哪个特点和我的问题最相似?”如果对于你需要的东西其实已经有了某种固定形式,那么你绝对会感到惊讶的。事实上,这种现象实在是太普遍了,甚至即使在很有经验的Python程序员中也会出现,以至于Python社区中给这种现象起了个名字。我们称之为“GUIDO的时间机器”,因为在我们自己还没有掌握它之前,通常看上去要得到我们所需要的东西好像那是唯一的方法。

所以,如果你在使用Python时候不能感到比使用Java要至少多出10倍的生产力话,你就最好做一下改动,你是不是忘记使用time machine!(chances are good that you've been forgetting to use the time machine)(同时如果你还怀念你的Java IDE,你可以这样想:因为你写的Python程序比他所需要的要复杂得多.)

译者注:Phillip J. Eby的blog在国外软件开发领域影响很大,广为流传的两篇文章是:“Python Is Not Java”与“Java is not Python, either...”,我会陆续为大家奉上。Phillip J. Eby还是一位多面手,除了在Python上的深厚造诣外,在心理学研究上也颇有建树,著作有《You, Version 2.0》

注:
原文的链接:http://dirtsimple.org/2004/12/python-is-not-java.html
翻译来自CSDN,链接:http://blog.csdn.net/pjeby/archive/2006/08/06/1028334.aspx

星期四, 七月 06, 2006

介绍Python编程相关思想——动态函数重载(Dynamic Function Overloading)

All Things Pythonic
Dynamic Function Overloading
by Guido van Rossum
April 7, 2006
 
Summary
I've checked an implementation of dynamic function overloading into Python's subversion sandbox.

I'm going to stick to the term "(dynamically, or run-time) overloaded functions" for now, despite criticism of this term; the alternatives "generic functions", "multi-methods" and "dispatch" have been (rightly) criticized as well.

A slightly optimized implementation is here: http://svn.python.org/view/sandbox/trunk/overload/

Phillip Eby has suggested that my implementation is too slow; but I recommend that you run test_overloading.py and see for yourself. (This requires the Python 2.5a1 release that was just released from python.org.)

Phillip also suggested that I use issubclass() instead of relying on the MRO (method resolution order); he believes the MRO approach can cause false precision. I'm not sure I believe that, since for single dispatch my approach actually matches the class method lookup approach exactly.

There are tons of additional features or potential requirements. For example, should there be a mechanism to invoke the "next" applicable method? But the algorithm doesn't clearly produce a next method. Also, in the case of ambiguity, should an exception be raised or the default method be invoked? (Or perhaps the "next" method, if we can agree on what it is?)

In terms of optimization, I believe that a little bit of code generation could make overloaded functions nearly as fast as hand-coded functions; see 'accelerated' in test_overloading.py.

And no, this isn't for Python 2.5; it's a highly contagious but also highly controversial thing that should be tried out in Python 3000 (or perhaps as a 3rd party add-on for 2.5) first.

I totally forgot that I blogged about this earlier, over a year ago. My implementation then was pathetic compared to today's! Phillip's ideas have helped a lot.

My hope is to eventually drive PyProtocols out of the market. Phillip should be so proud of me. :-)
 
 
 
译:

Python编程思想

介绍Python编程相关思想
动态函数重载(Dynamic Function Overloading)

我已经向Python的subversion沙箱里提交了一个新的动态函数重载的实现。
尽管"(动态或运行时)重载函数[(dynamically, or run-time) overloaded functions]"这一术语受到各方批评,我还是要继续讲讲它。"(动态或运行时)重载函数"又可称为泛型函数(generic functions)、多重方法(multi-methods)或分派(dispatch),这些术语也都受到了批评。
大家可以在http://svn.python.org/view/sandbox/trunk/overload/上看到一个稍微优化过的实现。Phillip Eby曾说我的实现速度太慢,不过,我建议你们运行一下test_overloading.py,看看你们的情况怎么样。(这需要Python 2.5a 1版本,python.org刚刚发布了该版本。)
Phillip还建议我使用issubclass(),而不要依赖方法解析次序(Method Resolution Order,简称MRO)。他认为,MRO方法会导致虚假的准确性。我不知道是否会这样,因为对于单一派遣(single dispatch)而言,我的方法确实与类方法查找完全匹配。当然,这需要很多附加功能或潜在要求。例如,是否存在调用"next" 可用方法的机制?但是,算法没有清楚地生成下一个方法。另外,如果存在歧义,是否应该抛出异常或者调用缺省方法(或"next"方法,如果我们能够对它的定义达成一致的话)?
对于优化,我相信代码生成(code generation)可以让重载的函数和手写函数运行得一样快。请参见test_overloading.py中的accelerated。
这并不适用Python2.5。这是一个颇具争议的话题,所以应该到Python3000才来试验(也许可以作为Python2.5的第三方附加品)。
大约在一年前我曾经写过相关话题的blog,但是我却忘记有这么一回事了。与今天的情况相比,那时候的实现显得暗淡无光。看来,Plillip的提议还是帮了很大忙。
我希望最终将PyProtocols挤出市场。Plillip应该为我感到骄傲。

(原文链接网址:http://www.artima.com/weblogs/viewpost.jsp?thread=155514;Guido van Rossum的英文blog网址: http://www.artima.com/weblogs/index.jsp?blogger=guido

(翻译转自:http://blog.csdn.net/gvanrossum/category/217090.aspx

[转]Python 3000――配接(Adaptation) 还是泛型函数(Generic Functions)?

All Things Pythonic
Python 3000 - Adaptation or Generic Functions?
by Guido van Rossum
April 5, 2006
Summary
We've started discussing Python 3000 for real. There's a new mailing list and a branch. The first point of order is about process; a slew of meta-PEPs are being written (and the goal is to avoid a repeat of Perl 6 :-). But I'm blogging about a feature proposal that evolved dramatically over the past days.


Alex Martelli has been arguing for adaptation since the dawn of time; and at various times he's chided me for not seeing the light. Well am I ever grateful for dragging my feet!

Adaptation

Let me first briefly describe adaptation, reduced to its simplest form. The motivation comes from a common situation where an object wrapper is required (aptly named the Adapter Pattern). PEP 246 proposes a built-in function adapt(X, P) where X is any object and P a Protocol. We intentionally don't define what a protocol is; all we care about that it can be represented by an object. The call adapt(X, P) returns an object constructed from X that satisfies P, or raises an exception if it can't. It uses a global registry which maps types and protocols to adapter functions; we can write this as a dict R = {(T, P): A, ...}. Then, adapt(X, P) computes the adapter A = R[type(X), P] and then returns A(X). There is a registration function register(T, P, A) which simply sets R[T, P] = A. Read Alex's post for a gentler explanation (and lots of things I left out).

As soon as Alex had posted this version of how adaptation works, several people (including myself) independently realized that the global registry (which has always been a sore point to some) is a red herring; the registry could just as well be separated out per protocol! So now we make adapt() and register() methods on the protocol: instead of adapt(X, P) we write P.adapt(X) and instead of register(T, P, A) we write P.register(T, A). The signature of A is unchanged. I call this "second-generation adaptation".

The nice thing about this is that you no longer have a fixed global implementation of exactly what register() and adapt() do. Alex mentions a whole slew of issues he's ignoring but that would need to be addressed for a real-life implementation, such as how to handle adaptation for an object where its type hasn't been registered but some base type has been registered; or the notion of inheritance between protocols (useful when you equate protocols with interfaces, as in Zope and Twisted); or automatic detection of the where an object already implements a protocol/interface (again useful in Zope and Twisted). Several of those extensions make lookup performance an issue, and there are different ways to address that. By having multiple protocol implementations (each implementing the same adapt() and register() APIs) each framework can have its own notion of how adaptation works for its own protocols, without having to deal with a fixed global implementation of adaptation that may or may not do the best thing for a particular framework.

Generic Functions

But then Ian Bicking posted a competing idea: instead of adaptation, why don't we use generic functions? In his and Phillip Eby's view these are more powerful than adapters, yet in some sense equivalent. Let me briefly describe generic functions to set the stage.

A generic function, G, is a callable that behaves like a function (taking arguments and returning a value) but whose implementation is extensible and can be spread across different modules. TG contains a registry of implementations which is indexed by a tuple of the combined types of the arguments. Suppose we want G to be callable with two arguments, then the registry would map type pairs to implementation functions. We'd write G.register((T1, T2), F) to indicate that F(X1, X2) is a suitable implementation for G(X1, X2) when type(X1)==T1 and type(X2)==T2. The simplest implementation would just map the arguments to their types (or better, classes), convert to a tuple, and use that as a key in the registry to find the implementation function. If that key is not found, a default implementation is invoked; this is provided when G is first defined and can either provide some fallback action or raise an exception.

A useful implementation of generic functions must also support looking for matches on the base types of the argument types. This is where things get hairy, at least when you have multiple arguments. For example, if you have a solution that is an exact match on the first argument and a base type match on the second, and another that is a base type match on the first argument an exact match on the second; which do you prefer? Phillip Eby's implementation, RuleDispatch (part of PEAK) refuses to guess; if there isn't one dominant solution (whatever that means) it raises an exception. You can always cut the tie by registering a more specific signature.

C++ users will recognize generic functions as a run-time implementation of the strategy used by the C++ compiler to resolve function overloading. Fortunately we're not bound by backwards compatibility with C to repeat C++'s mistakes (which, for example, can cause a float to be preferred over a bool). Lisp or Dylan users (are there any left? :-) and PyPy developers will recognize them as multi-methods.

In order to contrast and compare the two ideas, I posted a very simple version of adaptation and generic functions, applied to the idea of reimplementing the built-in iter() function. I used descriptors for registration, making the signatures slightly different from what I showed above, but the essence is the same.

The Lightbulb Went Off

Now we're ready for the Aha! moment (already implicit in Ian's and Phillip's position), brought home by an altenative version of the Protocol independently developed by Tim Hochberg: P.adapt(X) is just a verbose way of spelling a generic function call G(X)!

Interestingly, it took Alex a little time to like this -- he was thinking of adaptation as more powerful because it can return an object implementing several methods, while doing the same thing with generic functions would required a separate generic function for each method. But of course we can write a generic factory function, which returns an object with multiple methods, just like adapt() can; and the generic function approach wins in the (common) case where we want to adapt to a "point protocol" -- an interface with just one method, which we immediately call in order to obtain the desired result. When using adaptation, this would require each adapter to use a helper class with a single method that does the desired operation; when using a generic function, the generic function can just do the operation. And we haven't even used generic function dispatch on multiple arguments!

I'm not sure where this will eventually lead; but I've already killed PEP 246 (adaptation) and PEP 245 (interfaces) in anticipation of a more "generic" proposal.

Acknowledgements

  • Aahz, for bringing Alex's post to my attention
  • Google, for getting Alex and me together on the same campus so we could have face time
译:

我们开始真正地讨论Python3000了。这里有一个新的邮件列表和一个版本分支。首要的问题是关于流程的。Python 增强建议书(Python Enhancement Proposal,简称PEP)的很多新格式正在制定,目的是为了避免重蹈Perl 6的覆辙:-)。这篇blog所讨论的东西在过去一段时间里已经发生了很大的变化。

自盘古开天地之日起,Alex Martelli就一直是配接的忠实拥护者。他经常埋怨我对配接的光芒视而不见。现在,我为自己当时的不开窍而感到庆幸。

我先用最简单的形式来介绍一下配接吧。当需要用到对象包装器(object wrapper)[配接器模式(Adapter Pattern)或许更贴切]时,我突然萌发了这个想法。PEP246打算提供一个内置的函数adapt(X, P), X可以是任何对象,而P也可以是任何Protocol。我们故意不对protocol进行定义,只要它可以通过对象表现出来就可以。调用adapt(X, P)返回一个由X构建并满足P的对象,如果创建对象失败,则抛出一个异常。它使用全局注册表(global registry)为配接器功能提供了类型和protocol之间的映射关系。我们可以写为dict R = {(T, P): A, ...}。然后,adapt(X, P) 计算出adapter A = R[type(X), P],并返回A(X)。 还有一个注册函数register(T, P, A),它简单地设置 R[T, P] = A。请参见Alex更为精彩的解释,他补充了很多我遗漏掉的东西。

当Alex提出他对配接工作原理的这一看法,好几个人(包括我自己在内)都意识到全局注册表(对一些人来说,它永远是一个伤痛)是没有必要的。每个protocol都可以有自己的注册表(registry)。所以,现在我们在protocol上使用adapt()和register()方法。我们使用P.adapt(X)而非adapt(X, P),使用P.register(T, A)而非register(T, P, A)。A的签名(signature)保持不变。我称之为第二代配接(second-generation adaptation)。

对于register()和adapt()到底有什么用,你们再也不用局限于一种固定的全局实现,这是一个好消息。Alex提到了很多他忽略的问题,但是如果要真正实现,这些问题需要得以解决。例如,如何处理对象类型未被注册而一些基本类型已被注册的配接,protocol之间的继承是如何定义的(当你把protocol和接口看得同等重要时会很有用,就像Zope和Twisted一样),对象已实现protocol/接口时的自动检测(这在Zope和Twisted中有用)。这些扩展(extension)使得查找性能(lookup performance)成为一个问题,我们可以通过几种不同的方法来解决。通过多重协议实现(multiple protocol implementations)(每次都实现adapt()和register() APIs),每个框架(framework)都对配接如何为其自身拥有的protocol服务有自己的主张,而没必要使用一个固定的全局实现。对于一个特定的框架而言,配接的全局实现可能达到最佳效果,但也未必就是最好的选择。

Ian Bicking提出了一个对立的观点:我们为什么不使用泛型函数而非配接呢?他和Phillip Eby都认为泛型函数具有的功能比配接器更强大,至少可以与之抗衡。现在我就来简要说一下泛型函数。

一个泛型函数G,可以被调用,这种行为类似一个普通函数(取参数并返回一个值),但其实现是可扩展的(extensible),并可以在不同的模块中进行定义。TG包含一个由复合类型参数的元组索引的注册表实现。假设我们想让具有两个参数的G可调用,那么注册表将会把type pairs映射到实现的函数中。我们可以使用G.register((T1, T2), F) 来显示地定义,当type(X1)==T1、type(X2)==T2时,F(X1, X2)是G(X1, X2)的合适的实现。 最简单的实现就是把参数映射到它们的类型(类或许更好),转换为元组,并利用它作为注册表的键值来找到实现函数。如果没有找到健值,就调用缺省实现,前提是预先定义G,并提供一些恢复措施或抛出一个异常。

一个有用的泛型函数实现也必须支持在参数类型的基本类型上查找匹配。这就是使事情变得复杂的地方,特别是当你有多个参数时。例如,你有一个实现方案,它与第一个参数完全匹配,基本类型与第二个参数匹配;另一个实现方案是,它与第二个参数完全匹配,基本类型与第一个参数匹配。这种情况下,你会选择哪一种?Phillip Eby的实现、RuleDispatch (part of PEAK) 拒绝做出猜测; 如果没有占优势的实现方案(不管它是什么意思),都会抛出异常。你可以通过注册一个更加具体的签名来彻底解决问题。

C++用户会认为泛型函数是一个C++编译器用来解决函数重载问题的策略的运行时实现。幸运的是,我们不需要与C向后兼容,从而避免了重蹈C++的错误(如,导致浮点类型的优先级高于布尔类型。)。Lisp或 Dylan用户(不知是否还存在:-),以及PyPy 开发者会认为它们是多重方法(multi-methods)。

为了对比上述两种观点,我提出了一个关于配接和泛型函数的一个简单版本,通过这一版本来再现内置iter()函数的重复实现。我在注册表中使用了描述符,这使得签名与我上面所述有一些轻微的差别,但实质是一样的。

胜负分晓了
现在我们已经为庆祝时刻做好准备了。Tim Hochberg独立开发的一个可以替代的Protocol版本给我们带来了这一欢乐时刻。P.adapt(X)只是泛型函数G(x) 调用的另外一种冗长形式罢了。

有趣的是,Alex费了一些周折才开始喜欢上它。他过去一直认为配接的功能更强大,因为配接可以返回实现多个方法的对象,而泛型函数要实现同样的功能则要求每个方法都有一个单独的泛型函数。当然,我们可以使用泛型工厂函数(generic factory function),它可以像adapt()一样返回带有多个方法的对象。当我们想要适应"point protocol"时,泛型工厂函数也可以起到作用。point protocol是一个只有一个方法的接口,可以马上调用,以获得想要的结果。在使用配接时,这可能要求每个配接器使用一种单一方法的helper类,helper类来完成预期的运行。在使用泛型函数时,泛型函数则可以完成运行。我们还没有在多个参数上使用过泛型函数分派。

我不知道这样是否会成功,但是还没有想到更加通用的方案前,我已kill掉了PEP 246 (配接) 和PEP 245 (接口) 。

(原文链接网址:http://www.artima.com/weblogs/viewpost.jsp?thread=155123;Guido van Rossum的英文blog网址: http://www.artima.com/weblogs/index.jsp?blogger=guido



[转自] http://tb.blog.csdn.net/TrackBack.aspx?PostId=869173