编译器插件(1)

一个语言的编译器往往都支持所谓的 plugin,它们存在的理由大概是编译器本身是一个非常复杂的东西,如果只能借助它完成语言的翻译(一种语言到另一种语言),有点大材没有尽用。因此通过 plugin 可以为高端用户提供一次搭顺风车的机会。我们这里简单的看三个例子,算是初来练手了。

这里我们先看 protobuf 的 plugin。作为一个定义数据类型的语言,它可以为多种语言生成数据在该语言中的类型、容器,并提供对应 serialize/deserialize 的代码。那么在处理 protobuf 的定义过程中,我们怎么建立一个 plugin 呢?

一个简单的 python plugin 可以参看这里,最核心的部分就是将 CodeGenerationRequest 转换到 CodeGenerationRequest 对象的过程。这最终是一个 binary,通过 –plugin=protoc-gen-NAME 传递给 protoc,同时可以指定 –NAME_out 作为 plugin 的输出。比较有意思的是我们可以看到,在 traverse proto 文件的过程中我们实际是通过 proto 的 descriptor API 来判断处理的是什么类型的信息。这跟我们使用 reflection API 来操纵 proto 是非常类似的。

比较复杂的 plugin 我们可能还是希望使用 C/C++ 来实现,这部分一般通过 plugin.h 提供的 API 来完成。

试想如果我们希望为每个被标注过的 message 类型生成一些额外的 C++ 代码,比如将某个 proto message 关联到某个类,这样可以提供一些额外的 accessor 方便我们使用,那么我们大致如何处理这样一个事情呢?

  • 引入我们自己的 options,在 message 里面通过诸如 binding_class 指定一个需要生成的 C++ 类
  • 为我们生成的 binding class 提供一个公用的基类,这样可以减少一些重复性的代码,很显然这个会使用 CRTP
  • plugin 的实现其实挺直接,拿到 message 看有没有我们的 options,有的话就写 CodeGenerationResponse

又比如说,如果我们需要为 proto 定义的数据提供一个 query engine,某些数据其实可以通过 options 标注其表达(比如 uint64 可能是一个 timestamp 而通过 query 显示出来的时候可能就当个日期显示比较合理)。我们是否也可以通过 plugin 来 customize 这些 field 对应的 rendering 的代码呢?

通过这个例子我们可以清楚的看到一个 compiler 的 plugin 本身就是在 inspection 过程中嵌入的一些 user hook。对于更加复杂的 compiler/language,这个 plugin 获得的功能会更加广泛。

编译器插件(1)

python 杂谈(1)

既然 style guide 上已经列举出来了很多有意思东西,这里就开始这个系列了

metaclass

我们可以先看看 python 提供的 abc,它是一个基于 metaclass 概念实现的 module,我们只需要在定义一个 class 的时候指定 __metaclass__ 为 abc.ABCMeta 就可以直接使用这个 module 提供的一些功能,比如 @abstractmethod,registration 等。为了理解这个 module 我们就必须理解 python 的 metaclass 到底是一个什么样的机制。从某种意义上来说 python 这一点应该是向 ruby 学习的。这部分参看这里

python 的核心思想就是万物皆对象,我们所有的对象都存在一个生成它的“类”,而类本身也是一个对象,每当我们在一个 module 里面写 class 的时候可以理解为我们定义了一个“类对象”,这些类对象的共同特点就是提供 __init__ 方法,类似与一个 factory method 用于生成我们看到的对象。但是类与一般对象的区别在于,一个对象生成了之后可以 dynamically change,而类按照写法是 statically code 在 module 里面的,我们在某些时候希望能够通过程序改变类对象本身,这就涉及到了 metaclass。

metaclass 与 type 关键字紧密相关,type 不仅可以取得对象的类别,还可以通过类名、一个 tuple (父类列表)和一个 dict (成员)生成一个类,也就是说它可以构造类对象。Python 默认使用 type 来生成类对象,同时允许我们提供一个类似 type 的东西,来改变 type 默认的行为。为了证实这一点,我们可以通过 __class__ 来看,一般的对象 __class__ 指向对应的类对象,而对应的类对象的 __class__ 则为 type。

从某种意义上来说 metaclass 可以认为是类对象的 factory method,只是它可以走得更远。python 允许我们通过 __metaclass__ 来覆盖默认的 type 作为一个类的 metaclass

  • 我们可以用任意一个函数作为 metaclass,只要它和 type 一样接受类名(字符串),一个 tuple(父类列表)和一个 dict(成员列表)并且返回一个类对象就行,怎么生成类对象呢?我们还是可以将对应的参数 forward 给 type 的
  • 也可以用一个类,只要它继承 type,提供了 __new__ 方法,这个方法和 type 接受的参数类似,但是多一个参数表示其上级 metaclass

很显然,有了这个认识我们就可以想象当我们改变一个类的 metaclass 之后,其子类的构造将会调用一样的 code,这时诸如 abc 之类的小把戏就可以通过一个查询来实现对应的继承,检查 abstract 方法什么的了。希望了解更多的话,阅读这个 PEP 吧。

copy

Python 虽说是 GC 语言,但是它使用了比较保守的 copy 语义,很多时候只是传递引用,这会导致一些看起来没什么问题的代码出现意想不到的情况,比如

a = [list()] * 5
a[0].append(1)
print a

结果居然是 [[1], [1], [1], [1], [1]]。我们可以如下解决

a = [list() for _ in xrange(5)]

这种问题在 numpy 和 pandas 里面也经常出现,记得需要的时候显式的注明 deep=True 或者用 copy.deepcopy。

annotation

annotation 本身就是一个函数,它输入是被 annotate 的对象(类,函数),输出也是同类的对象。annotation 如果还有参数,其本质就是一个返回前者的函数,所以有点 nestedness。更多参看这里

python 也支持 annotation class,一般需要保持额外的 state 的时候 class 的 __init__ 就可以发挥作用,其 __call__ 方法和 function 是一样的,def 本身也就是定义一个函数对象,它带有 __call__ 的实现。

inspect

这个 module 允许我们做一些类似 reflection 的事情,比如看看某些对象是不是函数或者别的什么,这在很多 annotation 的实现里面非常有用,annotation 本身是一个函数,输入是一个类(或者函数),输出是一个被其处理过的类(或者函数),这时候我们可能需要通过这个对象的 __name__ 做一些查询等操作,那就是字符串转换到某些条件,这时看看 inspect 是个不错的选择。

__getattr__

这是一个 fallback 的查询方法,当 self 里面不存在 也没有通过 def 定义对应的成员时就会调用 __getattr__ 方法,默认它抛出异常,我们可以在这里加一些自己的逻辑,比如通过网络查询等等。

——————
And it came to pass, as he interpreted to us, so it was; me he restored unto mine office, and him he hanged

python 杂谈(1)

TensorFlow 学习(2)

准备工作

决定玩玩 docker 版本的,工作环境为 Mac

  • 安装 VirtualBox 最新版本,docker 在 Mac 上不能直接使用(依赖 Linux kernel 的 cgroup 等特性)
  • 安装 docker toolbox,这是一些在 Mac 下使用 docker 的工具,装好后会自动配置一个叫 default 的虚拟机,这个虚拟机可以用 docker-machine 命令来操纵,这个虚拟机已经配置好了 ssh,因此可以直接 ssh 到虚拟机上想干嘛干嘛
  • 在 docker quickstart terminal 里面,PATH 已经设置好了,可以直接用 docker 等命令,更多可以参考这里
  • 安装 docker 镜像,docker run -it b.gcr.io/tensorflow/tensorflow
  • 测试 TF,参看这里

运行 code 如下

$ docker run -it b.gcr.io/tensorflow/tensorflow
Unable to find image 'b.gcr.io/tensorflow/tensorflow:latest' locally
latest: Pulling from tensorflow/tensorflow

0bf056161913: Pull complete
1796d1c62d0c: Pull complete
e24428725dd6: Pull complete
89d5d8e8bafb: Pull complete
91dc2e300ec6: Pull complete
f2b205f23258: Pull complete
33c0372bafe3: Pull complete
e58fef39e1c1: Pull complete
20bc72598f11: Pull complete
d1627c65b6cf: Pull complete
f44441b29ccc: Pull complete
2b13eefb13c7: Pull complete
151aba0ce7fe: Pull complete
4ea38d2f04f0: Pull complete
74cd363304ab: Pull complete
4ac133eed955: Pull complete
Digest: sha256:bed079b6881a45cd548c319991ef79dbc17b2ec1f6d5295ca4fd5c45b180ef5a
Status: Downloaded newer image for b.gcr.io/tensorflow/tensorflow:latest
root@55f407ea0937:~# python
Python 2.7.6 (default, Jun 22 2015, 17:58:13)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
I tensorflow/core/common_runtime/local_device.cc:40] Local device intra op parallelism threads: 1
I tensorflow/core/common_runtime/direct_session.cc:58] Direct session inter op parallelism threads: 1
>>> print(sess.run(hello))
Hello, TensorFlow!
>>> a = tf.constant(10)
>>> b = tf.constant(32)
>>> print(sess.run(a + b))
42
>>>

Hello World

我们来试试个简单的 TF 程序,参看 mnist 的例子:

from tensorflow.examples.tutorials.mnist import input_data
import tensorflow as tf

if __name__ == '__main__':
  mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
  x = tf.placeholder(tf.float32, [None, 784])
  W = tf.Variable(tf.zeros([784, 10]))
  b = tf.Variable(tf.zeros([10]))
  y = tf.nn.softmax(tf.matmul(x, W) + b)
  y_ = tf.placeholder(tf.float32, [None, 10])
  cross_entropy = -tf.reduce_sum(y_*tf.log(y))
  train_step = tf.train.GradientDescentOptimizer(0.01).minimize(cross_entropy)
  init = tf.initialize_all_variables()
  sess = tf.Session()
  sess.run(init)
  for i in range(1000):
    batch_xs, batch_ys = mnist.train.next_batch(100)
    sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
  correct_prediction = tf.equal(tf.argmax(y,1), tf.argmax(y_,1))
  accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float"))
  print(sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels}))

执行之后

$ python mnist.py
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting MNIST_data/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
I tensorflow/core/common_runtime/local_device.cc:40] Local device intra op parallelism threads: 1
I tensorflow/core/common_runtime/direct_session.cc:58] Direct session inter op parallelism threads: 1
0.917

看起来就这么简单。我们来想个复杂的问题试试?首先我们要理解这段程序

  • 数据用 placeholder 表示,这样 training 和 inference 阶段可以向里面填
  • 优化变量使用 variable 表示,这样 TF 在 training 的时候会使用自动求出来的导数来更新它们
  • 表达出来目标函数(cross_entropy)后我们需要通过一个 solver 来求解,那个 train_step 就是迭代的其中的一步
  • session 通过 run 方法执行一次 computation graph,这是 train_step 表示的一个更新优化变量的图
  • 后面计算 accuracy 也直接使用 TF 自己计算,然后把 test data 代入获得结果

是不是有点心动想找个实际问题来试试看了呢?

——————
And it came to pass at the end of two full years, that Pharaoh dreamed:and, behold, he stood by the river

TensorFlow 学习(2)