About
RSS

Bit Focus


小记 Python yield 与生成器

    yield 是 Python 中的一个关键字, 这个关键字比较特殊, 用于在任何表达式前, 但它不仅会对其后的表达式有影响, 对整个函数上下文都有影响. 实际上, 凡是在函数体中出现了 yield 关键字, Python 都会对此函数特殊处理, 调用这个函数不再返回值, 而是一个生成器对象.
    比如
def f():
    yield 1

g = f()
print type(g)
其结果是
<type 'generator'>
    如果要获取产生器产生的值, 则需要调用产生器对象的 next 函数
def f():
    yield 1

g = f()
print g.next()
    如果在函数中, 出现多个 yield, 或者函数的执行过程中反复路过某个 yield, 那么 next 每次调用会得到下一个产生的值, 比如
def f():
    yield 1
    yield 2

g = f()
print g.next(),
print g.next()
    得到的会是 1 2. 或如下的循环
def f():
    for x in range(3):
        yield x * 2

g = f()
print g.next(),
print g.next(),
print g.next()
    该循环中反复产生值, 每次产生一个就被传递给 next 函数作为返回值... 当然这样来说是不准确地, 如果上面 for 循环如果跑得太快, 那样会疾速产生值导致 next 函数应接不暇, 这样会有诡异的同步问题. 所以正确的语义应该是
  • 当生成器函数执行到包含 yield 的表达式时, 函数挂起, 并将 yield 之后的表达式作为返回值传递给 next 函数调用
  • 下一次 next 函数调用又会驱动该生成器的函数体继续执行此后的语句, 直到遇见下一个 yield 再次挂起并生成一个值交给 next
  • 如果某次 next 调用驱动了生成器继续执行, 而此后函数正常结束的话, 那么不会有任何值传递给 next 函数, 同时, 生成器会抛出 StopIteration 异常
    对于最后一点, 下面这个例子可作为参考
def f():
    yield 1

g = f()
print g.next(),
print g.next()
    另外生成器中如果出现 return, 它必须是一句空返回, 即 return 之后不允许跟任何表达式, 这也是限制之一.

    从下面这个例子可以更好地窥探生成器的执行模式
def f():
    print 'a'
    yield 1
    print 'b'
    yield 2
    print 'c'

g = f()
print 'start'
print g.next()
print 'next'
print g.next()
print 'end'
    输出结果为
start
a
1
next
b
2
end
    也就是说当第一次调用了 next 之后, f 函数体的执行就挂起了, 等着下一次调用 next. 另外, f 中最后一句应该输出的 c 并没有输出, 因为第二次生成器给出了 2 这个值之后就一直处于挂起状态.
    如果要大慈大悲地让最后一句也执行, 那么需要再加一句 next 调用
def f():
    print 'a'
    yield 1
    print 'b'
    yield 2
    print 'c'

g = f()
print 'start'
print g.next()
print 'next'
print g.next()
print 'end'
g.next()

Permanent Link: /p/502 Load full text

Post tags:

 Python
 yield
 Generator

句读探析 - Stekinscript 实现折行及多行 lambda 语法

上篇

自动机与表达式折行语法分析

概述

    在分析折行实现前, 先说说对基本的算术结构的分析.
    自动机基类 grammar::AutomationBase (grammar/automation-base.h: 30) 能够处理所有没有在 Yacc 语法规则中给出的细节, 比如将因子与算符组成算式, 分析括号配对, 还有对逗号, 冒号等分隔符的处理.
    自动机对可以处理的项目作了基本的分类, 对应于其中各名为 pushXxx 的函数和 matchClosing 函数. 而 matchClosing 函数相当于将 pushCloseParen, pushCloseBracket 等三种不同的结束括号合并成一个函数了, 因为大部分自动机自身不处理三种结束括号, 而将它们派发给其它的自动机处理.
    什么, 自动机还有很多种? 没错, 在 Stekinscript 语法模块设计中, 用来分析表达式的自动机有多种, 不同的自动机互相利用使得代码复杂度能够大大降低. 举个例子, 算术运算自动机 grammar::ArithAutomation (grammar/expr-automations.h: 9) 本身不处理大括号配对, 但表达式里面终归是可以出现大括号裹起来的字典的; 当这种情况发生时, 算术自动机会创建一个字典识别自动机 grammar::DictAutomation (grammar/expr-automations.h: 132), 让它顶替自己处理接下来的部分; 而这个字典自动机自己又是很懒的, 它才不会自己动手去识别用表达式描述的字典的键跟值, 怎么办呢? 与是它再新建一些算术自动机来识别表达式, 而自己只坐等逗号冒号等分隔符, 或者花括号结束.
    无论哪一种自动机, 都只会对特定的一些种类的 Token 感兴趣, 而基类 grammar::AutomationBase 对任何项目的实现都是一句话 (grammar/automation-base.cpp: 58-97)
error::unexpectedToken(posimage);
    那就是报错. 也就是说如果实现的自动机子类不重写它的函数, 就相当于子类丢弃了这个 Token 并报错. 除了这种粗暴的对待方式, 大致上来说每个函数还有如下方式对待 Token
  • 接受并记录, 比如 ArithAutomation 对象在正确的时候遇到运算符或者因子
  • 接受并丢弃, 这个说法有点语死早的感觉, 例子就是上面 ArithAutomationDictAutomation 的故事, 在 ArithAutomation 对象遇到开始的大括号时, 它会创建一个 DictAutomation 对象, 然后丢弃大括号 (留着也没用)
  • 将自身归约并移交, 比如刚才提到, DictAutomation 会委托 ArithAutomation 为它识别键值表达式, 那么此时如果 ArithAutomation 遇到一个冒号, 那么它自身不会处理, 而是首先将自己归约成一个表达式 grammar::Expression (grammar/node-base.h: 24) 对象, 并传递给它的委托者, 然后将这个冒号也一并移交给其委托者处理
  • 将自身归约并丢弃, 比如 DictAutomation 遇到结束的大括号时, 它会将自己归约成一个字典 grammar::Dictionary (grammar/expr-nodes.h: 228) 对象, 完成使命, 然后弃掉结束大括号

自动机栈

Permanent Link: /p/501 Load full text

Post tags:

 Syntax Analysis
 Stekin
 Compiler Construction

NodeJS liteview Postgres Stekinscript 搭建留言板 [完]

上篇

前期准备

安装软件与库

    需要安装
  • Postgres (建议 9 或以上版本)
  • libpq-dev
    npm 安装 Postgres 库
npm install pg

配置数据库与表

    参考这篇文章前半部分配置 Postgres 数据库.
    如果已经为 PG 用户 psuser 授权使用 mydb 这个数据库, 现在进入数据库
psql -Upsuser -dmydb -W
Password for user psuser:
建立数据表用于存放留言
mydb=> CREATE TABLE message (
mydb(>   id SERIAL,
mydb(>   content TEXT,
mydb(>   post_time TIMESTAMP DEFAULT NOW(),
mydb(>   PRIMARY KEY(id)
mydb(> );
NOTICE:  CREATE TABLE will create implicit sequence "message_id_seq" for serial column "message.id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "message_pkey" for table "message"
CREATE TABLE

显示留言信息

更新页面模板

    之前的页面模板实在显得太单薄, 下面修改一下准备放入留言数据
<html>
<head><title>Guestbook</title></head>
<body>
#{foreach(#messages)}
<div>
    <p style='font-size: 0.6em'>#{[post_time]}</p>
    <p style='margin-left: 20px'>#{[content]}</p>
</div>
#{end}
    liteview 文档如是说
  • #{foreach(#XXX)} 即在 XXX 对象上作循环
  • #{[YYY]} 即每次循环的循环变量的 YYY 属性
    因此上面的页面模板需要传入留言对象列表, 每个留言对象有属性 post_time, content. 这命名与数据库表是一致的.
    然后改一下 index.stkn, 放假数据进去先看看效果
view: require('liteview').create()

exports.get: (request, response):
    response.writeHead(200, { 'Content-Type':: 'text/html' })
    response.end(view.render('index.html', {messages: [
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Nostalgia',
        },
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Eternal Rite',
        },
        {
            post_time: '1970-01-01 00:00:00',
            content: 'Shinto Shrine',
        },
    ]}))
    刷下页面, 这些东西就出现了.

从数据库加载内容

    巧妇难为无米之炊, 现在首先要做的是向数据库插入一些数据. 还是更刚才建表一样, 进入 psql, 使用下面的语句插入一些水帖

Permanent Link: /p/500 Load full text

Post tags:

 Postgres
 Stekin
 Tutorial
 Web Server
 NodeJS

NodeJS liteview Postgres Stekinscript 搭建留言板 [零]

前期准备

配置软件与库

    需要安装
  • nodejs 0.8 或以上版本 (之后编译 Postgres 模块需要高版本)
  • npm
  • 下载编译 Stekinscript (非必需) $ git clone git://github.com/neuront/stekinscript.git
    Ubuntu 用户安装 nodejs 0.8 版本请加入下面的源
add-apt-repository ppa:chris-lea/node.js
    创建一个工作目录, 通过 npm 安装要用到的模块
npm install liteview
npm install validator

Stekinscript 快速入门 (非必需)

    请参见 https://github.com/neuront/stekinscript/wiki/Language-Specification.
    对 Stekinscript 没有兴趣的同学可以无视这一步, 在每节之后生成的 Javascript 代码会奉上, 对于 Stekinscript 代码, 可以认为它是更易阅读的 JS.

开工

上手

    创建如下内容的 main.stkn 文件, 先启动一个简单的服务器
http: require('http')

port: 8888

http.createServer((request, response):
    response.writeHead(200, { 'Content-Type':: 'text/html' })
    response.end('<html><body>Hello, World!')
).listen(port, '127.0.0.1')

console.log('Server running at local host port=' + port)
    使用 stekin 编译它 (请用合适的路径替换下面的 stekin, 或者将编译生成的 stekin 可执行程序加入 PATH)
stekin -i require < main.stkn > main.js
    生成的 JS 代码类似 (下面这是经过去除额外括号与重新排版的, 实际上现在 Stekinscript 生成的代码真是惨不忍睹)
(function () {
    const s_http = require("http");
    s_http.createServer(function (s_request, s_response) {
        s_response.writeHead(200, {
            "Content-Type": "text/html"
        });
        s_response.end("<html><body>Hello, World!");
    }).listen(8888, "127.0.0.1");
    console.log("Server running at local host port=8888");
})();
    然后使用 node 来运行它, 就在本地搭建了一枚服务器
node main.js
Server running at local host port=8888
    访问 http://localhost:8888/ 就可以看到服务器响应了.

Makefile

Permanent Link: /p/499 Load full text

Post tags:

 NodeJS
 Stekin
 Web Server
 Tutorial

句读探析 - Stekinscript 对缩进语法的实现

获取源代码

    Stekinscript 的项目地址在 https://github.com/neuront/stekinscript, 获取最新版代码
git clone git://github.com/neuront/stekinscript.git
    本文将以 2012 年 11 月 15 日签入的版本介绍如何设计编译器语法部分, 如果 clone 的版本不是此版本, 可以通过下面的指令回滚到该版本
$ cd stekinscript
$ git reset --hard 9b756ad111fa3cf1d78a89e050a1adf38fa5e0b6

外围模块概述

    下面内容中, 凡是首次提到某个类或函数时, 会在括弧中用文件路径: 行号的方式给出它所声明或定义的位置, 如 main 函数 (stekin.cpp: 11); 上下文很明显有说明哪个文件时, 以 L 行号 说明在哪一行.

对 C++11 的使用

    既然是用 C++ 实现那么内存管理总是个大问题, Stekinscript 自然也要为此做好准备.
    Stekinscript 的内存管理大量依赖于 C++11 中的 std::unique_ptr 跟 move semantic, 并且对 std::unique_ptr 自有一套封装, 包括如下的几个类型
  • util::sptr (util/pointer.h: 94) 是直接对 std::unique_ptr 的封装, 但不允许通过 get() 方法直接取得其中的裸指针, 也不允许通过单目 * 操作符获取对象的引用, 单目型号操作符获取的是 util::sref 类型对象
  • util::sref (util/pointer.h: 23) 是共享引用类型, 几乎等价于一般对象引用类型, 只是不可以通过单目 & 操作符取引用, 另外使用 -> 操作符使用其内部的对象而不是直接使用 . 操作符
    以上类型一般只用于存在多态的情况.
    C++11 的基础知识可以参考这些文章.

错误处理

    需要安装有 Python 2.7 版本, 在源码目录下执行
make code-gen
    生成 report/errors.h 与 report/errors.cpp 文件 (一同生成的还有单元测试需要的 test/phony-errors.h test/phony-errors.cpp).
    report/errors.h 包括了所有编译报错函数, 凡是源代码中出现 error:: 名字空间中的调用均声明于此. (虽然生成的代码格式上寒碜了点, 勉强可读)

Lex/Yacc

    既然是讲 C++ 程序, 本该从 main 函数入手, 但 Stekinscript 有一些依赖在初始化环境 (stekin::initEnv env.h: 9) 之后, 立即调用了 Yacc 中的 yyparse() 函数, 所以这时要去看 Lex/Yacc 文件了.
    grammar/lexica.l 文件定义了词法单元, 其中行首空格 (L 19) 不可被忽略 (否则哪来的缩进语法), 而行首不能有制表符否则报错 (L 24). 缩进层次将被缓存在全局变量 grammar::last_indent 中 (grammar/yy-misc.h: 23). 其它的词法单元看看便罢.Analysis
    然后小的重头在 grammar/syntacticall.y 这个文件中, 它实现了部分简单的语法规则. 去掉其中的语句, 开头的一些产生式如下 (L 42)

Permanent Link: /p/498 Load full text

Post tags:

 Stekin
 Compiler Construction
 Syntax Analysis

句读探析 - 又一个 Javascript 生成器

    前段时间博客没怎么更新, 因为不才去刷一个编译器项目了. 这项目是个 Javascript 产生器.
    起这个念头的契机是看到连 M$ 都动心思搞 JS 生成器了 (然而不得不吐槽的是, 这语言看起来跟 JS 简直一个德行). 而模仿的对象则是 CoffeeScript, 可是我又不想去分支 CoffeeScript 改点语法什么的改成 LatteScript 或 CappuccinoScript (我对 Coffee 的某些语法不很满意), 于是乎自己折腾了一个.
    不过另一方面, 这个编译器 (暂定名是 Stekinscript) 也不是完全无中生有, 而是从我的毕业项目修改过来的. 要说这样有什么不好的话, 就是 --- 这东西用 C++ 实现的 (准确地说是 C++11); 要说有什么好的话, 就是貌似这样可以直接集成 v8, 嗯 (自黑一下, 我没考察过这个, 也没有做这个事情的计划).

    下面介绍一下这个语言本身, 撇开日常的什么分支语句啦函数定义返回这些 (详情可参考文档), 先说说重点.
    首先 Stekinscript 支持缩进语法, 类似 Python 跟 CoffeeScript. 代码很重要的一点就是要看着舒服不是么?
    其次就是非强制类型, 强制类型给人的感觉呢, 就像三岁小孩子学写字旁边有个执戒尺的先生一样, 错一点就打手, 我们已经是断奶的程序员了好么, 所以什么 Dart 就先搁下不用了.
    最后是实际使用的感觉, 这一点我排斥 CoffeeScript 的 lambda 语法如 (x)-> x * x 这种. 要知道输入类似 -> 这样的符号两个字符都在右手无名指跟小指区, 还是一上一下, 而且减号不用按住 Shift 而大于号又需要, 这是有多别扭设计语言的人没知觉么? 还是说已经练成星际争霸九段这点微操不算什么?

    吐槽就到此为止, 下面贴个 Stekinscript 的例子
x: []
setTimeout(():
    if x.length = 10
        return
    x.push(document.getElementById('input').value)
, 2000)

Permanent Link: /p/497 Load full text

Post tags:

 Compiler Construction
 Stekin
 C++

无限的困扰 - 对角线方法注校

    承认或不承认有不可数集这个东西, 倒有点哲学的味道, 而非证明这样数学上的东西. --- 我自己说的.
    这篇文章算不得很数学的研讨文章, 用开源许可证里面经常用到的措辞就是它 AS IS.

无限集的数量概念

    在离散数学中, 集合论部分中有个证明, 给出了两个不那么跟经验相符的说法
  • 整数跟有理数一样多
  • 实数比有理数多 (自然也就比整数多)
    对于无穷数量的比较, 日常的说法就是, 给定无限集合 A 跟 B, 如果每从 A 里面拿出来一个东西, B 里面都相应地能拿出一个东西, 那么至少 B 中元素个数不会比 A 中元素数目少; 如果反过来也成立, 那么 A 跟 B 在元素个数上相同.
    按照上面的思路, 前面一个断言的证明多种多样. 首先经验告诉我们有理数集 Q 不会比整数集 Z 中的元素少 (因为 Z 是 Q 的子集); 反过来, 从 Q 中拿出任何一个数 X/Y, 其中 X Y 分别在用二进制表示为
  • X = ...xnxn-1...x3x2x1x0
  • Y = ...ynyn-1...y3y2y1y0
那么必然可以在 Z 中找到数
  • K = ...xnynxn-1yn-1...x1y1x0y0
与之对应, (并且不同表示的有理数对应的整数一定是不同的,) 因此 Z 中元素也不少于 Q. 命题得证.

常见于对角线方法的误解

    而为了证明实数集 R 中元素多于 Z, 康托尔引入了称为对角线方法的证明方式. 对于这个证明方法如果叙述不严谨, 容易出现误解. 对角线方法不准确地阐述方式包括如下不准确的步骤
  • 反证法: 假定实数区间 [0, 1) 是可数无限的
  • 将这个区间中的元素按照某种方式排列成一个序列, 它与自然数一一映射
  • 构造一个数 S, 它与这个序列中第 1 个数在小数点后第 1 位不同, 而与第 2 个数在小数点后第 2 位不同, ...
  • 那么 S (很) 可能不是一个有理数, 但一定是一个实数, 并且与列表中任何一个实数的值不同
  • 因此产生矛盾, 假定的条件不成立.
    即证: 实数集不是可数的.

    上述描述容易导致如下的误解
  • (S 真的不在列表中吗?) 两个数表现不同, 但它们的值可以是相同的. 比如构造出的 S=0.8999... 而列表中有 0.9000... 存在, 实际上这两个数虽然长得不一样, 值却是相同的.
  • (要花多久构造这个 S?) 如果让一台图灵机来干这个活计, 当然是 --- 无限久. 一个定理的破证明需要无穷多个步骤, 那还叫证明么? 四色定理的证明都比这强.
    看这个看多了, 久而久之当然就不确信对角线方法的靠谱度.

勘误

问题描述

    第一个误解是因为每次讲到这货, 书上 (甚至维基百科也如此) 偏偏要拿实数集来搞事. 其实还是回归证明的本质 (证明可数集的幂集的基数严格大于该可数集的基数), 只要设某个可数集 T (不一定是整数集之类的) 的幂集 p(T) 是可数的, 并且可列出一张表, 然后构造出一个 T 的子集不存在于这张表里面, 构造的要点当然也是.
    因此, 首先明确要证明的是: 给定一个可数集 T, 如 {t0, t1, ... }, 它的幂集中元素的个数严格大于 T 中元素的个数.

证明

    第二个误解不过因为大部分凡人数学家将自己限制到无论什么都得眼见为实, 对于搞数学这确实不是个好事情, 不过有些人 (譬如我) 就爱这样搞. 如果不喜欢, 只须谨记: 只要否定皇帝新衣的不可见性, 它就是可见的. 这才是超凡逻辑学家应有的节操.
    那么证法变为如下

Permanent Link: /p/496 Load full text

Post tags:

 Diagonal Argument
 Set Theory
 Power Set

MIDI 文件格式基础备忘

基本概念与构成

    MIDI 是一种音频格式, 它用来描述声音在什么时间以什么音色多高的音调被发出或终止.
    一个 MIDI 文件中通常包含多个音轨 (track), MIDI 文件可以配置多个音轨以并行方式播放还是串行方式播放, 一般应用上多个音轨当然是同时播放比较好.
    为什么要有多个音轨呢? 这个... 写代码的时候一般也不会把所有代码塞在一个函数里面吧.
    而一个音轨就是一系列事件构成的. 事件包括了演奏或停止直接与发声有关的, 以及换乐器这样的控制指令.

文件内容组成

    MIDI 文件全景可以用下面的产生式来描述.
MIDI =>
    文件头部信息 音轨集

# 至少得有一个音轨
音轨集 =>
    音轨 音轨集
    |
    音轨
    下面解释其中的细节.

文件头部信息详情

文件头部信息 =>
    "MThd" 0x00000006 音轨类型 音轨数 节拍描述
    任何 MIDI 文件开头都由 "MThd" 四个字母 ASCII 码构成.
    接下来 0x00000006 是一个四字节类似大端码表示的整数 (是 0x00 0x00 0x00 0x06 而不是 0x06 0x00 0x00 0x00), 它的含义是文件头部剩下多少个字节. 本来这东西设计是可变的, 不过实际上后面 "音轨类型" "音轨数" "节拍描述" 每个都是固定的 2 字节, 因此这里直接填 6 就行.

    音轨类型有三种取值
  • 0 : 只有一个音轨
  • 1 : 多个音轨, 同时播放
  • 2 : 多个音轨, 串行播放
一般来说这个就取 1 (同样编码为 0x00 0x01, 而不是 0x01 0x00, 后面的音轨数, 节拍描述同样).

    音轨数也是一个 2 字节整数, 表示后面实际含有的音轨数量.
    节拍描述是一个 2 字节整数, 表示一个四分音符 tick 数. (本人没学过乐理, 这信息是照搬过来的)

音轨结构详情

音轨 =>
    "MTrk" 音轨内容长度 事件集 0x00ff2f00
    类似整个文件的开头, 音轨开头固定由 "MTrk" 四个字符填充. 此外, 音轨以固定的四字节 0x00 0xff 0x2f 0x00 结尾.
    音轨内容长度是 4 字节整数, 大端表示. 这个整数指示音轨中事件集的字节数加上末尾的 0x00ff2f00 填充物这 4 个字节.

事件集与事件结构

事件集 =>
    事件 事件集
    |
    ε

事件 => 相对时间 事件类型 事件参数
    相对时间是一个整数, 单位是 tick, 指的是该事件相对于上一个事件延迟多久. 如第一个事件发生在 0x08, 第二个事件的相对时间如果是 0x18, 则实际发生时间会是 0x20.
    比较麻烦的是, 这个时间并不是固定字节长度的, 它的构成大概如下:
  • 如果这个数在 0~127 之间, 则用 1 字节表示, 否则
  • 最后一个字节存放这个数对 128 的模, 再将这个数地板除 128 得到的商以递归的方式在前面 (较低位置) 表示
  • 除了最后一个字节, 其它字节最高为均为 1 (这样判别何处结束)

Permanent Link: /p/495 Load full text

Post tags:

 MIDI

Android SDK r20 深坑逃脱

    最近有点不淡定, 一方面是看到 Google Nexus 7 还不错的样子, 另一方面听说已经有达人在 Nokia N9 上成功刷了 JB, 还能打电话 (作为外观控表示被 N9 的设计萌得死去活来), 所以无论如何近期是要入一台 Android 设备了 (嗯, N9 刷 Android 是肯定要进行的, 用 Meego 总感觉... 应用不多不靠谱? 或许是心理作用吧)
    言归正传, 就在这个时候, 想到, 嗯, 咱还是继续来一炮 Android 研发吧.
    所以搞了 JDK 搞了 Eclipse 搞了 ADT 准备开始.

    好了, 新建 Android Application, 映入眼帘的是三个小红叉, 看得我一阵浓重的違和感. 这些是什么东西? 跟以前见过的大不相同啊, 然后我的视线飘到小红叉左边的项目, 如下
Application Name:
Project Name:
Package Name:

Permanent Link: /p/494 Load full text

Post tags:

 Android
 Configuration Problem

对象炼金术 - SQLAlchemy 使用 Postgres 数据库

安装 Postgres 与基本配置

Postgres, 或者又称为 PostgreSQL (结尾的小写 s 变成了 SQL) 是一个功能强大的数据库.
在 ubuntu 下安装
apt-get install postgresql
其它发行版应该差不多. Windows 用户请自行解决.

安装完成之后, 系统中会多出一个名为 postgres 的用户, 这个用户用于登录数据库. 但无法直接用该用户在 shell 或 xdm 中登录, 须先用其它用户登录 shell, 然后 su 到该用户. 先为该用户设置一下密码
passwd postgres
再切换到该用户
me@host:~$ su postgres
Password:
postgres@host:/home/me $
如果当时处在某个用户的 home 目录, 如 /home/me 下, 则命令行会弹出一行错误信息
could not change directory to "/home/me"
因为 postgres 这个用户无法读取当前用户的 home 目录. 觉得讨厌的话可以
cd /
此时在命令行输入指令进入 Postgres 交互环境
psql
psql (version number)
Type "help" for help.

postgres=# input instructions here
这样会以 postgres 用户身份登录, 如果要输入密码就输入刚才为 postgres 用户设置的密码.

这时 postgres 用户相当于数据库的根用户, 就像 root 用户之于 Linux 系统一样, 直接让应用程序使用 postgres 用户是比较危险的. 下面新建一个用户 "psuser", 密码为 "qwerty"
postgres=# CREATE USER psuser WITH PASSWORD 'qwerty';
CREATE ROLE
当然 Postgres 是不区分大小写的. 不过这里 (包括下文) 中将以全大写标明这些是关键字, 而小写的部分是可替换的. 密码需要用引号包围起来.

然后再建立一个新数据库, 并授权 psuser 这个用户可以使用该数据库
postgres=# CREATE DATABASE mydb;
CREATE DATABASE
postgres=# GRANT ALL PRIVILEGES ON DATABASE mydb TO psuser;
GRANT
这样就差不多好了. 下面开始搞 Python 与 SQLAlchemy 部分.

如果上面每个句子输入之后没回显结果, 并且交互环境开头变为了 postgres-# (注意 # 前是一个减号而非等号), 请查看一下句尾的分号是否漏掉了.

使用 SQLAlchemy 连接 Postgres

有关 SQLAlchemy 的基本使用请见这里.
编写这样一段 Python 代码, 来测试 Postgres 数据库连接是否可用

Permanent Link: /p/493 Load full text

Post tags:

 Postgres
 Python
 SQLAlchemy

使用 Python 2.7 作为 GAE 运行时

GAE 支持 Python 2.7 也有段时间了, 考虑到 2.7 加入了很多有意思的东西, 其中一些特性是先在 Python 3 里面出现, 然后移到 2.7 里面来的, 如
  • 有序字典 (而非默认的散列字典) 类型, 如 collections.OrderedDict({ 1: 'abc', 2: 'def' })
  • 集合类型字面常量, 如 { 1, 2, 3 } (以前版本写作 set([1, 2, 3]))
  • 字典及集合类型的推导式 (这么翻译准确么? Set / dictionary comprehensions), 如 { x: x * x for x in range(5) }
要简单地将运行时环境切换到 Python 2.7 版本, 需要修改 app.yaml 为如下形式
application: YOUR APP ID
version: YOUR APP VERSION
runtime: python27
threadsafe: false
api_version: 1
此外需要修改应用程序启动代码, 类似如下方式
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
import wsgiref.handlers
import webapp2

class Index(webapp.RequestHandler):
    def get(self):
        print 'Hello'

if __name__ == '__main__':
    application = webapp2.WSGIApplication([
        ('/', Index),
    ], debug=True)
    wsgiref.handlers.CGIHandler().run(application)
使用 2.7 版本的另一个好处是 GAE 上为 2.7 版本提供了很多库支持, 包括 jinja (这样就可以摆脱 django 模板了 :-). 如果需要引用 jinja2 库最近版本, 需要在 app.yaml 中加入以下内容
application: YOUR APP ID
version: YOUR APP VERSION
runtime: python27
threadsafe: false
api_version: 1

handlers:
- url: /.*
  script: main.py

libraries:
- name: jinja2
  version: latest
libraries 下面还可以附上其它想要使用的库. 在此处可以查看所有 Python 2.7 运行时所支持的库.

Permanent Link: /p/492 Load full text

Post tags:

 Google AppEngine
 Python

模板之工 - Mako 上手篇

简介

Mako 是一个 Python 模板程序. 用于给定一个字符串, 或给定一个文件, 使用 Python 对象值动态地替换其中的一部分内容.

可是使用 pip 或 easy_install 安装 Mako 库.
# pip install Mako
# easy_install Mako

使用

基本文本替换

安装完成后, 在交互环境中可以通过下面的代码来测试 Mako
>>> import mako.template
>>> print mako.template.Template('Hello, ${ what }!').render(what='Mako')
Hello, Mako!
上述代码中, 引入 Mako 模板模块, 并使用模板对字符串 Hello, ${ what }! 中的动态内容进行替换. 动态内容是形如 ${ what } 这样的 Mako 标记, 它表示将传递给 render 函数的字典中, what 对应的项的值替换这个标记. 因此上面的 render 结果便是 Hello, Mako!.

替换文件中的动态内容

更多的应用场景是将需要生成的内容写成一个文件模板 (如 HTML 页面), 读取这个页面并生成结果内容. 在当前目录下创建 hello-mako.html 文件, 内容如下
<html>
<body>
Dear ${ username }, welcome back!
并使用如下的 Python 代码
import mako.template
templ = mako.template.Template(filename='hello-mako.html')
print templ.render(username='Mako')
就能看到输出的结果 HTML 代码了.

使用中文及指定编码

上面的页面模板代码中如果包含中文或其它非 ASCII 字符, 如
<html>
<body>
你好, ${ username }, 欢迎回来!
那么这一段程序在构造 mako.template.Template 对象时多半会挂掉. 要为 Mako 模板指定输入编码方式才行
import mako.template
templ = mako.template.Template(filename='hello-mako.html', input_encoding='utf-8')
print templ.render(username=u'柑奈')

分支

接下来, 如果这个用户是个管理员用户, 那么希望为这个用户呈现一个后台管理的入口 (当然其它用户就没这个福利了), 那么可以用 Mako 提供的分支来实现
你好, ${ username }, 欢迎回来!

%if is_admin:
    <a href='/admin'>管理页面</a>
%endif
并使用如下语句分别测试
templ = mako.template.Template(filename='hello-mako.html', input_encoding='utf-8')
print templ.render(username=u'柑奈', is_admin=False)
print templ.render(username=u'柑奈', is_admin=True)
%if %endif 之间表示一段分支. 如果确实需要书写一个百分号, 而不是作为 Mako 的指令符号, 那么此处须连续两个百分号 %% 来转义, 通常页面上似乎也不会出现那么多百分号, 所以这个应该还是可以接受的吧.

关于分支, 更详细的例子是
%if condition_a:
    do_a
%elif condition_b:
    do_b
%else:
    do_c
%endif

对象式引用

Permanent Link: /p/491 Load full text

Post tags:

 Python
 Tutorial
 Template
 Mako

多态泛型不两全 - 面向对象中的协变与逆变

在面向对象语言中, 子类覆盖父类中的实例成员函数时, 可以酌情修改返回值类型, 使之变得更加具体. 比如在 C++ 中, 下面的代码是可行的.

Code Snippet 0-0

class base {
public:
    virtual base* copy() const
    {
        return new base;
    }
};

class inherit: public base {
    virtual inherit* copy() const
    {
        return new inherit;
    }
};

因为 inherit* 类型可以无压力转换为 base* 类型, 所以这样做并不会破坏多态机制. 这种做法称之为协变 (covariance).

然而协变一方面满足着面向对象设计的种种需求时, 另一方面与泛型的协作却非常糟糕, 比如如果尝试使用自动指针取代上述代码中的返回值类型就不正确了

Code Snippet 0-1

class base {
public:
    virtual std::unique_ptr<base> copy() const
    {
        return new base;
    }
};

class inherit: public base {
    virtual std::unique_ptr<inherit> copy() const
    {
        return new inherit;
    }
};

编译这一段代码会得到类似如下的错误

invalid covariant return type for 'virtual std::unique_ptr<inherit> inherit::copy() const'

简而言之, std::unique_ptr<base>std::unique_ptr<inherit> 并无实质上的继承关系, 因此无法如此重定义子类函数的返回类型.

这并不是语言层面出现的疵漏, 如果这确实可行, 反而还会引起问题, 举个例子

Code Snippet 0-2

class fruit {};
class apple: public fruit {public: void do_something_as_apple();};
class banana: public fruit {public: void do_something_as_banana();};

int main(void)
{
    std::unique_ptr<apple> a(new apple);

    /* 如果子类的自动指针对象是可以为父类的自动指针的引用直接引用
       也就是说下面这一句合法 */
    std::unique_ptr<fruit>& f = a;

    /* 接下来利用 f 来重置内部的裸指针 */
    f.reset(new banana);

    /* 由于 f 就是 a 的引用, 因此 a 中的裸指针指向了
       与 apple 类型不兼容的 banana 的实例 */
    a->do_something_as_apple(); // 謎の結果
    return 0;
}

因此, 这种利用自动指针绕着弯子来造错误的行为是绝对不被允许的. 此外, 下面的这种赋值方式 (虽然明显看起来很怪异) 也是不被允许的

int main(void)
{
    inherit* i;
    base*& b = i; // 跪
    return 0;
}

之前的一篇文章也有简单介绍到, 不过当时说的是父类的二级指针不能被子类二级指针所赋值.

除了指针之外, 可以想象数组, 包括 STL 容器也会纷纷中枪, 比如 std::vector<base*> 类型的引用是无法引用 std::vector<inherit*> 类型的对象的.

Permanent Link: /p/490 Load full text

Post tags:

 Java
 C++
 面向对象程序设计

风之力 - Tornado 搭建基于 WebSocket 的聊天服务

    这年头 Python web 框架是有点泛滥了. 下面要介绍的是 facebook 的开源框架 tornado. 这东西比较简单, 而且自带 WebSocket 支持, 可以用它做个简单的聊天室.
    读者最好已具备 Javascript 与 WebSocket 的基础知识.

安装

    使用 easy_install 能很方便地爬到 tornado. 或者, 下载源代码, 解包后在源码目录执行
$ python setup.py build
# python setup.py install
即可.

开张

    首先还是来个 hello world.
import tornado.web
import tornado.ioloop

class Index(tornado.web.RequestHandler):
    def get(self):
        self.write('<html><body>Hello, world!')

if __name__ == '__main__':
    app = tornado.web.Application([
        ('/', Index),
    ])
    app.listen(8000)
    tornado.ioloop.IOLoop.instance().start()
    保存为 main.py, 然后执行
$ python main.py
    并访问 http://localhost:8000/ 即可看到页面中的 "Hello, world!".

    在分支中定义的 app 在构造时接受的一个列表参数
[
    ('/', Index),
]
用来配置 URL 映射, 比如这里访问根路径则映射至 Index 实例去处理, 在 Index 实例中, 定义的 get 方法将会处理请求.

处理 WebSocket 连接

添加请求处理类

    接下来就进入 WebSocket 环节. 先修改返回的页面, 让这个页面在加载后连接服务器.
class Index(tornado.web.RequestHandler):
    def get(self):
        self.write('''
<html>
<head>
<script>
var ws = new WebSocket('ws://localhost:8000/soc');
ws.onmessage = function(event) {
    document.getElementById('message').innerHTML = event.data;
};
</script>
</head>
<body>
<p id='message'></p>
        ''')
    修改这个类后, 然后在控制台中止服务器 (猛击 Ctrl-C), 并重新启动之.
    现在, 访问 http://localhost:8000/ 会遇到 404 错误, 因为 WebSocket 请求的 URL "ws://localhost:8000/soc" 还没有映射任何处理器, 因此这里需要再添加一个, 用于处理 WebSocket 请求的类.
import tornado.websocket

class SocketHandler(tornado.websocket.WebSocketHandler):
    def open(self):
        self.write_message('Welcome to WebSocket')
    并为这个类加上 URL 映射

Permanent Link: /p/489 Load full text

Post tags:

 Python
 Web Server
 Tornado
 Tutorial

对象炼金术 - SQLAlchemy 多外键关联同一实体类

    继续上节 SQLAlchemy 外键的探索.
    假设现在需求改变了, 需要给每个 Album 加个字段表示作词者, 而作词者本身也应是 Artist 的实例 (比如很多演唱者会自己为歌曲作词编曲等等), 这时 Album 会有超过一个关联到 Artist 的外键.
    如果仅仅简单如下处理
class Album(Base):
    __tablename__ = 'album'
    album_id = sqla.Column('id', sqla.Integer, primary_key=True)
    name = sqla.Column('name', sqla.String)
    artist_id = sqla.Column('artist', sqla.ForeignKey('artist.id'))
    artist = sqlorm.relationship(
          Artist,
          backref=sqlorm.backref('albums', cascade='all,delete-orphan'))
    lyricist_id = sqla.Column('lyricist', sqla.ForeignKey('artist.id'))
    lyricist = sqlorm.relationship(
          Artist,
          backref=sqlorm.backref('written_albums', cascade='all,delete-orphan'))
然后添加一点数据进去
def save():
    artist0 = Artist(name='aki misawa')
    artist1 = Artist(name='katou emiri')
    artist2 = Artist(name='yamada hirosi')
    album0 = Album(name='stella musica', artist=artist0, lyricist=artist0)
    album1 = Album(name='hoshikage no ama no hara', artist=artist0,
                   lyricist=artist0)
    album2 = Album(name='jump!', artist=artist1, lyricist=artist2)
    session = Session()
    try:
        session.add(album0)
        session.add(album1)
        session.add(album2)
        session.flush()
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save()
结果运行立即悲剧, SQLAlchemy 会报下面的错误
sqlalchemy.exc.ArgumentError: Could not determine join condition between parent/child tables on relationship Album.artist. Specify a 'primaryjoin' expression.  If 'secondary' is present, 'secondaryjoin' is needed as well.
也许这是 SQLAlchemy 的问题, 明明这么明了的外键关系咋就跪了呢.

    好吧, 简单的正确答案是, 手动配置一下外键的 primaryjoin 属性, 如下

Permanent Link: /p/488 Load full text

Post tags:

 ORM
 SQLAlchemy
 Python
 Tutorial

对象炼金术 - SQLAlchemy 中关系的级联

    在上一节中谈到了如何把有关联的对象一起塞进数据表中, 现在来试试从数据表里面取出一条数据然后删掉.
    在之前代码的基础上添加下面的函数
def remove_album(name):
    session = Session()
    try:
        for a in session.query(Album).filter(Album.name == name).all():
            session.delete(a)
        session.flush()
        session.commit()
    finally:
        session.close()
    在这个函数中, 查询 Album 类型, 然后调用查询对象的 filter 函数, 指定列 name 的值严格等于该 name 参数. 接下来, 将查询得到的对象逐个删除.
    来看看搞起来如何
if __name__ == '__main__':
    save()
    remove_album('stella musica')
    list_all()
    嗯, 看起来还不错的样子.

    那么, 接下来试试删 Artist
def remove_artist(name):
    session = Session()
    try:
        for a in session.query(Artist).filter(Artist.name == name).all():
            session.delete(a)
        session.flush()
        session.commit()
    finally:
        session.close()

if __name__ == '__main__':
    save()
    remove_artist('katou emiri')
    list_all()
于是乎就悲剧了.
    崩盘之前的几行输出似乎是这样子的
= Albums =
stella musica
+ Artist: aki misawa
hoshikage no ama no hara
+ Artist: aki misawa
jump!
+ Artist:
    也就是说, remove_artist 确实从数据库中删去了名字为 'katou emiri' 的那个值, 因此名为 'jump!' 的那个 Album 实例的 artist 域成了 None, 悲剧就发生了.

    要解决这个问题, 就得在 AlbumArtist 的关联关系上动动手脚, 加上级联信息.

Permanent Link: /p/487 Load full text

Post tags:

 SQLAlchemy
 ORM
 Python
 Tutorial

0 1 2 Page 3 4 5 6 7 8 9 10 11 12


. Back to Bit Focus
NijiPress - Copyright (C) Neuron Teckid @ Bit Focus
About this site