├── .gitignore ├── Clustering by Consensus └── 分布式系统.md ├── DBDB_Dog Bed Database ├── DBDB_Dog Bed Database.md └── DBDB_非关系型数据库.md ├── Dagoba_内存型图数据库 └── Dagoba.md ├── LICENSE ├── README.md ├── static-analysis ├── code │ └── TypeCheck │ │ ├── .travis.yml │ │ ├── README.md │ │ ├── REQUIRE │ │ ├── src │ │ └── TypeCheck.jl │ │ └── test │ │ └── test.jl ├── static-analysis.markdown └── 静态分析.markdown ├── 光学文字识别 Optical Character Recognition (OCR) ├── code │ ├── data.csv │ ├── dataLabels.csv │ ├── neural_network_design.py │ ├── nn.json │ ├── ocr.html │ ├── ocr.js │ ├── ocr.py │ └── server.py ├── img │ ├── Figure_13.1.jpg │ ├── WijL.png │ ├── a22.png │ └── a22f.png ├── ocr.markdown └── 光学文字识别.md ├── 决策采样器_A_Rejection_Sampler ├── image │ ├── Z.png │ ├── damage.png │ ├── damage_distribution.png │ ├── gamma.png │ ├── int_px_150.png │ ├── pmf_eq1.png │ ├── pmf_eq2.png │ ├── pmf_eq3.png │ ├── pmf_eq4.png │ ├── probs.png │ └── sum_p.png └── 决策采样器_A_Rejection_Sampler.md ├── 持续集成系统 A Continuous Integration System ├── CI.md ├── image │ └── diagram.png ├── 持续集成系统.html └── 持续集成系统.md ├── 术语库 ├── computer_general.csv ├── computer_hardware.csv ├── computer_software.csv ├── computer_system.csv ├── iicm_1.csv ├── iicm_2.csv ├── iicm_3.csv ├── iicm_4.csv ├── iicm_5.csv ├── iicm_6.csv └── it.csv ├── 简易web服务器 A simple web server ├── http-cycle.png ├── http-request.png ├── http-response.png └── 简易web服务器.md ├── 静态分析 static-analysis ├── code │ └── TypeCheck │ │ ├── .travis.yml │ │ ├── README.md │ │ ├── REQUIRE │ │ ├── src │ │ └── TypeCheck.jl │ │ └── test │ │ └── test.jl ├── static-analysis.markdown └── 静态分析.markdown └── 高效爬虫与协程 A Web Crawler With asyncio Coroutines └── 高效爬虫与协程.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.pyc -------------------------------------------------------------------------------- /Clustering by Consensus/分布式系统.md: -------------------------------------------------------------------------------- 1 | 2 | # 分布式系统 3 | Dustin是一个开源的软件开发者,同时也是Mozilla的一名发布工程师。他参与的项目包括在Puppet中配置主机系统,一个基于Flask的Web框架,为防火墙配置做单元测试,还有一个在Twisted Python下开发的持续集成系统框架。你可以通过[GitHub](https://github.com/djmitche)或者联系他。 4 | ## 介绍 5 | 在这一章,我们将会一起探索如何实现一个网络协议用于可靠的分布式计算。正确实现一个网络协议并不简单,因此我们会采用一些技巧来尽可能的减少、查找和修复漏洞。要建立一个可靠地软件,同样需要一些特别的开发和调试技巧。 6 | ## 情景思考 7 | 这一章的重点在于网络协议的实现,但是首先让我们以简单的银行账户管理服务为例做一个思考。在这个服务中,每一个账户都有一个当前余额,同时每个账户都有自己的账号。用户可以通过“存款”、“转账”、“查询当前余额”等操作来连接账户。“转账”操作同时涉及了两个账户——转出账户和转入账户——并且如果账户余额不足,转账操作必须被驳回。 8 | 9 | 如果这个服务仅仅在一个服务器上部署,很容易就能够实现:使用一个操作锁来确保“转账”操作不会同时进行,同时对转出账户的进行校验。然而,银行不可能仅仅依赖于一个服务器来储存账户余额这样的关键信息,通常,这些服务都是被分布在多个服务器上的,每一个服务器各自运行着相同代码的实例。用户可以通过任何一个服务器来操作账户。 10 | 11 | 在一个简单的分布式处理系统的实现中,每个服务器都会保存一份账户余额的副本。它会处理任何收到的操作,并且将账户余额的更新发送给其他的服务器。但是这种方法有一个严重的问题:如果两个服务器同时对一个账户进行操作,哪一个新的账户余额是正确的?即使服务器不共享余额而是共享操作,对一个账户同时进行转账操作也可能造成透支。 12 | 13 | 从根本上来说,这些错误的发生都是由于服务器使用它们本地状态来响应操作,而不是首先确保本地状态与其他服务器相匹配。比如,想象服务器A接到了从账号101向账号202转账的操作指令,而此时服务器B已经处理了另一个把账号101的钱都转到账号202的请求,却没有通知服务器A。这样,服务器A的本地状态与服务器B不一样,即使会造成账户101透支,服务器A依然允许从账号101进行转账操作。 14 | ## 分布式状态机 15 | 为了防止上述情况发生我们采用了一种叫做“分布式状态机”的工具。它的思路是对每个同样的输入,每个服务器都运行同样的对应的状态机。由于状态机的特性,对于同样的输入每个服务器的输出都是一样的。对于像“转账”、“查询当前余额”等操作,账号和余额也都是状态机的输入。 16 | 17 | 这个应用的状态机比较简单: 18 | ```python 19 | def execute_operation(state, operation): 20 |     if operation.name == 'deposit': 21 |         if not verify_signature(operation.deposit_signature): 22 | return state, False 23 |         state.accounts[operation.destination_account] += operation.amount 24 | return state, True 25 |     elif operation.name == 'transfer': 26 | if state.accounts[operation.source_account] < operation.amount: 27 |             return state, False 28 | state.accounts[operation.source_account] -= operation.amount 29 |         state.accounts[operation.destination_account] += operation.amount 30 | return state, True 31 |     elif operation.name == 'get-balance': 32 | return state, state.accounts[operation.account] 33 | ``` 34 | 值得注意的是,运行“查询当前余额”操作时虽然并不会改变当前状态,但是我们依然把它当做一个状态变化操作来实现。这确保了返回的余额是分布式系统中的最新信息,并且不是基于一个服务器上的本地状态来进行返回的。   35 | 36 | 这可能跟你在计算机课程中学习到的典型的状态机不太一样。传统的状态机是一系列有限个状态的集合,每个状态都与一个标记的转移行为相对应,而在本文中,状态机的状态是账户余额的集合,因此存在无穷多个可能的状态。但是,状态机的基本规则同样适用于本文的状态机:对于同样的初始状态,同样的输入总是有同样的输出。 37 | 38 | 因此,分布式状态机确保了对于同样的操作,每个主机都会有同样的相应。但是,为了确保每个服务器都允许状态机的输入,前文中提到的问题依然存在。这是一个一致性问题,为了解决它我们采用了一种派生的Paxos算法。 39 | -------------------------------------------------------------------------------- /DBDB_Dog Bed Database/DBDB_非关系型数据库.md: -------------------------------------------------------------------------------- 1 | # DBDB: Dog Bed DataBase 2 | 3 | ## 作者简介 4 | Taavi Burns 是[Countermeasure](https://www.countermeasuremusic.com/) 乐团中最新的男低音(有时是男高音)。Taavi致力于打破常规。比如说他参加过的工作有,IBM(做C和Perl),FreshBooks(做所有的东西),Points.com(做Python),现在在PagerDuty(做Scala)。 除此之外 - 当他没有在玩他的Brompton牌折叠式自行车时,你可能会发现他和他的儿子一起玩 我的世界(Minecraft),或者和他的妻子一起参加跑酷(或攀岩或其他冒险)。 Taavi的爱好很广泛。 5 | 6 | ## 简介 7 | 8 | DBDB (狗床数据库) 是一个用Python实现的简单的键值对存储数据库(key/value database)。它把键与值相关联,并将该关联存储在磁盘上。 9 | 10 | DBDB 的特点是在电脑崩溃或程序出错的时候也能保证数据的安全。它也避免了把所有数据同时保存在内存中,所以你可以储存比内存容量更多的数据。 11 | 12 | ## 往事 13 | 14 | 记得有一次我在找一个BASIC程序里的bug的时候,电脑屏幕上突然出现了几个闪烁的光点,然后程序就提前终止了。当我再次检查代码的时候,代码的最后几行消失了。 15 | 16 | 我问了我麻麻的一个懂编程的朋友,然后我意识到程序崩溃的原因是程序太大了,占用到了电脑的显存。屏幕上闪烁的光点是苹果BASIC电脑的一个特性,这表示内存不够用了。 17 | 18 | 从那一刻开始,我变得非常注意内存的使用。我学习了指针和malloc,我学习了数据是怎么存放进内存的。我会非常非常小心的使用内存。 19 | 20 | 几年后,在学习一个面向过程的编程语言 Erlang 的时候,我懂得了进程间的通信不需要把数据再拷贝一份以供另一个进程使用,因为所有数据都是不可变的(immutable)。然后我就喜欢上了 Clojure 的那些不可变的数据结构。 21 | 22 | 当我在2013年看到CouchDB的时候,我认识到了管理复杂数据的结构和机制。 23 | 24 | 我认识到了可以设计一个以不可变的(immutable)数据为基础的系统。 25 | 26 | 我写了一些相关的文章, 27 | 28 | 因为我觉着描述 CouchDB 的核心数据储存原理(根据我的理解)会很有有意思。 29 | 30 | 当我写到一个二叉树自平衡的算法的时候,我才发现这个算法描述起来好复杂啊,边缘情况(edge case) 太多了,比如说解释二叉树的一处改变的时候,树的其他部分为什么也会改变。然后我就不知道怎么写了。 31 | 32 | 吸取了那次的经验教训,我仔细观察了使用递归算法来更新不可变二叉树,感觉这个算法更简单明了,和蔼可亲。 33 | 34 | 所以我又双叒叕一次感受到了不可变的结构的好处。 35 | 36 | 然后,就有了DBDB。 37 | 38 | ## 为啥这个项目很有趣? 39 | 40 | 很多项目都会使用到数据库。但是你没有必要去自己写一个,就算只是存储json文件在磁盘上,也会有各种各样的边缘情况(edge case)需要去考虑,比如说: 41 | 42 | 43 | - 存储空间不足时会发生什么? 44 | - 电脑没电了,数据怎么保存? 45 | - 如果数据大小超过可用内存怎么办? (台式机上的大多数应用程序基本不会出现这种情况,但移动设备或服务器端Web应用程序可能会发生) 46 | 47 | 如果自己写一个数据库,你就知道数据库是怎么处理这些情况的了。 48 | 49 | 我们在这里讨论的技术和概念应适用于应对各种情况(包括发生故障时)。 50 | 51 | 关于不足。。。 52 | 53 | ## DBDB 的不足之处 54 | 55 | 数据库为保证事务(transaction)是正确可靠的,必须具备的四个特性(ACID属性):原子性(atomicity),一致性(consistency),独立性(isolation)和持久性(durability)。 56 | 57 | DBDB 的数据更新方法具有原子性和持久性。这两个属性在后面的章节会做详细介绍。因为我们没有对储存的数据做限制,所以DBDB 不能保持数据的一致性。独立性在DBDB里也没有实现。 58 | 59 | 应用程序的代码当然可以保证自己的一致性,但是实现独立性需要一个事务管理器(transaction manager)。 我们不会在这里试图增加事务管理器的部分,但是你可以通过 [CircleDB chapter](http://aosabook.org/en/500L/an-archaeology-inspired-database.html) 来进一步了解事务管理器。 60 | 61 | 还有一些系统维护的问题需要考虑。 在DBDB中,陈旧数据(Stale data)不会被收回处理,因此更新数据(甚至是使用相同的键)将会导致最终消耗掉所有的磁盘空间(你会很快发现为什么会这样)。[PostgreSQL](https://www.postgresql.org/s) 把处理陈旧数据的过程称之为“吸尘(vacuuming)”,这使得旧的行空间可以重新使用,[CouchDB](http://couchdb.apache.org/) 把处理陈旧数据的过程称之为“压缩”,通过把正常数据(live data)的部分重写入新文件,并替代旧的文件。 62 | 63 | DBDB 可以添加“压缩旧数据”的功能,这就留给读者们作为小练习了【尾注1】。 64 | 65 | ## DBDB 的架构 66 | 67 | DBDB 把“将数据放在磁盘某处”(数据是怎么分布在文件中的;物理层)与数据的逻辑结构(本例中为二叉树;逻辑层) 从键/值存储的内容中分离出来(比如:键`a`与值`foo`的关联;公共API)。 68 | 69 | 许多数据库为了提高效能,通常把逻辑层和物理层分开实现。 比如说,DB2的SMS(文件系统中的文件)和DMS(原始块设备)或MySQL的 [替代引擎](https://dev.mysql.com/doc/refman/5.7/en/storage-engines.html). 70 | 71 | ## DBDB 的设计 72 | 73 | 本文使用了大量篇幅介绍一个程序是怎么从无到有的写出来的。但是,这并不是大多数人参与、开发代码的方式。我们通常先是阅读别人写的代码,然后通过修改或者拓展这些代码来达到自己的需求。 74 | 75 | 所以,我们假设DBDB 是一个完整的项目,然后去了解它的流程和逻辑。让我们先从DBDB的包含的文件开始了解吧。 76 | 77 | ## DBDB 包含的文件 78 | 79 | 下列文件的排列顺序是从前到后,比如说,第一个文件就是距离终端用户“最近的”。 80 | 81 | - `tool.py` 是一个在终端中执行的命令行工具。 82 | 83 | - `interface.py` 定义了一个 `DBDB` 类,它使用`二叉树`(`BinaryTree`) 来实现了Python中的字典(`dict`)。 84 | 85 | - `logical.py` 定义了逻辑层。是使用键/值存储的接口(interface)。 86 | 87 | - `LogicalBase` 提供了使用get, set, commit 的接口,用了一个子类来完成具体的实现。它还用于管理存储的锁定,和内部节点的解引用(dereferencing)。 88 | 89 | - `ValueRef` 是一个Python的对象,是存在数据库中的二进制大型对象 BLOB(basic large object). 它间接使我们能够避免将整个数据存储一次性加载到内存中。 90 | 91 | - `binary_tree.py` 定义了逻辑接口下的二叉树算法。 92 | 93 | - `BinaryTree` 提供二叉树的具体实现,包括get, insert, 和delete。BinaryTree 是一个 不变的(immutable) 的树,所以数据的更新会产生一个新的树。 94 | 95 | - `BinaryNode` 实现了二叉树的节点的类。 96 | 97 | - `BinaryNodeRef` 是一个特殊的`ValueRef`实现,用来实现 `BinaryNode` 的序列化(serialise)和反序列化(deserialise)。 98 | 99 | - `physical.py` 定义了物理层,`Storage` 类提供了持久的,(大部分是)只可添加的(append-only)记录存储。 100 | 101 | 每个文件都只包含一个类,换句话说,每个类只能由一个改变的原因(“each class should have only one reason to change”)。 102 | 103 | ## 读取数据 104 | 105 | 一个简单的例子:从数据库里读取一个数据。一起来看看怎么从 `example.db` 数据库里获取键为`foo`的值吧。 106 | 107 | ``` 108 | $ python -m dbdb.tool example.db get foo 109 | ``` 110 | 111 | 这行代码运行了 `dbdb.tool` 中的 `main()` 函数。 112 | 113 | ```python 114 | # dbdb/tool.py 115 | def main(argv): 116 | if not (4 <= len(argv) <= 5): 117 | usage() 118 | return BAD_ARGS 119 | dbname, verb, key, value = (argv[1:] + [None])[:4] 120 | if verb not in {'get', 'set', 'delete'}: 121 | usage() 122 | return BAD_VERB 123 | db = dbdb.connect(dbname) # CONNECT 124 | try: 125 | if verb == 'get': 126 | sys.stdout.write(db[key]) # GET VALUE 127 | elif verb == 'set': 128 | db[key] = value 129 | db.commit() 130 | else: 131 | del db[key] 132 | db.commit() 133 | except KeyError: 134 | print("Key not found", file=sys.stderr) 135 | return BAD_KEY 136 | return OK 137 | ``` 138 | 139 | 函数 `connect()` 会打开一个数据库文件(或者是创建一个新的,但是永远不会覆盖其它的文件),然后返回一个名为`DBDB`的实例。 140 | 141 | ```python 142 | # dbdb/__init__.py 143 | def connect(dbname): 144 | try: 145 | f = open(dbname, 'r+b') 146 | except IOError: 147 | fd = os.open(dbname, os.O_RDWR | os.O_CREAT) 148 | f = os.fdopen(fd, 'r+b') 149 | return DBDB(f) 150 | ``` 151 | 152 | ```python 153 | # dbdb/interface.py 154 | class DBDB(object): 155 | 156 | def __init__(self, f): 157 | self._storage = Storage(f) 158 | self._tree = BinaryTree(self._storage) 159 | ``` 160 | 161 | 从上面的代码中,我们可以看到`DBDB`的实例中包含了一个对`Storage`实例的引用,它还把这个引用分享给了 `self._tree`。为什么要这样呢?`self._tree`不可以有自己单独的对存储的访问吗? 162 | 163 | 关于哪个对象应该“拥有”一个资源的问题,在设计中通常是一个重要的问题,因为它影响到了程序的安全性。我们稍后会解释这个问题。 164 | 165 | 当我们获得`DBDB`的实例后,获取一个键的值就会通过`dict`的查找功能完成(Python的解释器会调用`DBDB.__getitem__()`)。 166 | 167 | ```python 168 | # dbdb/interface.py 169 | class DBDB(object): 170 | # ... 171 | def __getitem__(self, key): 172 | self._assert_not_closed() 173 | return self._tree.get(key) 174 | 175 | def _assert_not_closed(self): 176 | if self._storage.closed: 177 | raise ValueError('Database closed.') 178 | ``` 179 | 180 | `__getitem__()` 通过调用 `_assert_not_closed` 确保数据库仍处于打开状态。啊哈!这里我们看到了一个`DBDB`需要直接访问 `Storage`实例的原因:因此它可以强制执行前提条件。(你同意这个设计吗?你能想出一个不同的方式吗?) 181 | 182 | 然后DBDB通过调用`_tree.get()`函数(由`LogicalBase`提供)来查找`key`所对应的值: 183 | 184 | ```python 185 | # dbdb/logical.py 186 | class LogicalBase(object): 187 | # ... 188 | def get(self, key): 189 | if not self._storage.locked: 190 | self._refresh_tree_ref() 191 | return self._get(self._follow(self._tree_ref), key) 192 | ``` 193 | 194 | `get()` 先检查了储存是否被锁。目前,我们并不明白为什么在这里可能会有一个锁,但是我们可以猜到它是用来管理数据写入权限的。如果存储没有锁定会发生什么呢? 195 | 196 | ```python 197 | # dbdb/logical.py 198 | class LogicalBase(object): 199 | # ... 200 | def _refresh_tree_ref(self): 201 | self._tree_ref = self.node_ref_class( 202 | address=self._storage.get_root_address()) 203 | ``` 204 | 205 | `_refresh_tree_ref` 将磁盘上数据树的“视图”更新了,这使我们能够读取最新的数据。 206 | 207 | 如果我们读取数据的时候,数据被锁了呢?这说明其他的进程或许正在更新这部分数据;我们读取的数据不太可能是最新的。 这通常被称为“脏读”(dirty read)。这种模式允许许多读者访问数据,而不用担心阻塞,相对的缺点就是数据可能不是最新的。 208 | 209 | 现在,一起来看看是怎么具体拿取数据的。 210 | 211 | ```python 212 | # dbdb/binary_tree.py 213 | class BinaryTree(LogicalBase): 214 | # ... 215 | def _get(self, node, key): 216 | while node is not None: 217 | if key < node.key: 218 | node = self._follow(node.left_ref) 219 | elif node.key < key: 220 | node = self._follow(node.right_ref) 221 | else: 222 | return self._follow(node.value_ref) 223 | raise KeyError 224 | ``` 225 | 226 | 这里用到了用到了二叉搜索。上文中介绍过了`Node` 和 `NodeRef` 是`BinaryTree`中的对象。他们是不可变的,所以他们的值不会改变。`Node`类包括键值和左右子项,这些不会改变。当更换根节点时,整个`BinaryTree`的内容才会明显变化。 这意味着在执行搜索时,我们不需要担心我们的树的内容被改变。 227 | 228 | 当找到了相应的值后,`main()`函数会把这个值写入到`stdout`,输出的值中,并且不会包含换行符。 229 | 230 | ## 插入和更新 231 | 232 | 现在,我们在`example.db`数据库中,把`foo`键的值设为`bar`: 233 | 234 | ``` 235 | $ python -m dbdb.tool example.db set foo bar 236 | ``` 237 | 238 | 这段代码会运行`dbdb.tool`文件中的`main()`函数。这里,我们只介绍几个重要的地方。 239 | 240 | ``` python 241 | # dbdb/tool.py 242 | def main(argv): 243 | ... 244 | db = dbdb.connect(dbname) # CONNECT 245 | try: 246 | ... 247 | elif verb == 'set': 248 | db[key] = value # SET VALUE 249 | db.commit() # COMMIT 250 | ... 251 | except KeyError: 252 | ... 253 | ``` 254 | 255 | 给键赋值,我们使用了`db[key] = value`的方法来调用`DBDB.__setitem__()` 256 | 257 | ``` python 258 | # dbdb/interface.py 259 | class DBDB(object): 260 | # ... 261 | def __setitem__(self, key, value): 262 | self._assert_not_closed() 263 | return self._tree.set(key, value) 264 | ``` 265 | 266 | `__setitem__` 确保了数据库的链接是打开的,然后调用`_tree.set()`来把键`key` 和值`value`存入`_tree` 267 | 268 | `_tree.set()` 由 `LogicalBase` 提供: 269 | 270 | ``` python 271 | # dbdb/logical.py 272 | class LogicalBase(object): 273 | # ... 274 | def set(self, key, value): 275 | if self._storage.lock(): 276 | self._refresh_tree_ref() 277 | self._tree_ref = self._insert( 278 | self._follow(self._tree_ref), key, self.value_ref_class(value)) 279 | ``` 280 | 281 | `set()` 先检查了数据有没有被锁定。 282 | 283 | ``` python 284 | # dbdb/storage.py 285 | class Storage(object): 286 | ... 287 | def lock(self): 288 | if not self.locked: 289 | portalocker.lock(self._f, portalocker.LOCK_EX) 290 | self.locked = True 291 | return True 292 | else: 293 | return False 294 | ``` 295 | 296 | 这里有两个重要的点需要注意: 297 | 298 | - 我们使用了的第三方库提供的锁,名叫[portalocker](https://pypi.python.org/pypi/portalocker)。 299 | - 如果数据库被锁定了,`lock()`函数会返回`False`。否则,会返回`True` 300 | 301 | 302 | 回到`_tree.set()`,现在我们明白了为什么需要先检查数据的锁(`lock()`)了:它会调用`_refresh_tree_ref`函数来获取最新的根节点。然后它会用一个新的树(已经插入/更新过数据)来替代原有的树。 303 | 304 | 插入和更新一个树,不会改变任何一个节点。因为`_insert()`会返回一个新的树。新树与老树会共享数据不变的部分以节省内存和执行时间。我们使用了递归来实现: 305 | 306 | ``` python 307 | # dbdb/binary_tree.py 308 | class BinaryTree(LogicalBase): 309 | # ... 310 | def _insert(self, node, key, value_ref): 311 | if node is None: 312 | new_node = BinaryNode( 313 | self.node_ref_class(), key, value_ref, self.node_ref_class(), 1) 314 | elif key < node.key: 315 | new_node = BinaryNode.from_node( 316 | node, 317 | left_ref=self._insert( 318 | self._follow(node.left_ref), key, value_ref)) 319 | elif node.key < key: 320 | new_node = BinaryNode.from_node( 321 | node, 322 | right_ref=self._insert( 323 | self._follow(node.right_ref), key, value_ref)) 324 | else: 325 | new_node = BinaryNode.from_node(node, value_ref=value_ref) 326 | return self.node_ref_class(referent=new_node) 327 | ``` 328 | 329 | 请注意我们总是返回一个新的节点(含在一个`NodeRef`中)。我们建一个新的节点,它会与旧的节点共享未改变的部分。而不是更新旧的节点上的数据。这使我们的二叉树变得 不可变(immutable)。 330 | 331 | 你可能意识到有有个奇怪的地方:我们还没对磁盘上的数据做任何处理呢。我们目前所做的只是通过移动树的节点来操纵我们对磁盘数据的视图。 332 | 333 | 为了真正的把新的数据保存在硬盘上,我们需要调用`commit()`函数。我们在前面的讲`set`操作的章节见过这个函数。 334 | 335 | `commit`会把所有的脏状态(dirty state)写入内存中的,然后保存下磁盘地址作为树的新根节点。 336 | 337 | 从commit的接口开始看: 338 | 339 | ``` python 340 | # dbdb/interface.py 341 | class DBDB(object): 342 | # ... 343 | def commit(self): 344 | self._assert_not_closed() 345 | self._tree.commit() 346 | ``` 347 | 348 | `_tree.commit()`是在`LogicalBase`里面实现的: 349 | 350 | ``` python 351 | # dbdb/logical.py 352 | class LogicalBase(object) 353 | # ... 354 | def commit(self): 355 | self._tree_ref.store(self._storage) 356 | self._storage.commit_root_address(self._tree_ref.address) 357 | ``` 358 | 359 | `NodeRef`的序列化(serialise)是通过让它们的子节点使用`prepare_to_store()`完成序列化 而完成的。 360 | 361 | ``` python 362 | # dbdb/logical.py 363 | class ValueRef(object): 364 | # ... 365 | def store(self, storage): 366 | if self._referent is not None and not self._address: 367 | self.prepare_to_store(storage) 368 | self._address = storage.write(self.referent_to_string(self._referent)) 369 | ``` 370 | 371 | 这里的`LogicalBase`里面的`self._tree_ref`其实使用了`BinaryNodeRef`(`ValueRef`的子类)。所以`prepare_to_store()`的具体实现方式为: 372 | 373 | ``` python 374 | # dbdb/binary_tree.py 375 | class BinaryNodeRef(ValueRef): 376 | def prepare_to_store(self, storage): 377 | if self._referent: 378 | self._referent.store_refs(storage) 379 | ``` 380 | 381 | [](The `BinaryNode` in question, `_referent`, asks its refs to store themselves:) 382 | 383 | ``` python 384 | # dbdb/binary_tree.py 385 | class BinaryNode(object): 386 | # ... 387 | def store_refs(self, storage): 388 | self.value_ref.store(storage) 389 | self.left_ref.store(storage) 390 | self.right_ref.store(storage) 391 | ``` 392 | 393 | 这个递归会在任何`NodeRef`有未写入的数据更新(比如说缺少`_address`)的时候一直循环下去。 394 | 395 | 现在让我们来回忆一下`ValueRef`里的`store`方法。`store()`里的最后一步是序列化这个节点,然后保存它的存储地址: 396 | 397 | ``` python 398 | # dbdb/logical.py 399 | class ValueRef(object): 400 | # ... 401 | def store(self, storage): 402 | if self._referent is not None and not self._address: 403 | self.prepare_to_store(storage) 404 | self._address = storage.write(self.referent_to_string(self._referent)) 405 | ``` 406 | 407 | 这里,`NodeRef`的 `_referent`保证会有引用的地址,所以我们通过创建这个节点的字节串(bytestring)来序列化它: 408 | 409 | ``` python 410 | # dbdb/binary_tree.py 411 | class BinaryNodeRef(ValueRef): 412 | # ... 413 | @staticmethod 414 | def referent_to_string(referent): 415 | return pickle.dumps({ 416 | 'left': referent.left_ref.address, 417 | 'key': referent.key, 418 | 'value': referent.value_ref.address, 419 | 'right': referent.right_ref.address, 420 | 'length': referent.length, 421 | }) 422 | ``` 423 | 424 | 在`store()`中更新地址在实际上是改变`ValueRef`。 因为它对用户可见的值没有任何影响,所以我们可以认为它是不可变的。 425 | 426 | 根节点`_tree_ref`在`store()`之后(在`LogicalBase.commit()`中),所有的数据就已经保存在磁盘上了。现在我们可以调用根地址的提交(commit)了: 427 | 428 | ``` python 429 | # dbdb/physical.py 430 | class Storage(object): 431 | # ... 432 | def commit_root_address(self, root_address): 433 | self.lock() 434 | self._f.flush() 435 | self._seek_superblock() 436 | self._write_integer(root_address) 437 | self._f.flush() 438 | self.unlock() 439 | ``` 440 | 441 | 我们确保句柄(file handle)已被刷新(所以系统就知道了我们想要所有数据都被保存起来,比如:SSD)以及返回了根节点的地址。我们知道最后一次写入是具有原子性(atomic)的,因为我们将磁盘地址存储在扇区边界上(sector boundary)。这是文件中的最靠前的,所以无论扇区大小如何,这都是正确的,单扇区磁盘写入能由磁盘硬件保证原子性。 442 | 443 | 因为根节点地址要么是旧值要么是新值(没有中间值),所以其他进程可以从数据库中读取而不用锁定。 外部进程可能会获取到旧的或新的数据。所以,提交(commit)是原子性的。 444 | 445 | 因为我们在赋予根节点地址之前,会把新的数据写入磁盘并调用`fsync` syscall [尾注2],所以未提交的数据是无法访问的。 相反,一旦根节点地址被更新,我们知道它引用的所有数据也在磁盘上。以这种方式,提交(commit)也具有持久性(durability)。 446 | 447 | 就是这样! 448 | 449 | ## NodeRefs 是怎么节省空间的 450 | 451 | 为了避免把这个树的数据同时保存在内存中,当从磁盘读取逻辑节点时,其左和右子节点的磁盘地址(还有值)将被加载到内存中。所以访问子节点需要调用一个额外的函数`NodeRef.get()`来获取真正的数据。 452 | 453 | `NodeRef` 只需包含一个地址: 454 | 455 | +---------+ 456 | | NodeRef | 457 | | ------- | 458 | | addr=3 | 459 | | get() | 460 | +---------+ 461 | 462 | 当点用 `get()`后,`NodeRef` 会返回具体的节点,并包括其两个子节点的`NodeRef`类。 463 | 464 | +---------+ +---------+ +---------+ 465 | | NodeRef | | Node | | NodeRef | 466 | | ------- | | ------- | +-> | ------- | 467 | | addr=3 | | key=A | | | addr=1 | 468 | | get() ------> | value=B | | +---------+ 469 | +---------+ | left ----+ 470 | | right ----+ +---------+ 471 | +---------+ | | NodeRef | 472 | +-> | ------- | 473 | | addr=2 | 474 | +---------+ 475 | 476 | 当树的更改未提交时,它们保存在内存中,包括从根(root)向下到更改的叶(leaves)。 当更改还没保存到磁盘时,所以被更改的节点包含具体的键和值,但是没有磁盘地址。 处理写入的进程可以看到这些未提交的更改,并且可以在发出提交之前再次对其进行更改,这是因为`NodeRef.get()`有值的话,会返回一个未提交的值; 在通过API访问时,提交和未提交的数据之间没有区别。其他读者可以看到最新的数据,因为新的更改在根节点的地址被写入磁盘前,是看不到的。并发的更新操作会被磁盘上的文件锁阻止。文件会在第一次更新时上锁,并在提交后解锁。 477 | 478 | ## 留给读者的小练习 479 | 480 | DBDB 允许多进程同时访问同一个数据库。为做到这一点,我们付出的是,读取有时获得的是陈旧的数据。如果我们需要总是读取最新的数据该怎么办? 一个常见的用例是读取值,然后根据该值进行更新。 你如何在“DBDB”上实现这个方法呢?你需要做什么权衡来做到这个功能呢? 481 | 482 | 更新数据存储的算法可以通过改变`interface.py`文件中的这个词`BinaryTree`来使用别的算法。 比如说可以用 B-trees, B+ trees 或其他的结构来提高数据库的效能。一个平衡的二叉树需要做O(log2(n))次节点的读取,来查找值。而B+树只需要更少的次数,比如O(log32(n))次,因为每个节点有32个子节点(而不是2个)。这会很大的提高练习的难度。比如40亿条数据中查找一条信息,这需要大约 log2(2 ^ {32})= 32 至 log32(2 ^ {32})=6.4次查找。每个查找都是随机访问,因为开销非常大,所以这是难以做到的。SSD或许可以用延迟解决,但I/O的节省仍然存在。 483 | 484 | 默认情况下,值以字节的形式(为了能直接传入到`Storage`)存储在`ValueRef`里。二叉树的节点是`ValueRef`的子类。通过[json](http://json.org) 或者 [msgpack](http://msgpack.org) 格式保存更丰富的数据则需要编写自己的文件并将其设置为“value_ref_class”。`BinaryNodeRef` 就是一个使用 [pickle](https://docs.python.org/3.4/library/pickle.html) 来序列化数据的例子。 485 | 486 | 压缩数据库是另一个有趣的练习。 压缩可以随着树的移动通过中间遍历(infix-of-median traversal)完成。如果树节点全部在一起可能是最好的,因为它们是遍历以查找任何数据的。将尽可能多的中间节点打包进到磁盘扇区中可以提高读取性能,至少是在压缩之后,可以提高读取效率。 这里有一些细节需要注意(例如,内存使用),如果你打算完成这个练习。 请记住:在修改前后,总是注意记录性能的改变!你经常会对结果感到惊讶。 487 | 488 | ## 模式与原则 489 | 490 | 有些没有实现的测试接口。作为开发DBDB的一部分,我写了一些描述我想要使用DBDB的测试。第一个测试针对数据库的内存版本进行,然后我将DBDB的存储方式扩展到了磁盘,甚至后来添加了NodeRefs的概念。而且大多数测试并不需要改变,这让我对开发DBDB更加有信心。 491 | 492 | 遵守单一责任原则(Single Responsibility Principle)。类最多只能有一个改变的原因。这不是严格按照DBDB的情况,但也多种拓展途径与只需要局部的变化。Refactoring as I added features was a pleasure! 493 | 494 | ## 总结 495 | 496 | DBDB 是一个实现了简单功能的数据库,但是它也可以变的很复杂。我为了管理它的复杂性,做的最重要的事情就是用不可变数据结构实现一个表面上可变的对象(implement an ostensibly mutable object with an immutable data structure.)。 我鼓励你以后遇到某些棘手问题(非常多的边际情况)的时候,考虑使用这种方法。 497 | 498 | ------------------------------------------------------------------------ 499 | 500 | 1. 额外的功能:您能保证压实的树结构是平衡的吗? 这有助于保持性能。 501 | 502 | 2. 在文件描述符(file descriptor)上调用`fsync`请求会让操作系统和硬盘驱动器(或SSD)立即写入所有缓冲的数据。操作系统和驱动器通常为保证性能,不会立即写入所有内容。 503 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 本作品采用知识共享 署名-非商业性使用-相同方式共享 3.0 中国大陆 许可协议进行许可。要查看该许可协议,可访问 http://creativecommons.org/licenses/by-nc-sa/3.0/cn/ 或者写信到 Creative Commons, PO Box 1866, Mountain View, CA 94042, USA。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 500LineorLess_CN 2 | 500 line or less 中文翻译计划。 3 | 4 | [原文链接](http://aosabook.org/en/500L/) 5 | 6 | [英文项目地址](https://github.com/aosabook/500lines/blob/master/README.md) 7 | 8 | **在翻译过程,发现部分文章有译者已经完成,该部分将不再翻译,并在下列表格中给出链接。** 9 | 10 | ## 对应表 11 | 12 | |英文标题|作者|中文标题| 13 | |:------|:------|:------| 14 | |[Blockcode: A visual programming toolkit](http://aosabook.org/en/500L/pages/blockcode-a-visual-programming-toolkit.html)|Dethe Elza|[Blockcode:可视化编程工具](http://blog.csdn.net/code_for_fun/article/details/51898028)| 15 | |[A Continuous Integration System](http://aosabook.org/en/500L/pages/a-continuous-integration-system.html)|Malini Das|[持续集成系统](https://github.com/HT524/500LineorLess_CN/blob/master/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E7%B3%BB%E7%BB%9F%20A%20Continuous%20Integration%20System/%E6%8C%81%E7%BB%AD%E9%9B%86%E6%88%90%E7%B3%BB%E7%BB%9F.md)| 16 | |[Clustering by Consensus](http://aosabook.org/en/500L/pages/clustering-by-consensus.html)|Dustin J. Mitchell|[分布式系统](https://github.com/HT524/500LineorLess_CN/blob/master/Clustering%20by%20Consensus/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F.md)| 17 | |[A Web Crawler With asyncio Coroutines](http://aosabook.org/en/500L/pages/a-web-crawler-with-asyncio-coroutines.html)|A. Jesse Jiryu Davis and Guido van Rossum|[高效爬虫与协程](https://linux.cn/article-8265-1.html)| 18 | |[Dagoba: an in-memory graph database](http://aosabook.org/en/500L/pages/dagoba-an-in-memory-graph-database.html)|Dann Toliver|Dagoba:内存中的图形数据库| 19 | |[DBDB: Dog Bed Database](http://aosabook.org/en/500L/pages/dbdb-dog-bed-database.html)|Taavi Burns|[DBDB:非关系型数据库](https://github.com/HT524/500LineorLess_CN/blob/master/DBDB_Dog%20Bed%20Database/DBDB_%E9%9D%9E%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93.md)| 20 | |[A Flow Shop Scheduler](http://aosabook.org/en/500L/pages/a-flow-shop-scheduler.html)|Dr. Christian Muise|Flow Shop 调度问题| 21 | |[An Archaeology-Inspired Database](http://aosabook.org/en/500L/pages/an-archaeology-inspired-database.html)|Yoav Rubin|Python 实现数据库| 22 | |[ A Python Interpreter Written in Python](http://aosabook.org/en/500L/pages/a-python-interpreter-written-in-python.html)|Allison Kaptur|[Python 解析器](https://linux.cn/article-7753-1.html)| 23 | |[A 3D Modeller](http://aosabook.org/en/500L/pages/a-3d-modeller.html)|Erick Dransch|3D建模| 24 | |[ A Simple Object Model](http://aosabook.org/en/500L/pages/a-simple-object-model.html)|Carl Friedrich Bolz|[简易对象模型](https://manjusaka.itscoder.com/posts/2016/12/15/A-Simple-Object-Model/)| 25 | |[Optical Character Recognition (OCR)](http://aosabook.org/en/500L/pages/optical-character-recognition-ocr.html)|Marina Samuel|[光学文字识别](https://github.com/HT524/500LineorLess_CN/blob/master/%E5%85%89%E5%AD%A6%E6%96%87%E5%AD%97%E8%AF%86%E5%88%AB%20Optical%20Character%20Recognition%20(OCR)%2F%E5%85%89%E5%AD%A6%E6%96%87%E5%AD%97%E8%AF%86%E5%88%AB.md)| 26 | |[A Pedometer in the Real World](http://aosabook.org/en/500L/pages/a-pedometer-in-the-real-world.html)|Dessy Daskalov|[计步器与Python图解](https://zhuanlan.zhihu.com/p/167900043)| 27 | |[ A Rejection Sampler](http://aosabook.org/en/500L/pages/a-rejection-sampler.html)|Jessica B. Hamrick|[决策取样器](https://github.com/HT524/500LineorLess_CN/blob/master/%E5%86%B3%E7%AD%96%E9%87%87%E6%A0%B7%E5%99%A8_A_Rejection_Sampler/%E5%86%B3%E7%AD%96%E9%87%87%E6%A0%B7%E5%99%A8_A_Rejection_Sampler.md)| 28 | |[ Web Spreadsheet](http://aosabook.org/en/500L/pages/web-spreadsheet.html)|Audrey Tang|Web 电子表格[(繁体中文版)](https://github.com/aosabook/500lines/blob/master/spreadsheet/spreadsheet.zh-tw.markdown)| 29 | |[Static Analysis](http://aosabook.org/en/500L/pages/static-analysis.html)|Leah Hanson|静态检查| 30 | |[A Template Engine](http://aosabook.org/en/500L/pages/a-template-engine.html)|Ned Batchelder|[模板引擎](http://www.jianshu.com/p/b5d4aa45e771)| 31 | |[ A Simple Web Server](http://aosabook.org/en/500L/pages/a-simple-web-server.html)|Greg Wilson|[简易Web服务器](https://github.com/HT524/500LineorLess_CN/blob/master/%E7%AE%80%E6%98%93web%E6%9C%8D%E5%8A%A1%E5%99%A8%20A%20simple%20web%20server/%E7%AE%80%E6%98%93web%E6%9C%8D%E5%8A%A1%E5%99%A8.md)| 32 | 33 | ## 术语库 34 | 35 | [术语库](https://github.com/HT524/500LineorLess_CN/tree/master/%E6%9C%AF%E8%AF%AD%E5%BA%93) 36 | 37 | > 术语库 是从[電腦名詞譯名](http://www.iicm.org.tw/term/)(`iicm_1.csv` ~ `iicm_6.csv`)和[English to Chinese translation glossaries](https://www.proz.com/glossary-translations/english-to-chinese-glossaries)抓取的。 38 | 39 | 可以直接导入[Google Translate Toolkit](https://translate.google.com/toolkit/) 40 | 41 | 抓取脚本在[placehoder](). 42 | 43 | ## 认领表 44 | 45 | |标题|译者|进度| 46 | |:------|:------|:------| 47 | |Blockcode: A visual programming toolkit|[taozi_cao](http://my.csdn.net/cwt8805)|已完成| 48 | |A Continuous Integration System|[Alovez](https://github.com/Alovez)|已完成| 49 | |Clustering by Consensus|[escapecat](https://github.com/escapecat)|翻译中| 50 | |A Web Crawler With asyncio Coroutines|[harold](https://github.com/haroldrandom) , [skhe](https://github.com/skhe)|翻译中| 51 | |Dagoba: an in-memory graph database|[yanwang10](https://github.com/yanwang10)|翻译中| 52 | |DBDB: Dog Bed Database|[JinXJinX](https://github.com/JinXJinX)|已完成| 53 | |A Flow Shop Scheduler|待认领|| 54 | |An Archaeology-Inspired Database|待认领|| 55 | |A Python Interpreter Written in Python|[qingyunha](https://github.com/qingyunha)|已完成| 56 | |A 3D Modeller|[Kalung Tsang](https://github.com/TsangKalung)|翻译中| 57 | |A Simple Object Model|[Manjusaka](http://manjusaka.itscoder.com/)|已完成| 58 | |Optical Character Recognition (OCR)|[Alovez](https://github.com/Alovez)|已完成| 59 | |A Pedometer in the Real World|[Austin](https://www.zhihu.com/people/link-hs)|已完成| 60 | |A Rejection Sampler|[Hideki_Nakamoto](https://github.com/inamoto85)|已完成| 61 | |Web Spreadsheet|待认领|| 62 | |Static Analysis|待认领|| 63 | |A Template Engine|[treelake](http://www.jianshu.com/users/66f24f2c0f36/latest_articles)|已完成| 64 | |A Simple Web Server|[skhe](https://github.com/skhe)|已完成| 65 | -------------------------------------------------------------------------------- /static-analysis/code/TypeCheck/.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - clang 4 | notifications: 5 | email: leah.a.hanson@gmail.com 6 | env: 7 | matrix: 8 | - JULIAVERSION="julianightlies" 9 | before_install: 10 | - sudo add-apt-repository ppa:staticfloat/julia-deps -y 11 | - sudo add-apt-repository ppa:staticfloat/${JULIAVERSION} -y 12 | - sudo apt-get update -qq -y 13 | - sudo apt-get install libpcre3-dev julia -y 14 | script: 15 | - julia -e 'Pkg.init();Pkg.add("FactCheck");Pkg.checkout("FactCheck")' 16 | - julia -e 'run(`ln -s $(pwd()) $(Pkg.dir("TypeCheck"))`); Pkg.pin("TypeCheck"); Pkg.resolve()' 17 | - julia -e 'using TypeCheck; @assert isdefined(:TypeCheck); @assert typeof(TypeCheck) === Module' 18 | - julia test/test.jl 19 | -------------------------------------------------------------------------------- /static-analysis/code/TypeCheck/README.md: -------------------------------------------------------------------------------- 1 | TypeCheck.jl 2 | ============ 3 | [![Build Status](https://travis-ci.org/astrieanna/TypeCheck.jl.png?branch=master)](https://travis-ci.org/astrieanna/TypeCheck.jl) 4 | 5 | Type-based static analysis for the Julia programming language. 6 | 7 | There are three main checks you can run: `checkreturntypes`, `checklooptypes`, and `checkmethodcalls`. 8 | Running a check on a function checks each method; running a check on a module checks each function (by checking each method of each function). 9 | 10 | To use any of these functions, you'll need to `Pkg.add("TypeCheck")` once to install the package on your computer and then import it using `using TypeCheck`. You'll need to re-import every time you restart the REPL. 11 | 12 | ### `checkreturntypes`: do the return types of your functions depend on the types, not the values of your arguments? 13 | 14 | It is considered good style in Julia to have the return type of functions depend only on their argument types, not on the argument values. 15 | This function tries to check that you did so. 16 | 17 | You can run this on a generic function or on a module: 18 | * `checkreturntypes(istext)` 19 | * `checkreturntypes(Base)` 20 | 21 | It is only effective at catching functions with annotated argument types. 22 | 23 | It will catch things like: 24 | ~~~ 25 | julia> foo1(x::Int) = isprime(x) ? x: false 26 | foo1 (generic function with 1 method) 27 | 28 | julia> checkreturntypes(foo1) 29 | foo1(Int64)::Union(Bool,Int64) 30 | ~~~ 31 | 32 | However, it will not catch: 33 | ~~~ 34 | julia> foo2(x) = isprime(x) ? x : false 35 | foo2 (generic function with 1 method) 36 | 37 | julia> checkreturntypes(foo2) 38 | 39 | ~~~ 40 | 41 | Additionally, it does a check to see if the return type of the function depends on a function call in the return statement. 42 | This prevents the analysis from complaining about every function that calls a "bad" function. 43 | However, it's possible that this silences too many alerts. 44 | 45 | ### `checklooptypes`: do the variables in your loops have stable types? 46 | 47 | A common performance problem is having unstable (numeric) variable types in an important loop. 48 | Having stable types within loops allows Julia's JIT compiler to output code as fast as a static compiler; 49 | having unstable types means resorting to slower, dynamic code. 50 | 51 | You can run this on a generic function or on a module: 52 | * `checklooptypes(sort)` 53 | * `checklooptypes(Base)` 54 | 55 | It will complain about: 56 | ~~~ 57 | julia> function barr1() 58 | x=4 59 | for i in 1:10 60 | x *= 2.5 61 | end 62 | x 63 | end 64 | barr1 (generic function with 1 method) 65 | 66 | julia> checklooptypes(barr1) 67 | barr1()::Union(Float64,Int64) 68 | x::Union(Float64,Int64) 69 | ~~~ 70 | 71 | It will correctly not complain about: 72 | ~~~ 73 | julia> function barr2() 74 | x = 4 75 | x = 2.5 76 | for i=1:10 77 | x *= 2.5 78 | end 79 | end 80 | barr2 (generic function with 1 method) 81 | 82 | julia> checklooptypes(barr2) 83 | 84 | ~~~ 85 | and 86 | ~~~ 87 | julia> function barr3() 88 | x::Int = 4 89 | for i=1:10 90 | x *= 2.5 91 | end 92 | end 93 | barr3 (generic function with 1 method) 94 | 95 | julia> checklooptypes(barr3) 96 | 97 | ~~~ 98 | (`barr3()` will throw an error rather than actually making `x` a `Float64`.) 99 | 100 | 101 | It is possible that it misses loose types in some cases, but I am not currently aware of them. Please let me know if you find one. 102 | 103 | ### `checkmethodcalls`: could your functions have run-time NoMethodErrors? 104 | 105 | `NoMethodError`s are probably the most common error in Julia. This is an attempt to find them statically. 106 | 107 | You can run this on a generic function or on a module: 108 | * `checkmethodcalls(sort)` 109 | * `checkmethodcalls(Base)` 110 | 111 | This functionality is still clearly imperfect. I'm working on refining it to be more useful. 112 | 113 | ### More Helper Functions 114 | This package also defined `code_typed(f::Function)` to get the Expr for each method of a function 115 | and `whos(f::Function)` to get a listing of the names and types of all the variables in the function. 116 | 117 | `whos`'s output is modeled on the output of the existing methods in Base: 118 | ~~~ 119 | julia> function xyz(x::Int,y) 120 | p = pi 121 | z = x + y * pi 122 | end 123 | xyz (generic function with 1 method) 124 | 125 | julia> whos(xyz) 126 | (Int64,Any)::Any 127 | #s38 Any 128 | p MathConst{:π} 129 | x Int64 130 | y Any 131 | z Any 132 | ~~~ 133 | 134 | ####`methodswithdescendants(t::DataType;onlyleaves::Bool=false,lim::Int=10)` 135 | 136 | This method goes through the descendents of a given type and finds what methods are implemented for them. It returns a list of (Symbol,Float64) tuples, where the Symbol is the name of a function and the Float64 is the percentage of subtypes whose `methodswith` shows a result for that function. 137 | 138 | Here's an example of calling it: 139 | ~~~julia 140 | julia> using TypeCheck 141 | 142 | julia> methodswithdescendants(Real) 143 | 10-element Array{(Symbol,Float64),1}: 144 | (:<,0.9166666666666666) 145 | (:convert,0.9166666666666666) 146 | (:<=,0.9166666666666666) 147 | (:+,0.75) 148 | (:-,0.7083333333333334) 149 | (:*,0.6666666666666666) 150 | (:~,0.5833333333333334) 151 | (:|,0.5833333333333334) 152 | (:&,0.5833333333333334) 153 | (:$,0.5833333333333334) 154 | 155 | julia> methodswithdescendants(Real;onlyleaves=true) 156 | 10-element Array{(Symbol,Float64),1}: 157 | (:<,1.0) 158 | (:convert,1.0) 159 | (:<=,1.0) 160 | (:~,0.7647058823529411) 161 | (:bswap,0.7647058823529411) 162 | (:|,0.7647058823529411) 163 | (:&,0.7647058823529411) 164 | (:$,0.7647058823529411) 165 | (:>>,0.7058823529411765) 166 | (:>>>,0.7058823529411765) 167 | 168 | julia> methodswithdescendants(Real;onlyleaves=true,lim=20) 169 | 20-element Array{(Symbol,Float64),1}: 170 | (:<,1.0) 171 | (:convert,1.0) 172 | (:<=,1.0) 173 | (:~,0.7647058823529411) 174 | (:bswap,0.7647058823529411) 175 | (:|,0.7647058823529411) 176 | (:&,0.7647058823529411) 177 | (:$,0.7647058823529411) 178 | (:>>,0.7058823529411765) 179 | (:>>>,0.7058823529411765) 180 | (:<<,0.7058823529411765) 181 | (:*,0.6470588235294118) 182 | (:count_ones,0.6470588235294118) 183 | (:-,0.6470588235294118) 184 | (:+,0.6470588235294118) 185 | (:trailing_zeros,0.6470588235294118) 186 | (:leading_zeros,0.5882352941176471) 187 | (:signbit,0.5882352941176471) 188 | (:^,0.4117647058823529) 189 | (:rem,0.4117647058823529) 190 | ~~~ 191 | 192 | 193 | 194 | ### Other Ways to Run Checks 195 | If you want to run these only on a single method, you can get the `Expr` for the method from `code_typed` and then pass that into the check you would like to run. 196 | 197 | -------------------------------------------------------------------------------- /static-analysis/code/TypeCheck/REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.3- 2 | -------------------------------------------------------------------------------- /static-analysis/code/TypeCheck/src/TypeCheck.jl: -------------------------------------------------------------------------------- 1 | # These are some functions to allow static type-checking of Julia programs 2 | 3 | module TypeCheck 4 | export checkreturntypes, checklooptypes, checkmethodcalls, 5 | methodswithdescendants 6 | 7 | ## Modifying functions from Base 8 | 9 | # return the type-inferred AST for each method of a generic function 10 | function Base.code_typed(f::Function) 11 | Expr[code_typed(m) for m in f.env] 12 | end 13 | 14 | # return the type-inferred AST for one method of a generic function 15 | function Base.code_typed(m::Method) 16 | linfo = m.func.code 17 | (tree,ty) = Base.typeinf(linfo,m.sig,()) 18 | if !isa(tree,Expr) 19 | ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, tree) 20 | else 21 | tree 22 | end 23 | end 24 | 25 | # specify a method via a method, function, or function+argument-type-tuple 26 | # prints out the local variables of that method, and their types 27 | function Base.whos(f,args...) 28 | for e in code_typed(f,args...) 29 | display(MethodSignature(e)) 30 | for x in sort(e.args[2][2];by=x->x[1]) 31 | println("\t",x[1],"\t",x[2]) 32 | end 33 | println("") 34 | end 35 | end 36 | 37 | ## Basic Operations on Function Exprs 38 | 39 | # given an Expr representing a method, return its inferred return type 40 | returntype(e::Expr) = e.args[3].typ 41 | 42 | # given an Expr representing a method, return an Array of Exprs representing its body 43 | body(e::Expr) = e.args[3].args 44 | 45 | # given an Expr representing a method, return all of the return statement in its body 46 | returns(e::Expr) = filter(x-> typeof(x) == Expr && x.head==:return,body(e)) 47 | 48 | # given an Expr representing a method, 49 | # return all function all Exprs contained in return statements in the method body 50 | function extractcallsfromreturns(e::Expr) 51 | rs = returns(e) 52 | rs_with_calls = filter(x->typeof(x.args[1]) == Expr && x.args[1].head == :call,rs) 53 | Expr[expr.args[1] for expr in rs_with_calls] 54 | end 55 | 56 | # A type that covers both Types and TypeVars 57 | AType = Union(Type,TypeVar) 58 | 59 | # given an Expr representing a method, get the type of each of the arguments in the signature 60 | function argumenttypes(e::Expr) 61 | argnames = Symbol[typeof(x) == Symbol ? x : x.args[1] for x in e.args[1]] 62 | argtuples = filter(x->x[1] in argnames, e.args[2][2]) #only arguments, no local vars 63 | AType[t[2] for t in argtuples] 64 | end 65 | 66 | # given an Expr, determine if it is calling a TopNode 67 | # (this affects how we should handle resolving the callee name) 68 | istop(e) = Base.is_expr(e,:call) && typeof(e.args[1]) == TopNode 69 | 70 | # given a call Expr (:call, :call1, :new), determine its return type 71 | function returntype(e::Expr,context::Expr) #must be :call,:new,:call1 72 | if Base.is_expr(e,:new); return e.typ; end 73 | if Base.is_expr(e,:call1) && isa(e.args[1], TopNode); return e.typ; end 74 | if !Base.is_expr(e,:call); error("Expected :call Expr"); end 75 | 76 | if istop(e) 77 | return e.typ 78 | end 79 | 80 | callee = e.args[1] 81 | if istop(callee) 82 | return returntype(callee,context) 83 | elseif isa(callee,SymbolNode) # only seen (func::F), so non-generic function 84 | return Any 85 | elseif is(callee,Symbol) 86 | if e.typ != Any || any([isa(x,LambdaStaticData) for x in e.args[2:end]]) 87 | return e.typ 88 | end 89 | 90 | if isdefined(Base,callee) 91 | f = eval(Base,callee) 92 | if !isa(f,Function) || !isgeneric(f) 93 | return e.typ 94 | end 95 | fargtypes = tuple([argumenttype(ea,context) for ea in e.args[2:end]]) 96 | return Union([returntype(ef) for ef in code_typed(f,fargtypes)]...) 97 | else 98 | return @show e.typ 99 | end 100 | end 101 | 102 | return e.typ 103 | end 104 | 105 | # for an Expr `e` used as an argument to a call in function Expr `context`, 106 | # determine the type of `e` 107 | function argumenttype(e::Expr,context::Expr) 108 | if Base.is_expr(e,:call) || Base.is_expr(e,:new) || Base.is_expr(e,:call1) 109 | return returntype(e,context) 110 | end 111 | 112 | @show e 113 | return Any 114 | end 115 | 116 | # for a Symbol `s` used as an argument to a call in a function Expr `e`, 117 | # determine the type of `s`. 118 | function argumenttype(s::Symbol,e::Expr) 119 | vartypes = [x[1] => x[2] for x in e.args[2][2]] 120 | s in vartypes ? (vartypes[@show s]) : Any 121 | end 122 | 123 | # as above, but for different call argument types 124 | argumenttype(s::SymbolNode,e::Expr) = s.typ 125 | argumenttype(t::TopNode,e::Expr) = Any 126 | argumenttype(l::LambdaStaticData,e::Expr) = Function 127 | argumenttype(q::QuoteNode,e::Expr) = argumenttype(q.value,e) 128 | 129 | # as above, but for various literal values 130 | argumenttype(n::Number,e::Expr) = typeof(n) 131 | argumenttype(c::Char,e::Expr) = typeof(c) 132 | argumenttype(s::String,e::Expr) = typeof(s) 133 | argumenttype(i,e::Expr) = typeof(i) #catch all, hopefully for more literals 134 | 135 | # start, next, and done are the functions for-loops use to iterate 136 | # having implementations of them makes a type iterable 137 | # this defines iterating over a DataType to mean iterating over its descendants 138 | # (breadth-first-search ordering) 139 | Base.start(t::DataType) = [t] 140 | function Base.next(t::DataType,arr::Vector{DataType}) 141 | c = pop!(arr) 142 | append!(arr,[x for x in subtypes(c)]) 143 | (c,arr) 144 | end 145 | Base.done(t::DataType,arr::Vector{DataType}) = length(arr) == 0 146 | 147 | # this is like the `methodswith` function from Base, 148 | # but it considers all descendants of the type 149 | # rather than the type itself (or the type + super types) 150 | # it produces a list of function names and statistics 151 | # about how many descendants implement that function 152 | # this is good for discovering implicit interfaces 153 | function methodswithdescendants(t::DataType;onlyleaves::Bool=false,lim::Int=10) 154 | d = Dict{Symbol,Int}() 155 | count = 0 156 | for s in t 157 | if !onlyleaves || (onlyleaves && isleaftype(s)) 158 | count += 1 159 | fs = Set{Symbol}() 160 | for m in methodswith(s) 161 | push!(fs,m.func.code.name) 162 | end 163 | for sym in fs 164 | d[sym] = get(d,sym,0) + 1 165 | end 166 | end 167 | end 168 | l = [(k,v/count) for (k,v) in d] 169 | sort!(l,by=(x->x[2]),rev=true) 170 | l[1:min(lim,end)] 171 | end 172 | 173 | # check all the generic functions in a module 174 | function checkallmodule(m::Module;test=checkreturntypes,kwargs...) 175 | score = 0 176 | for n in names(m) 177 | f = eval(m,n) 178 | if isgeneric(f) && typeof(f) == Function 179 | fm = test(f;mod=m,kwargs...) 180 | score += length(fm.methods) 181 | display(fm) 182 | end 183 | end 184 | println("The total number of failed methods in $m is $score") 185 | end 186 | 187 | # use checkallmodule to implement the Module version of other checks 188 | checkreturntypes(m::Module;kwargs...) = checkallmodule(m;test=checkreturntypes,kwargs...) 189 | checklooptypes(m::Module) = checkallmodule(m;test=checklooptypes) 190 | checkmethodcalls(m::Module) = checkallmodule(m;test=checkmethodcalls) 191 | 192 | ## Checking that return values are base only on input *types*, not values. 193 | 194 | type MethodSignature 195 | typs::Vector{AType} 196 | returntype::Union(Type,TypeVar) # v0.2 has TypeVars as returntypes; v0.3 does not 197 | end 198 | MethodSignature(e::Expr) = MethodSignature(argumenttypes(e),returntype(e)) 199 | function Base.writemime(io, ::MIME"text/plain", x::MethodSignature) 200 | println(io,"(",join([string(t) for t in x.typs],","),")::",x.returntype) 201 | end 202 | 203 | type FunctionSignature 204 | methods::Vector{MethodSignature} 205 | name::Symbol 206 | end 207 | 208 | function Base.writemime(io, ::MIME"text/plain", x::FunctionSignature) 209 | for m in x.methods 210 | print(io,string(x.name)) 211 | display(m) 212 | end 213 | end 214 | 215 | # given a function, run checkreturntypes on each method 216 | function checkreturntypes(f::Function;kwargs...) 217 | results = MethodSignature[] 218 | for e in code_typed(f) 219 | (ms,b) = checkreturntype(e;kwargs...) 220 | if b push!(results,ms) end 221 | end 222 | FunctionSignature(results,f.env.name) 223 | end 224 | 225 | # given an Expr representing a Method, 226 | # determine whether its return type is based 227 | # only on the arugment types or whether it is 228 | # also influenced by argument values 229 | # (the Method fails the check if the return type depends on values) 230 | function checkreturntype(e::Expr;kwargs...) 231 | (typ,b) = isreturnbasedonvalues(e;kwargs...) 232 | (MethodSignature(argumenttypes(e),typ),b) 233 | end 234 | 235 | # Determine whether this method's return type might change based on input values rather than input types 236 | function isreturnbasedonvalues(e::Expr;mod=Base) 237 | rt = returntype(e) 238 | ts = argumenttypes(e) 239 | if isleaftype(rt) || rt == None return (rt,false) end 240 | 241 | for t in ts 242 | if !isleaftype(t) 243 | return (rt,false) 244 | end 245 | end 246 | 247 | cs = [returntype(c,e) for c in extractcallsfromreturns(e)] 248 | for c in cs 249 | if rt == c 250 | return (rt,false) 251 | end 252 | end 253 | 254 | return (rt,true) # return is not concrete type; all args are concrete types 255 | end 256 | 257 | ## Checking that variables in loops have concrete types 258 | 259 | type LoopResult 260 | msig::MethodSignature 261 | lines::Vector{(Symbol,Type)} #TODO should this be a specialized type? SymbolNode? 262 | LoopResult(ms::MethodSignature,ls::Vector{(Symbol,Type)}) = new(ms,unique(ls)) 263 | end 264 | 265 | function Base.writemime(io, ::MIME"text/plain", x::LoopResult) 266 | display(x.msig) 267 | for (s,t) in x.lines 268 | println(io,"\t",string(s),"::",string(t)) 269 | end 270 | end 271 | 272 | type LoopResults 273 | name::Symbol 274 | methods::Vector{LoopResult} 275 | end 276 | 277 | function Base.writemime(io, ::MIME"text/plain", x::LoopResults) 278 | for lr in x.methods 279 | print(io,string(x.name)) 280 | display(lr) 281 | end 282 | end 283 | 284 | # for a given Function, run checklooptypes on each Method 285 | function checklooptypes(f::Function;kwargs...) 286 | lrs = LoopResult[] 287 | for e in code_typed(f) 288 | lr = checklooptypes(e) 289 | if length(lr.lines) > 0 push!(lrs,lr) end 290 | end 291 | LoopResults(f.env.name,lrs) 292 | end 293 | 294 | # for an Expr representing a Method, 295 | # check that the type of each variable used in a loop 296 | # has a concrete type 297 | checklooptypes(e::Expr;kwargs...) = LoopResult(MethodSignature(e),loosetypes(loopcontents(e))) 298 | 299 | # This is a function for trying to detect loops in the body of a Method 300 | # Returns lines that are inside one or more loops 301 | function loopcontents(e::Expr) 302 | b = body(e) 303 | loops = Int[] 304 | nesting = 0 305 | lines = {} 306 | for i in 1:length(b) 307 | if typeof(b[i]) == LabelNode 308 | l = b[i].label 309 | jumpback = findnext( 310 | x-> (typeof(x) == GotoNode && x.label == l) || (Base.is_expr(x,:gotoifnot) && x.args[end] == l), 311 | b, i) 312 | if jumpback != 0 313 | push!(loops,jumpback) 314 | nesting += 1 315 | end 316 | end 317 | if nesting > 0 318 | push!(lines,(i,b[i])) 319 | end 320 | 321 | if typeof(b[i]) == GotoNode && in(i,loops) 322 | splice!(loops,findfirst(loops,i)) 323 | nesting -= 1 324 | end 325 | end 326 | lines 327 | end 328 | 329 | # given `lr`, a Vector of expressions (Expr + literals, etc) 330 | # try to find all occurances of a variables in `lr` 331 | # and determine their types 332 | # `method` is only used as part of constructing the return type 333 | function loosetypes(lr::Vector) 334 | lines = (Symbol,Type)[] 335 | for (i,e) in lr 336 | if typeof(e) == Expr 337 | es = copy(e.args) 338 | while !isempty(es) 339 | e1 = pop!(es) 340 | if typeof(e1) == Expr 341 | append!(es,e1.args) 342 | elseif typeof(e1) == SymbolNode && !isleaftype(e1.typ) && typeof(e1.typ) == UnionType 343 | push!(lines,(e1.name,e1.typ)) 344 | end 345 | end 346 | end 347 | end 348 | return lines 349 | end 350 | 351 | ## Check method calls 352 | 353 | type CallSignature 354 | name::Symbol 355 | argumenttypes::Vector{AType} 356 | end 357 | function Base.writemime(io, ::MIME"text/plain", x::CallSignature) 358 | println(io,string(x.name),"(",join([string(t) for t in x.argumenttypes],","),")") 359 | end 360 | 361 | type MethodCalls 362 | m::MethodSignature 363 | calls::Vector{CallSignature} 364 | end 365 | 366 | function Base.writemime(io, ::MIME"text/plain", x::MethodCalls) 367 | display(x.m) 368 | for c in x.calls 369 | print(io,"\t") 370 | display(c) 371 | end 372 | end 373 | 374 | type FunctionCalls 375 | name::Symbol 376 | methods::Vector{MethodCalls} 377 | end 378 | 379 | function Base.writemime(io, ::MIME"text/plain", x::FunctionCalls) 380 | for mc in x.methods 381 | print(io,string(x.name)) 382 | display(mc) 383 | end 384 | end 385 | 386 | # given a Function, run `checkmethodcalls` on each Method 387 | # and collect the results into a FunctionCalls 388 | function checkmethodcalls(f::Function;kwargs...) 389 | calls = MethodCalls[] 390 | for m in f.env 391 | e = code_typed(m) 392 | mc = checkmethodcalls(e,m;kwargs...) 393 | if !isempty(mc.calls) 394 | push!(calls, mc) 395 | end 396 | end 397 | FunctionCalls(f.env.name,calls) 398 | end 399 | 400 | # given an Expr representing a Method, 401 | # and the Method it represents, 402 | # check the Method body for calls to non-existant Methods 403 | function checkmethodcalls(e::Expr,m::Method;kwargs...) 404 | if Base.arg_decl_parts(m)[3] == symbol("deprecated.jl") 405 | CallSignature[] 406 | end 407 | nomethoderrors(e,methodcalls(e);kwargs...) 408 | end 409 | 410 | # Find any methods that match the given CallSignature 411 | function hasmatches(mod::Module,cs::CallSignature) 412 | if isdefined(mod,cs.name) 413 | f = eval(mod,cs.name) 414 | if isgeneric(f) 415 | opts = methods(f,tuple(cs.argumenttypes...)) 416 | if isempty(opts) 417 | return false 418 | end 419 | end 420 | else 421 | #println("$mod.$(cs.name) is undefined") 422 | end 423 | return true 424 | end 425 | 426 | # Find any CallSignatures that indicate potential NoMethodErrors 427 | function nomethoderrors(e::Expr,cs::Vector{CallSignature};mod=Base) 428 | output = CallSignature[] 429 | for callsig in cs 430 | if !hasmatches(mod,callsig) 431 | push!(output,callsig) 432 | end 433 | end 434 | MethodCalls(MethodSignature(e),output) 435 | end 436 | 437 | # Look through the body of the function for `:call`s 438 | function methodcalls(e::Expr) 439 | b = body(e) 440 | lines = CallSignature[] 441 | for s in b 442 | if typeof(s) == Expr 443 | if s.head == :return 444 | append!(b, s.args) 445 | elseif s.head == :call 446 | if typeof(s.args[1]) == Symbol 447 | push!(lines,CallSignature(s.args[1], [argumenttype(e1,e) for e1 in s.args[2:end]])) 448 | end 449 | end 450 | end 451 | end 452 | lines 453 | end 454 | 455 | end #end module 456 | -------------------------------------------------------------------------------- /static-analysis/code/TypeCheck/test/test.jl: -------------------------------------------------------------------------------- 1 | module TestTypeCheck 2 | using TypeCheck, FactCheck 3 | 4 | istype(t) = isa(t,TypeCheck.AType) 5 | 6 | facts("Check Return Types: Make Sure It Runs on Base") do 7 | for n in names(Base) 8 | if isdefined(Base,n) 9 | f = eval(Base,n) 10 | if isgeneric(f) && typeof(f) == Function 11 | context(string(n)) do 12 | @fact TypeCheck.check_return_types(f) => anything # => FunctionSignature([],Symbol) 13 | [@fact istype(TypeCheck.returntype(e)) => true for e in code_typed(f)] 14 | [@fact TypeCheck.returntype(e) => istype for e in code_typed(f)] 15 | end 16 | end 17 | else 18 | @fact n => x->isdefined(Base,x) 19 | end 20 | end 21 | end 22 | 23 | caught(x) = x[2] == true 24 | notcaught(x) = x[2] == false 25 | function check_return(f,check) 26 | @fact length(code_typed(f)) => 1 27 | @fact TypeCheck.check_return_type(code_typed(f)[1]) => check 28 | end 29 | 30 | facts("Check Return Types: True Positives") do 31 | barr(x::Int) = isprime(x) ? x : false 32 | check_return(barr, caught) 33 | end 34 | 35 | facts("Check Return Types: False Negatives") do 36 | foo(x::Any) = isprime(x) ? x : false 37 | check_return(foo, notcaught) 38 | end 39 | 40 | 41 | facts("Check Loop Types: Make Sure It Runs on Base") do 42 | for n in names(Base) 43 | if isdefined(Base,n) 44 | f = eval(Base,n) 45 | if isgeneric(f) && typeof(f) == Function 46 | context(string(n)) do 47 | @fact TypeCheck.check_loop_types(f) => anything # => LoopResults 48 | end 49 | end 50 | else 51 | @fact n => x->isdefined(Base,x) 52 | end 53 | end 54 | end 55 | 56 | passed(x) = isempty(x.methods) 57 | failed(x) = !passed(x) 58 | function check_loops(f,check) 59 | @fact length(code_typed(f)) => 1 60 | @fact check_loop_types(f) => check 61 | end 62 | 63 | facts("Check Loop Types: True Positives") do 64 | function f1(x::Int) 65 | for n in 1:x 66 | x /= n 67 | end 68 | return x 69 | end 70 | check_loops(f1,failed) 71 | end 72 | 73 | facts("Check Loop Types: True Negatives") do 74 | function g1() 75 | x::Int = 5 76 | for i = 1:100 77 | x *= 2.5 78 | end 79 | return x 80 | end 81 | check_loops(g1,passed) 82 | function g2() 83 | x = 5 84 | x = 0.2 85 | for i = 1:10 86 | x *= 2 87 | end 88 | return x 89 | end 90 | check_loops(g2,passed) 91 | end 92 | 93 | 94 | facts("Check Method Calls: Make Sure It Runs on Base") do 95 | for n in names(Base) 96 | if isdefined(Base,n) 97 | f = eval(Base,n) 98 | if isgeneric(f) && typeof(f) == Function 99 | context(string(n)) do 100 | @fact TypeCheck.check_method_calls(f) => anything # => FunctionCalls 101 | end 102 | end 103 | else 104 | @fact n => x->isdefined(Base,x) 105 | end 106 | end 107 | end 108 | 109 | facts("Check Method Calls: True Positives") do 110 | end 111 | 112 | facts("Check Method Calls: False Negatives") do 113 | end 114 | 115 | exitstatus() 116 | end 117 | -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/code/neural_network_design.py: -------------------------------------------------------------------------------- 1 | """ 2 | In order to decide how many hidden nodes the hidden layer should have, 3 | split up the data set into training and testing data and create networks 4 | with various hidden node counts (5, 10, 15, ... 45), testing the performance 5 | for each. 6 | 7 | The best-performing node count is used in the actual system. If multiple counts 8 | perform similarly, choose the smallest count for a smaller network with fewer computations. 9 | """ 10 | 11 | import numpy as np 12 | from ocr import OCRNeuralNetwork 13 | from sklearn.cross_validation import train_test_split 14 | 15 | def test(data_matrix, data_labels, test_indices, nn): 16 | avg_sum = 0 17 | for j in xrange(100): 18 | correct_guess_count = 0 19 | for i in test_indices: 20 | test = data_matrix[i] 21 | prediction = nn.predict(test) 22 | if data_labels[i] == prediction: 23 | correct_guess_count += 1 24 | 25 | avg_sum += (correct_guess_count / float(len(test_indices))) 26 | return avg_sum / 100 27 | 28 | 29 | # Load data samples and labels into matrix 30 | data_matrix = np.loadtxt(open('data.csv', 'rb'), delimiter = ',').tolist() 31 | data_labels = np.loadtxt(open('dataLabels.csv', 'rb')).tolist() 32 | 33 | # Create training and testing sets. 34 | train_indices, test_indices = train_test_split(list(range(5000))) 35 | 36 | print "PERFORMANCE" 37 | print "-----------" 38 | 39 | # Try various number of hidden nodes and see what performs best 40 | for i in xrange(5, 50, 5): 41 | nn = OCRNeuralNetwork(i, data_matrix, data_labels, train_indices, False) 42 | performance = str(test(data_matrix, data_labels, test_indices, nn)) 43 | print "{i} Hidden Nodes: {val}".format(i=i, val=performance) -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/code/ocr.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |

OCR Demo

10 | 11 |
12 |

Digit:

13 | 14 | 15 | 16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/code/ocr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This module creates a 200x200 pixel canvas for a user to draw 3 | * digits. The digits can either be used to train the neural network 4 | * or to test the network's current prediction for that digit. 5 | * 6 | * To simplify computation, the 200x200px canvas is translated as a 20x20px 7 | * canvas to be processed as an input array of 1s (white) and 0s (black) on 8 | * on the server side. Each new translated pixel's size is 10x10px 9 | * 10 | * When training the network, traffic to the server can be reduced by batching 11 | * requests to train based on BATCH_SIZE. 12 | */ 13 | var ocrDemo = { 14 | CANVAS_WIDTH: 200, 15 | TRANSLATED_WIDTH: 20, 16 | PIXEL_WIDTH: 10, // TRANSLATED_WIDTH = CANVAS_WIDTH / PIXEL_WIDTH 17 | BATCH_SIZE: 1, 18 | 19 | // Server Variables 20 | PORT: "8000", 21 | HOST: "http://localhost", 22 | 23 | // Colors 24 | BLACK: "#000000", 25 | BLUE: "#0000ff", 26 | 27 | trainArray: [], 28 | trainingRequestCount: 0, 29 | 30 | onLoadFunction: function() { 31 | this.resetCanvas(); 32 | }, 33 | 34 | resetCanvas: function() { 35 | var canvas = document.getElementById('canvas'); 36 | var ctx = canvas.getContext('2d'); 37 | 38 | this.data = []; 39 | ctx.fillStyle = this.BLACK; 40 | ctx.fillRect(0, 0, this.CANVAS_WIDTH, this.CANVAS_WIDTH); 41 | var matrixSize = 400; 42 | while (matrixSize--) this.data.push(0); 43 | this.drawGrid(ctx); 44 | 45 | canvas.onmousemove = function(e) { this.onMouseMove(e, ctx, canvas) }.bind(this); 46 | canvas.onmousedown = function(e) { this.onMouseDown(e, ctx, canvas) }.bind(this); 47 | canvas.onmouseup = function(e) { this.onMouseUp(e, ctx) }.bind(this); 48 | }, 49 | 50 | drawGrid: function(ctx) { 51 | for (var x = this.PIXEL_WIDTH, y = this.PIXEL_WIDTH; x < this.CANVAS_WIDTH; x += this.PIXEL_WIDTH, y += this.PIXEL_WIDTH) { 52 | ctx.strokeStyle = this.BLUE; 53 | ctx.beginPath(); 54 | ctx.moveTo(x, 0); 55 | ctx.lineTo(x, this.CANVAS_WIDTH); 56 | ctx.stroke(); 57 | 58 | ctx.beginPath(); 59 | ctx.moveTo(0, y); 60 | ctx.lineTo(this.CANVAS_WIDTH, y); 61 | ctx.stroke(); 62 | } 63 | }, 64 | 65 | onMouseMove: function(e, ctx, canvas) { 66 | if (!canvas.isDrawing) { 67 | return; 68 | } 69 | this.fillSquare(ctx, e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); 70 | }, 71 | 72 | onMouseDown: function(e, ctx, canvas) { 73 | canvas.isDrawing = true; 74 | this.fillSquare(ctx, e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); 75 | }, 76 | 77 | onMouseUp: function(e) { 78 | canvas.isDrawing = false; 79 | }, 80 | 81 | fillSquare: function(ctx, x, y) { 82 | var xPixel = Math.floor(x / this.PIXEL_WIDTH); 83 | var yPixel = Math.floor(y / this.PIXEL_WIDTH); 84 | this.data[((xPixel - 1) * this.TRANSLATED_WIDTH + yPixel) - 1] = 1; 85 | 86 | ctx.fillStyle = '#ffffff'; 87 | ctx.fillRect(xPixel * this.PIXEL_WIDTH, yPixel * this.PIXEL_WIDTH, this.PIXEL_WIDTH, this.PIXEL_WIDTH); 88 | }, 89 | 90 | train: function() { 91 | var digitVal = document.getElementById("digit").value; 92 | if (!digitVal || this.data.indexOf(1) < 0) { 93 | alert("Please type and draw a digit value in order to train the network"); 94 | return; 95 | } 96 | this.trainArray.push({"y0": this.data, "label": parseInt(digitVal)}); 97 | this.trainingRequestCount++; 98 | 99 | // Time to send a training batch to the server. 100 | if (this.trainingRequestCount == this.BATCH_SIZE) { 101 | alert("Sending training data to server..."); 102 | var json = { 103 | trainArray: this.trainArray, 104 | train: true 105 | }; 106 | 107 | this.sendData(json); 108 | this.trainingRequestCount = 0; 109 | this.trainArray = []; 110 | } 111 | }, 112 | 113 | test: function() { 114 | if (this.data.indexOf(1) < 0) { 115 | alert("Please draw a digit in order to test the network"); 116 | return; 117 | } 118 | var json = { 119 | image: this.data, 120 | predict: true 121 | }; 122 | this.sendData(json); 123 | }, 124 | 125 | receiveResponse: function(xmlHttp) { 126 | if (xmlHttp.status != 200) { 127 | alert("Server returned status " + xmlHttp.status); 128 | return; 129 | } 130 | var responseJSON = JSON.parse(xmlHttp.responseText); 131 | if (xmlHttp.responseText && responseJSON.type == "test") { 132 | alert("The neural network predicts you wrote a \'" + responseJSON.result + '\''); 133 | } 134 | }, 135 | 136 | onError: function(e) { 137 | alert("Error occurred while connecting to server: " + e.target.statusText); 138 | }, 139 | 140 | sendData: function(json) { 141 | var xmlHttp = new XMLHttpRequest(); 142 | xmlHttp.open('POST', this.HOST + ":" + this.PORT, false); 143 | xmlHttp.onload = function() { this.receiveResponse(xmlHttp); }.bind(this); 144 | xmlHttp.onerror = function() { this.onError(xmlHttp) }.bind(this); 145 | var msg = JSON.stringify(json); 146 | xmlHttp.setRequestHeader('Content-length', msg.length); 147 | xmlHttp.setRequestHeader("Connection", "close"); 148 | xmlHttp.send(msg); 149 | } 150 | } -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/code/ocr.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import matplotlib.pyplot as plt 3 | import matplotlib.cm as cm 4 | import numpy as np 5 | from numpy import matrix 6 | from math import pow 7 | from collections import namedtuple 8 | import math 9 | import random 10 | import os 11 | import json 12 | 13 | """ 14 | This class does some initial training of a neural network for predicting drawn 15 | digits based on a data set in data_matrix and data_labels. It can then be used to 16 | train the network further by calling train() with any array of data or to predict 17 | what a drawn digit is by calling predict(). 18 | 19 | The weights that define the neural network can be saved to a file, NN_FILE_PATH, 20 | to be reloaded upon initilization. 21 | """ 22 | class OCRNeuralNetwork: 23 | LEARNING_RATE = 0.1 24 | WIDTH_IN_PIXELS = 20 25 | NN_FILE_PATH = 'nn.json' 26 | 27 | def __init__(self, num_hidden_nodes, data_matrix, data_labels, training_indices, use_file=True): 28 | self.sigmoid = np.vectorize(self._sigmoid_scalar) 29 | self.sigmoid_prime = np.vectorize(self._sigmoid_prime_scalar) 30 | self._use_file = use_file 31 | self.data_matrix = data_matrix 32 | self.data_labels = data_labels 33 | 34 | if (not os.path.isfile(OCRNeuralNetwork.NN_FILE_PATH) or not use_file): 35 | # Step 1: Initialize weights to small numbers 36 | self.theta1 = self._rand_initialize_weights(400, num_hidden_nodes) 37 | self.theta2 = self._rand_initialize_weights(num_hidden_nodes, 10) 38 | self.input_layer_bias = self._rand_initialize_weights(1, num_hidden_nodes) 39 | self.hidden_layer_bias = self._rand_initialize_weights(1, 10) 40 | 41 | # Train using sample data 42 | TrainData = namedtuple('TrainData', ['y0', 'label']) 43 | self.train([TrainData(self.data_matrix[i], int(self.data_labels[i])) for i in training_indices]) 44 | self.save() 45 | else: 46 | self._load() 47 | 48 | def _rand_initialize_weights(self, size_in, size_out): 49 | return [((x * 0.12) - 0.06) for x in np.random.rand(size_out, size_in)] 50 | 51 | # The sigmoid activation function. Operates on scalars. 52 | def _sigmoid_scalar(self, z): 53 | return 1 / (1 + math.e ** -z) 54 | 55 | def _sigmoid_prime_scalar(self, z): 56 | return self.sigmoid(z) * (1 - self.sigmoid(z)) 57 | 58 | def _draw(self, sample): 59 | pixelArray = [sample[j:j+self.WIDTH_IN_PIXELS] for j in xrange(0, len(sample), self.WIDTH_IN_PIXELS)] 60 | plt.imshow(zip(*pixelArray), cmap = cm.Greys_r, interpolation="nearest") 61 | plt.show() 62 | 63 | def train(self, training_data_array): 64 | for data in training_data_array: 65 | # Step 2: Forward propagation 66 | y1 = np.dot(np.mat(self.theta1), np.mat(data['y0']).T) 67 | sum1 = y1 + np.mat(self.input_layer_bias) # Add the bias 68 | y1 = self.sigmoid(sum1) 69 | 70 | y2 = np.dot(np.array(self.theta2), y1) 71 | y2 = np.add(y2, self.hidden_layer_bias) # Add the bias 72 | y2 = self.sigmoid(y2) 73 | 74 | # Step 3: Back propagation 75 | actual_vals = [0] * 10 # actual_vals is a python list for easy initialization and is later turned into an np matrix (2 lines down). 76 | actual_vals[data['label']] = 1 77 | output_errors = np.mat(actual_vals).T - np.mat(y2) 78 | hiddenErrors = np.multiply(np.dot(np.mat(self.theta2).T, output_errors), self.sigmoid_prime(sum1)) 79 | 80 | # Step 4: Update weights 81 | self.theta1 += self.LEARNING_RATE * np.dot(np.mat(hiddenErrors), np.mat(data['y0'])) 82 | self.theta2 += self.LEARNING_RATE * np.dot(np.mat(output_errors), np.mat(y1).T) 83 | self.hidden_layer_bias += self.LEARNING_RATE * output_errors 84 | self.input_layer_bias += self.LEARNING_RATE * hiddenErrors 85 | 86 | def predict(self, test): 87 | y1 = np.dot(np.mat(self.theta1), np.mat(test).T) 88 | y1 = y1 + np.mat(self.input_layer_bias) # Add the bias 89 | y1 = self.sigmoid(y1) 90 | 91 | y2 = np.dot(np.array(self.theta2), y1) 92 | y2 = np.add(y2, self.hidden_layer_bias) # Add the bias 93 | y2 = self.sigmoid(y2) 94 | 95 | results = y2.T.tolist()[0] 96 | return results.index(max(results)) 97 | 98 | def save(self): 99 | if not self._use_file: 100 | return 101 | 102 | json_neural_network = { 103 | "theta1":[np_mat.tolist()[0] for np_mat in self.theta1], 104 | "theta2":[np_mat.tolist()[0] for np_mat in self.theta2], 105 | "b1":self.input_layer_bias[0].tolist()[0], 106 | "b2":self.hidden_layer_bias[0].tolist()[0] 107 | }; 108 | with open(OCRNeuralNetwork.NN_FILE_PATH,'w') as nnFile: 109 | json.dump(json_neural_network, nnFile) 110 | 111 | def _load(self): 112 | if not self._use_file: 113 | return 114 | 115 | with open(OCRNeuralNetwork.NN_FILE_PATH) as nnFile: 116 | nn = json.load(nnFile) 117 | self.theta1 = [np.array(li) for li in nn['theta1']] 118 | self.theta2 = [np.array(li) for li in nn['theta2']] 119 | self.input_layer_bias = [np.array(nn['b1'][0])] 120 | self.hidden_layer_bias = [np.array(nn['b2'][0])] 121 | -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/code/server.py: -------------------------------------------------------------------------------- 1 | import BaseHTTPServer 2 | import json 3 | from ocr import OCRNeuralNetwork 4 | import numpy as np 5 | 6 | HOST_NAME = 'localhost' 7 | PORT_NUMBER = 8000 8 | HIDDEN_NODE_COUNT = 15 9 | 10 | # Load data samples and labels into matrix 11 | data_matrix = np.loadtxt(open('data.csv', 'rb'), delimiter = ',') 12 | data_labels = np.loadtxt(open('dataLabels.csv', 'rb')) 13 | 14 | # Convert from numpy ndarrays to python lists 15 | data_matrix = data_matrix.tolist() 16 | data_labels = data_labels.tolist() 17 | 18 | # If a neural network file does not exist, train it using all 5000 existing data samples. 19 | # Based on data collected from neural_network_design.py, 15 is the optimal number 20 | # for hidden nodes 21 | nn = OCRNeuralNetwork(HIDDEN_NODE_COUNT, data_matrix, data_labels, list(range(5000))); 22 | 23 | class JSONHandler(BaseHTTPServer.BaseHTTPRequestHandler): 24 | def do_POST(s): 25 | response_code = 200 26 | response = "" 27 | varLen = int(s.headers.get('Content-Length')) 28 | content = s.rfile.read(varLen); 29 | payload = json.loads(content); 30 | 31 | if payload.get('train'): 32 | nn.train(payload['trainArray']) 33 | nn.save() 34 | elif payload.get('predict'): 35 | try: 36 | response = {"type":"test", "result":nn.predict(str(payload['image']))} 37 | except: 38 | response_code = 500 39 | else: 40 | response_code = 400 41 | 42 | s.send_response(response_code) 43 | s.send_header("Content-type", "application/json") 44 | s.send_header("Access-Control-Allow-Origin", "*") 45 | s.end_headers() 46 | if response: 47 | s.wfile.write(json.dumps(response)) 48 | return 49 | 50 | if __name__ == '__main__': 51 | server_class = BaseHTTPServer.HTTPServer; 52 | httpd = server_class((HOST_NAME, PORT_NUMBER), JSONHandler) 53 | 54 | try: 55 | httpd.serve_forever() 56 | except KeyboardInterrupt: 57 | pass 58 | else: 59 | print "Unexpected server exception occurred." 60 | finally: 61 | httpd.server_close() 62 | -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/img/Figure_13.1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/光学文字识别 Optical Character Recognition (OCR)/img/Figure_13.1.jpg -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/img/WijL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/光学文字识别 Optical Character Recognition (OCR)/img/WijL.png -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/img/a22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/光学文字识别 Optical Character Recognition (OCR)/img/a22.png -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/img/a22f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/光学文字识别 Optical Character Recognition (OCR)/img/a22f.png -------------------------------------------------------------------------------- /光学文字识别 Optical Character Recognition (OCR)/光学文字识别.md: -------------------------------------------------------------------------------- 1 | # Optical Character Recognition (OCR) 2 | 3 | 4 | 这是[开源程序架构](http://aosabook.org/en/index.html)系列的第四本[《500 Lines or Less》](https://github.com/aosabook/500lines/blob/master/README.md)的早期章节。 5 | 如果你发现任何问题,可以在我们的[Github Issues](https://github.com/aosabook/500lines/issues)上反馈。 6 | 请关注[AOSA blog](http://aosabook.org/blog/)获取最新的章节及出版计划,还可以在[这个推特](https://twitter.com/aosabook)上获取关于本书的最新消息。 7 | 8 | ---- 9 | ## 简介 10 | 11 | 要是你的电脑能帮你洗盘子,洗衣服,做饭,收拾房间那会是怎样一番景象呢? 12 | 我敢肯定,大多数人会很喜欢这样的一个得力助手! 13 | 但是,计算机如何才能准确的像人一样,按照人完成这些工作的方式,完成这些任务呢? 14 | 15 | 著名的计算机科学家艾伦·图灵提出了图灵测试。 16 | 这是一种可以判断一台机器的智能程度是否已经与人类匹敌的测试。 17 | 图灵测试需要一个处在明处提问者,两个处在暗处的未知实体(一个是人,一个是机器)。 18 | 提问者需要提出问题并试图通过未知实体的回答来分辨出哪一个是人,哪一个是机器。 19 | 如果提问者是无法辨别机器和人类,那么说明被测试的机器已经具有了与人类相仿水平的智能。 20 | 21 | 虽然关于‘图灵测试’的有效性以及人们能否创造出这种水平的智能一直有着很多争议, 22 | 但是毋庸置疑的是现在已经存在了一些具有不俗智能的机器。 23 | 如今一些软件已经可以操控机器人完成简单的办公任务,或者帮助那些爱滋海默症(老年痴呆)的患者。 24 | 还有一些人工智能更加常见,像谷歌的搜索引擎以及Facebook的热点推荐系统等等。 25 | 26 | 一个比较普遍的人工智能的应用是光学字符识别(OCR)。 27 | OCR系统可以识别手写的文字,并且将其转换成机器可以处理的字符。 28 | 当你将一个手写支票导入银行设备中验证存款金额时,你有考虑过这些神奇的功能到底是怎么实现的吗? 29 | 本章将带你了解一个基于智能神经网络的(ANN)的数字识别OCR系统是如何运作的。 30 | 31 | 在开始一切工作之前,我们还要明确一些背景知识。 32 | 33 | ## 什么是人工智能 34 | 35 | 尽管图灵先生关于智能的定义看起来很合理,但是关于智能的构成,归根结底是一种哲学层面的思索。 36 | 计算机科学家们已经(怎么分的不重要)将一些系统和算法分类到人工智能的某些分支当中。 37 | 关于这些分类,我们举几个例子(当然还有[更多](http://www-formal.stanford.edu/jmc/whatisai/node2.html)): 38 | 39 | * 根据真实世界已知信息进行的概率性演绎推理。例如:模糊推论法可以帮助恒温器在感知到空气温热潮湿时做出打开空调的决定; 40 | * 启发式搜索。例如:在下棋时可以通过搜索所有可能的来选择出获益最高的走法; 41 | * 反馈的机器学习模型。例如:一些模式识别问题,像OCR。 42 | 43 | 总之,机器学习就是使用大量数据进行模式识别系统的训练。 44 | 用于训练的数据集可能有两种:一种是示踪的,是说根据输入限定系统的期望输出,也就是对于某种输入有人为的期望结果; 45 | 一种是非示踪的,也就是说对系统的期望输出没有限制。 46 | 使用非示踪数据集的算法被称作非监督式算法,使用示踪数据集的算法则被称作监督式算法。 47 | 可以用来实现OCR的机器学习算法核技术有很多,今天我们所用ANNs就是其中一种很简单的方法。 48 | 49 | ## 人工神经网络(ANNs) 50 | 51 | ### 什么是ANNs 52 | 人工神经网络(ANNs)是一种由很多相互连通且可以互相通讯的节点组成的网络结构。 53 | 这种结构和功能是受到了生物脑结构的启发。 54 | [赫布理论](http://www.nbb.cornell.edu/neurobio/linster/BioNB420/hebb.pdf)解释了生物体内的这些神经网络是如何通过改变物理层面的结构和连接强度来进行模式识别的学习的。 55 | 同样的,一个典型的ANN(如图13.1所示)的节点之间具有一些会根据网络的学习不断改变权值的连接(边)。 56 | 我们称节点标记+1为偏移。 57 | 最左边蓝色的一列节点是输入节点,中间一行包含很多隐藏节点,最右边的是输出节点。 58 | 中间包含很多隐藏节点的这一列,通常别叫作“隐藏层”。 59 | 60 | 61 | ![Figure 13.1](./img/Figure_13.1.jpg) 62 | 63 | 64 | 在图13.1中所有节点圆圈中的数字代表节点的输出。如果我们称第L层第n个节点的输出为n(L),第L层第i个节点与第L+1层第j个节点的链接称为![](./img/WijL.png)。那么,![](./img/a22.png)的输出为: 65 | 66 | ![function](./img/a22f.png) 67 | 68 | 其中,f()是激活函数,b是偏移量。激活函数决定着节点输出的类型。偏移量是一个用于提高神经网络精确性的加性节点。更多有关上述二者的细节可以参看[设计一个前馈神经网络](http://aosabook.org/en/500L/optical-character-recognition-ocr.html#sec.ocr.feedforward) 69 | 70 | 这类没有循环的神经网络我们称之为‘前馈’神经网络。节点的输出反馈回输入端的人工神经网络被则称为周期神经网络。有很多可以用于训练前馈神经网络的算法,*反向传播*就是其中比较常用的之一。在本章的OCR系统中就将使用反向传播的方式实现神经网络的。 71 | 72 | 73 | ### 怎么使用ANNs 74 | 75 | 和其他机器语言方法一样,使用反向传播的第一步就是将我们的问题化简。我们需要思考如何让我们的问题化简为一个可以用ANN解决的问题。具体一点,我们需要决定如何将我们的数据变换处理成能够输入到ANN之中的状态。对于这次要实现的OCR系统来说,我们可以将给定*数字的图片*中像素的位置作为输入。这种思路看起来非常直白简单,但是更多时候选择输入的格式并不会像这样简单。举个例子,如果我们想在一些比较大的图像中进行形状的识别,我们可能需要先对图像进行预处理来识别其中物体的大概轮廓,然后再将这个识别出的轮廓作为输入数据。 76 | 77 | 一旦我们决定了输入数据的格式,接下来要做些什么呢?就像我们在什么是人工神经网络小节中所讲的,因为反向传播是一种监督算法,所以他需要使用被标记的数据进行训练。因此,当我们使用像素的位置作为训练输入时,同时也要将其所代表的的数字也如输入其中。这意味着我们需要收集大量的包含数字图像及其所代表的数字的数据对。 78 | 79 | 下一步是将数据分成训练组及校验组。训练组的数据用于驱动反向传播算法设置ANN的节点权值。校验组的数据有两个作用,一是用于校验经过训练组数据训练后的ANN的输出结果,二是评估算法性能(准确性)。如果我们打算比较反向传播算法与其他类似算法在处理我们的数据时的性能,我们可以分出50%的数据作为训练组,25%用于比较两种算法的性能(校验组),剩余25%用于测试最终选取的算法的准确性(测试组)。如果我们不打算实验多种算法,那我们可以把25%用于比较算法性能的数据分到训练组中,剩余的25%作为校验组检测ANN最终被训练的如何。 80 | 81 | 评估ANN的准确性有着双重的目的。首先,评估ANN的准确性可以避免过拟合。所谓过拟合就是指,训练出来网络对训练组数据的识别准确性远高于对校验组的识别准确性。当出现过拟合状态时,说明我们选取的训练数据不够有代表性,比较片面。这时,我们需要重新提炼训练用的数据。另外,通过评估不同隐藏层及隐藏节点的ANN,可以帮助我们确定最佳的ANN规模。合适的ANN规模是指,在保证识别率的情况下使用最少的节点/连接,以此来减小计算开支,加快训练及识别的效率。一旦确定了合适的ANN规模并完成训练,我们的ANN就准备好进行识别预测了。 82 | 83 | ## 在一个简单的OCR系统中设计决策方案 84 | 刚刚我们了解了一下基础的前馈神经网络,以及如何使用它们。接下来我们将一起探索一下如何搭建一个OCR系统。 85 | 86 | 首先,我们必须明确我们的系统能够实现什么功能。简单来讲,当我的用户手写了一个数字作为输入时,我们的系统可以利用这个图片进行训练或者根据图片识别出其中数字。 87 | 虽然我们的OCR系统可以在单机运行,那但是创建一个客户端-服务器的框架会比较灵活。使用服务器我们可以利用网络社群的数据训练我们的神经网,同时也可以将复杂密集的运算交给性能更加强劲的服务器。 88 | 89 | 我们的OCR系统主要由5部分组成,分别写在5个文件之中。它们分别是: 90 | 91 | * 客户端(ocr.js) 92 | 93 | * 服务器(server.py) 94 | 95 | * 简单的用户界面(ocr.html) 96 | 97 | * 基于反向传播训练的ANN(ocr.py) 98 | 99 | * ANN的实现脚本(neural_network_design.py) 100 | 101 | 我们的用户界面非常简单:一个可以在其中绘制数字的画板,和一个可以切换训练还是识别的按钮。 102 | 客户端将会收集用户在用户界面绘制的数字图像,将其转换为一个数组,并将数组结合'训练/识别'指令传递给服务器。服务器简单的按照指令生成ANN模块的API请求。 103 | ANN模块在第一次初始化时,会利用已有的数据进行训练,并将训练后的权值存在一个文件中。在之后再次调用ANN模块时,ANN模块会自动读取权值文件来恢复之前的训练状态。 104 | 这个模块包含训练及识别的核心逻辑运算。最后,设计一个通过实验不同的隐藏节点数量寻求最佳性能的决策脚本。将上述的这些模块组合在一起,我们就可以实现一个简单但却强大的OCR系统了。 105 | 106 | 这样一来我们,我们的基本设计思路就很清晰了。接下来,让我们动起手来将思想转化为代码。 107 | 108 | ### 简单的界面(orc.html) 109 | 110 | 我们在前文中已经讲到,首先我们需要收集一些用于训练ANN的数据。我们可以上传一系列手写的数字的图片到服务器中,但是这样做非常的麻烦。 111 | 比较好的替代方案就是,直接让用户在HTML的画布(Canvas)元素中绘制数字。我们可以给用户一个用于选择'训练/识别'的按钮。如果选择了训练,用户还需要将绘制的图片所代表的数字一并提交。 112 | 下面是我们的HTML代码: 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 |
121 |

OCR Demo

122 | 123 |
124 |

Digit:

125 | 126 | 127 | 128 |
129 |
130 | 131 | 132 | 133 | ### OCR客户端(ocr.js) 134 | 135 | 网页中的一个像素非常的难看清,所以我们可以用一个10x10真实像素的方块代表我们识别中的一个像素单位。这样一个200x200的画布(Canvas)对我们的ANN来说就相当于一个20x20的画布(Canvas)了。 136 | 137 | var ocrDemo = { 138 | CANVAS_WIDTH: 200, 139 | TRANSLATED_WIDTH: 20, 140 | PIXEL_WIDTH: 10, // TRANSLATED_WIDTH = CANVAS_WIDTH / PIXEL_WIDTH 141 | 142 | 为了看上去更清楚,我们可以为我们的像素单位填充一些颜色。我们使用`drawGrid()`方法来生成蓝色的网格。 143 | 144 | drawGrid: function(ctx) { 145 | for (var x = this.PIXEL_WIDTH, y = this.PIXEL_WIDTH; 146 | x < this.CANVAS_WIDTH; x += this.PIXEL_WIDTH, 147 | y += this.PIXEL_WIDTH) { 148 | ctx.strokeStyle = this.BLUE; 149 | ctx.beginPath(); 150 | ctx.moveTo(x, 0); 151 | ctx.lineTo(x, this.CANVAS_WIDTH); 152 | ctx.stroke(); 153 | 154 | ctx.beginPath(); 155 | ctx.moveTo(0, y); 156 | ctx.lineTo(this.CANVAS_WIDTH, y); 157 | ctx.stroke(); 158 | } 159 | }, 160 | 161 | 我们需要将我们收集到的图像数据处理成能传递到服务器的格式。我们用0代表黑色像素,1代表白色像素,将数据储存在一个叫`data`的数组中。我们还需要监听鼠标在画布上的运动,来触发`fillSquare()`方法。`fillSquare()`方法可以将用户选中的像素上色。利用一些简单的运算,我们就能够将用户鼠标的绘制轨迹转化为我们画布中的像素信息了。 162 | 163 | onMouseMove: function(e, ctx, canvas) { 164 | if (!canvas.isDrawing) { 165 | return; 166 | } 167 | this.fillSquare(ctx, 168 | e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); 169 | }, 170 | 171 | onMouseDown: function(e, ctx, canvas) { 172 | canvas.isDrawing = true; 173 | this.fillSquare(ctx, 174 | e.clientX - canvas.offsetLeft, e.clientY - canvas.offsetTop); 175 | }, 176 | 177 | onMouseUp: function(e) { 178 | canvas.isDrawing = false; 179 | }, 180 | 181 | fillSquare: function(ctx, x, y) { 182 | var xPixel = Math.floor(x / this.PIXEL_WIDTH); 183 | var yPixel = Math.floor(y / this.PIXEL_WIDTH); 184 | this.data[((xPixel - 1) * this.TRANSLATED_WIDTH + yPixel) - 1] = 1; 185 | 186 | ctx.fillStyle = '#ffffff'; 187 | ctx.fillRect(xPixel * this.PIXEL_WIDTH, yPixel * this.PIXEL_WIDTH, 188 | this.PIXEL_WIDTH, this.PIXEL_WIDTH); 189 | }, 190 | 191 | 现在,我们离目标越来越近了!接下来,我们需要一个对传输到服务器的训练数据进行处理的函数。就像下面这个`train()`函数。它会对数据进行错误排查,之后将数据写入`trainArray`中,最后通过调用`sendData()`函数将数据发送给服务器。 192 | ```javascript 193 | train: function() { 194 | var digitVal = document.getElementById("digit").value; 195 | if (!digitVal || this.data.indexOf(1) < 0) { 196 | alert("Please type and draw a digit value in order to train the network"); 197 | return; 198 | } 199 | this.trainArray.push({"y0": this.data, "label": parseInt(digitVal)}); 200 | this.trainingRequestCount++; 201 | 202 | // Time to send a training batch to the server. 203 | if (this.trainingRequestCount == this.BATCH_SIZE) { 204 | alert("Sending training data to server..."); 205 | var json = { 206 | trainArray: this.trainArray, 207 | train: true 208 | }; 209 | 210 | this.sendData(json); 211 | this.trainingRequestCount = 0; 212 | this.trainArray = []; 213 | } 214 | }, 215 | ``` 216 | 217 | 这里有一个值得一提的有趣设计,就是`trainingRequestCount`, `trainArray`, 和`BATCH_SIZE`的使用。 218 | 为什么这么讲呢,`BATCH_SIZE`是一个预定义的常量,用于限定客户端在一次性将成批的数据传递至服务期之前的最大缓存容量。 219 | 这样设计的主要原因是为了避免大量请求对服务器造成过大的压力。 220 | 221 | 如果存在许多客户端(例如,许多用户在`ocr.html`页面训练系统),或者如果客户端中存在另一个层,它接收扫描的绘制数字并将它们转换为像素以训练网络,则“BATCH_SIZE”为1将导致许多不必要的请求。 这种方法为客户端带来了灵活性,然而,在实践中存在需要时,服务端也需要执行批处理。 因为服务端可能会遭到DoS攻击,其中恶意客户端有意地向服务器发送许多请求以淹没它,就会使得它崩溃。 222 | 223 | 我们还需要一个test()函数。与train()类似,它应该对数据的有效性进行简单检查并将其发送出去。 然而,对于test(),不需要批处理,因为用户请求预测时应该会得到一个即时的结果。 224 | 225 | ```javascript 226 | test: function() { 227 | if (this.data.indexOf(1) < 0) { 228 | alert("Please draw a digit in order to test the network"); 229 | return; 230 | } 231 | var json = { 232 | image: this.data, 233 | predict: true 234 | }; 235 | this.sendData(json); 236 | }, 237 | ``` 238 | 239 | 最后,我们将需要一些函数来进行HTTP POST请求,接收响应,并在处理任何可能的错误。 240 | 241 | ```javascript 242 | receiveResponse: function(xmlHttp) { 243 | if (xmlHttp.status != 200) { 244 | alert("Server returned status " + xmlHttp.status); 245 | return; 246 | } 247 | var responseJSON = JSON.parse(xmlHttp.responseText); 248 | if (xmlHttp.responseText && responseJSON.type == "test") { 249 | alert("The neural network predicts you wrote a \'" 250 | + responseJSON.result + '\''); 251 | } 252 | }, 253 | 254 | onError: function(e) { 255 | alert("Error occurred while connecting to server: " + e.target.statusText); 256 | }, 257 | 258 | sendData: function(json) { 259 | var xmlHttp = new XMLHttpRequest(); 260 | xmlHttp.open('POST', this.HOST + ":" + this.PORT, false); 261 | xmlHttp.onload = function() { this.receiveResponse(xmlHttp); }.bind(this); 262 | xmlHttp.onerror = function() { this.onError(xmlHttp) }.bind(this); 263 | var msg = JSON.stringify(json); 264 | xmlHttp.setRequestHeader('Content-length', msg.length); 265 | xmlHttp.setRequestHeader("Connection", "close"); 266 | xmlHttp.send(msg); 267 | } 268 | ``` 269 | 270 | ### 服务端 (`server.py`) 271 | 272 | 尽管这只是一个中继信息的小型服务器,但是我们仍然需要考虑如何接收和处理HTTP请求。 首先,我们需要决定使用什么样的HTTP请求。 在最后一节中,客户端使用的是POST请求,但是为什么我们决定使用POST呢?由于数据被发送到服务器,PUT或POST请求是最合适的。我们只需要发送一个不带任何URL参数的json包。所以在理论上,GET请求也能满足我们的需求,但在语义上这样却是没有意义的。在程序员之间长期以来一直有着关于选择PUT和POST的争论;对于这个问题KNPLabs有一个非常有[趣的总结](https://knpuniversity.com/screencast/rest/put-versus-post)。 273 | 274 | 另一个需要考虑的问题是将“训练”和“预测”请求发送到不同端点(例如`http://localhost/train`和`http://localhost/predict`),还是发送到同一端点进行处理。在这种情况下,我们可以使用后一种方法,因为训练和预测对数据执行的操作之间的差别很小,所以一个简单的if语句就可以非常完美的适应不同的情况。在实践中,如果服务器对每个请求类型进行的处理有很大的差别,那么最好将它们作为单独的端点。这个决定,同时影响着如何设计网页返回的错误信息。例如,当在有效载荷中既没有指定“train”或“predict”时,返回400“错误请求”错误。但是如果使用单独的端点,就不会有这样的问题。如果OCR系统在后台执行的处理由于任何原因而失败,或者服务器没有正确处理发送的信息,则返回500“内部服务器错误”。同样,如果端点是分开的,则将有更多的空间来详细地发送更适当的错误。例如,确定内部服务器错误实际上是由错误请求引起的等等。 275 | 276 | 最后,我们需要决定什么时候在哪里初始化OCR系统。在server.py中服务器启动之前进行初始化是一个不错的方法。因为在第一次运行时,OCR系统需要在对某些预插入的的数据进行网络训练,这可能需要几分钟。如果服务器在此处理完成之前启动,则任何训练或预测的请求都会抛出异常,因为在这种情况的情况下,OCR对象尚未被初始化。当然还有另一种实现方式,预先产生一些不准确的初始ANN用于前几个查询,同时在后台不断的异步的训练ANN。这种替代方法允许立即使用ANN,但是实现更复杂,并且如果服务器被重置,它将仅在服务器启动时及时保存。这种类型的实现对于需要高可用性的OCR服务将更有益。 277 | 278 | 这里我们的服务器主要的代码是在一个小巧的函数中处理POST请求。 279 | 280 | ```python 281 | def do_POST(s): 282 | response_code = 200 283 | response = "" 284 | var_len = int(s.headers.get('Content-Length')) 285 | content = s.rfile.read(var_len); 286 | payload = json.loads(content); 287 | 288 | if payload.get('train'): 289 | nn.train(payload['trainArray']) 290 | nn.save() 291 | elif payload.get('predict'): 292 | try: 293 | response = { 294 | "type":"test", 295 | "result":nn.predict(str(payload['image'])) 296 | } 297 | except: 298 | response_code = 500 299 | else: 300 | response_code = 400 301 | 302 | s.send_response(response_code) 303 | s.send_header("Content-type", "application/json") 304 | s.send_header("Access-Control-Allow-Origin", "*") 305 | s.end_headers() 306 | if response: 307 | s.wfile.write(json.dumps(response)) 308 | return 309 | ``` 310 | 311 | ### 设计前馈式神经网络 (`neural_network_design.py`) 312 | \label{sec.ocr.feedforward} 313 | 在设计前馈ANN时,我们需要先考虑一些因素。第一是使用什么激活函数。我们之前提到激活函数是节点输出的决策者。激活函数的决策的类型将帮助我们决定使用哪一个。在我们的例子中,我们将设计一个ANN,为每个数字(0-9)输出一个介于0和1之间的值。值越接近1意味着ANN预测这是绘制的数字,值越接近0意味着它被预测不是绘制的数字。因此,我们想要一个输出接近0或接近1的激活函数。我们还需要一个可微分的函数,因为我们将需要导数用于反向传播计算。在这种情况下常用的函数是S形,因为它满足这两个约束。 StatSoft提供了一个很好的[常用激活函数及其属性列表](http://www.fmi.uni-sofia.bg/fmi/statist/education/textbook/eng/glosa.html)。 314 | 315 | 另一个要考虑的因素是我们是否想包含偏移因子。我们之前提到过几次偏移因子,但没有真正讲他们是什么或为什么我们使用它们。让我们尝试通过回到描述如何计算节点的输出的图15.1来理解这一点。假设我们有一个输入节点和一个输出节点,我们的输出公式将是$ y = f(wx)$,其中$ y $是输出,$ f()$是激活函数,$ w $是节点之间链路的权重,$ x $是节点的变量输入。偏差本质上是一个节点,其输出总是$ 1 $。这将改变输出公式为$ y = f(wx + b)$其中$ b $是偏置节点和下一个节点之间的连接的权重。如果我们将$ w $和$ b $视为常量,将$ x $视为一个变量,那么添加一个偏差将一个常数添加到$ f(。)$的线性函数输入。 316 | 317 | 因此,添加偏移因子可以形成$ y $ 在截距范围的移位。所以通常来讲,节点输出会更加灵活性。添加一个偏移因子是一个非常常见的优化方案,特别是对于具有较少少量输入和输出的ANN。偏移因子会给ANN的输出带来更多灵活性,因此也可以为ANN提供更多的精度空间。没有偏移因子,我们不太可能使用我们的ANN进行正确的预测,或者我们会需要更多的隐藏节点来做出更准确的预测。 318 | 319 | 要考虑的其他因素还包括隐藏层的数量和每层的隐藏节点的数量。对于具有许多输入和输出的较大ANN,这些数字是通过尝试不同的值并测试网络的性能来决定的。在这种情况下,通过训练给定大小的ANN并观察验证集合的正确分类的百分比来测量性能。在大多数情况下,单个隐藏层对于正常的性能就够了,因此我们在这里只试验隐藏节点的数量。 320 | 321 | ```python 322 | # Try various number of hidden nodes and see what performs best 323 | for i in xrange(5, 50, 5): 324 | nn = OCRNeuralNetwork(i, data_matrix, data_labels, train_indices, False) 325 | performance = str(test(data_matrix, data_labels, test_indices, nn)) 326 | print "{i} Hidden Nodes: {val}".format(i=i, val=performance) 327 | ``` 328 | 329 | 这里我们初始化一个ANN,隐藏节点的数量在5-50个中间,每次实验的步幅是5个。然后我们调用test()函数来进行测试。 330 | 331 | ```python 332 | def test(data_matrix, data_labels, test_indices, nn): 333 | avg_sum = 0 334 | for j in xrange(100): 335 | correct_guess_count = 0 336 | for i in test_indices: 337 | test = data_matrix[i] 338 | prediction = nn.predict(test) 339 | if data_labels[i] == prediction: 340 | correct_guess_count += 1 341 | 342 | avg_sum += (correct_guess_count / float(len(test_indices))) 343 | return avg_sum / 100 344 | ``` 345 | 346 | 内部循环正在计数正确分类的数量,然后除以结束时尝试分类的总数。这样我们就会得到ANN的正确比率或百分比精度。因为每次训练ANN时,其权重可能略有不同,所以我们在外部循环中重复该过程100次,以便我们可以取这个特定ANN配置的准确性的平均值。在我们的示例中,neural_network_design.py的示例运行如下所示: 347 | 348 | ``` 349 | PERFORMANCE 350 | ----------- 351 | 5 Hidden Nodes: 0.7792 352 | 10 Hidden Nodes: 0.8704 353 | 15 Hidden Nodes: 0.8808 354 | 20 Hidden Nodes: 0.8864 355 | 25 Hidden Nodes: 0.8808 356 | 30 Hidden Nodes: 0.888 357 | 35 Hidden Nodes: 0.8904 358 | 40 Hidden Nodes: 0.8896 359 | 45 Hidden Nodes: 0.8928 360 | ``` 361 | 362 | 从这个输出,我们可以得出结论,15个隐藏节点是最优的。从10到15添加5个节点使我们的准确度提高〜1%,而将精度提高另一个1%将需要添加另外20个节点。增加隐藏节点计数也增加了计算开销。这将需要训隐藏节点网络并进行预测的时间会很长。因此,我们选择使用最后一个隐藏节点的数量,会带来准确性的急剧增加。当然,当设计一个ANN时,计算开销并不是一个很大的问题,并且使ANN拥有更高的准确性是我们优先级最高的工作。所以,在这种情况下,我们最好的选择使45个隐藏节点,而不是15个。 363 | 364 | ### OCR核心功能 365 | 366 | 在本节中,我们将讨论如何通过反向传播进行实际训练,如何使用网络进行预测,以及其他有关决策的核心功能的一些关键设计。 367 | 368 | #### 通过反向传播进行训练 (`ocr.py`) 369 | 370 | 我们使用反向传播算法来训练我们的ANN。它需要对训练集中的每个样本重复包括4个主要步骤来更新ANN权重。 371 | 372 | 首先,我们将权重初始化为小(-1和1)之间的随机值。在我们的例子中,我们将它们初始化为-0.06和0.06之间的值,并将它们存储在矩阵`theta1`,`theta2`,`input_layer_bias`和`hidden_​​layer_bias`中。由于一个层中的每个节点链接到下一层中的每个节点,我们可以创建具有m行和n列的矩阵,其中n是一个层中的节点数量,m是相邻层中的节点数量。该矩阵将表示这两个层之间的链路的所有权重。这里`theta1`为我们的20x20像素输入共400列,`num_hidden_​​nodes`行。同样,`theta2`表示隐藏层和输出层之间的链接。它有`num_hidden_​​nodes`列和`NUM_DIGITS(10)`行。其他两个向量(1行),`input_layer_bias`和`hidden_​​layer_bias`表示偏差。 373 | 374 | ```python 375 | def _rand_initialize_weights(self, size_in, size_out): 376 | return [((x * 0.12) - 0.06) for x in np.random.rand(size_out, size_in)] 377 | ``` 378 | 379 | ```python 380 | self.theta1 = self._rand_initialize_weights(400, num_hidden_nodes) 381 | self.theta2 = self._rand_initialize_weights(num_hidden_nodes, 10) 382 | self.input_layer_bias = self._rand_initialize_weights(1, 383 | num_hidden_nodes) 384 | self.hidden_layer_bias = self._rand_initialize_weights(1, 10) 385 | 386 | ``` 387 | 388 | 第二步是前向传播,其本质上是如[什么是anns](#什么是anns)中所描述的那样从输入节点开始逐层地计算的节点输出。这里,`y0`是我们希望用来训练ANN的大小为400的数组输入。我们将`θ1`乘以`y0`转置,使得我们有两个大小为(`num_hidden_​​nodes×400)*(400×1)`的矩阵,并且具有对于大小为`num_hidden_​​nodes`的隐藏层的输出的结果向量。然后,我们添加偏差向量,并应用矢量化S形激活函数得到一个输出向量`y1`。 `y1`是我们隐藏层的输出向量。再次重复相同的过程以计算输出节点的`y2`。 `y2`现在是我们的输出层向量,其值表示它们的索引是绘制数字的可能性。例如,如果有人绘制一个8,如果ANN做出正确的预测,则在第8个索引处的`y2`的值将是最大的。然而,6可能具有比为所绘制的数字的1更高的似然性,因为其看起来更类似于8,并且和8也有着更多重叠得像素.`y2`随着很多用于训练的绘制的数字,ANN将会变得更准确。 389 | 390 | ```python 391 | # The sigmoid activation function. Operates on scalars. 392 | def _sigmoid_scalar(self, z): 393 | return 1 / (1 + math.e ** -z) 394 | ``` 395 | 396 | ```python 397 | y1 = np.dot(np.mat(self.theta1), np.mat(data['y0']).T) 398 | sum1 = y1 + np.mat(self.input_layer_bias) # Add the bias 399 | y1 = self.sigmoid(sum1) 400 | 401 | y2 = np.dot(np.array(self.theta2), y1) 402 | y2 = np.add(y2, self.hidden_layer_bias) # Add the bias 403 | y2 = self.sigmoid(y2) 404 | ``` 405 | 406 | 第三步是反向传播,其涉及计算输出节点处的错误,然后在每个中间层返回到输入。这里我们首先创建一个期望的输出向量`actual_vals`,在表示绘制数字的值的数字的索引为1,否则为0。输出节点处的误差向量`output_errors`通过从`actual_vals`中减去实际输出向量`y2`来计算。对于每个隐藏层之后,我们计算两个组件。首先,我们有下一层的转置权重矩阵乘以其输出误差。然后我们得到应用于上一层的激活函数的导数。然后,我们对这两个分量执行元素级乘法,得到隐藏层的误差向量。这里我们称之为`hidden_​​errors`。 407 | 408 | ```python 409 | actual_vals = [0] * 10 410 | actual_vals[data['label']] = 1 411 | output_errors = np.mat(actual_vals).T - np.mat(y2) 412 | hidden_errors = np.multiply(np.dot(np.mat(self.theta2).T, output_errors), 413 | self.sigmoid_prime(sum1)) 414 | ``` 415 | 416 | 基于先前计算的误差得到的权重更新,调整ANN权重。通过矩阵乘法在每一层更新权重。每层的误差矩阵乘以前一层的输出矩阵。然后将该乘积乘以称为学习速率的标量,并将其加到权重矩阵。学习速率是在0和1之间的值,其影响ANN中的学习的速度和准确性。较大的学习速率值将生成快速学习但不太准确的ANN,而较小的值将生成学习速度较慢但是更准确的ANN。在我们的例子中,我们有一个相对较小的学习率,0.1。因为我们没有为了使用户进行训练或预测请求而立即完成对ANN的训练的需求,所以这样的学习率很不错。这样我们就可以通过简单地将学习速率乘以层的误差向量来更新偏差。 417 | 418 | ```python 419 | self.theta1 += self.LEARNING_RATE * np.dot(np.mat(hidden_errors), 420 | np.mat(data['y0'])) 421 | self.theta2 += self.LEARNING_RATE * np.dot(np.mat(output_errors), 422 | np.mat(y1).T) 423 | self.hidden_layer_bias += self.LEARNING_RATE * output_errors 424 | self.input_layer_bias += self.LEARNING_RATE * hidden_errors 425 | ``` 426 | 427 | #### 测试经过了训练的网络 (`ocr.py`) 428 | 429 | 一旦ANN通过反向传播进行了训练,使用它进行预测就是相当便捷的了。在这里我们可以看到的,如我们在反向传播的步骤2中所做的那样,我们计算了ANN的输出`y2`。然后我们在向量中寻找具有最大值的索引。该指数是ANN预测的数字。 430 | 431 | ``` 432 | def predict(self, test): 433 | y1 = np.dot(np.mat(self.theta1), np.mat(test).T) 434 | y1 = y1 + np.mat(self.input_layer_bias) # Add the bias 435 | y1 = self.sigmoid(y1) 436 | 437 | y2 = np.dot(np.array(self.theta2), y1) 438 | y2 = np.add(y2, self.hidden_layer_bias) # Add the bias 439 | y2 = self.sigmoid(y2) 440 | 441 | results = y2.T.tolist()[0] 442 | return results.index(max(results)) 443 | ``` 444 | 445 | #### 设计中的一些其他设计 (`ocr.py`) 446 | 我们可以在线获取很多详细介绍反向传播的实现的许多资源。来自[威拉米特大学的一门课程](http://www.willamette.edu/~gorr/classes/cs449/backprop.html)就是一个非常不错的资源。它讨论了反向传播的步骤,然后又解释如何将其转换为矩阵形式。虽然使用矩阵的计算量与使用循环的计算量相同,但其优点是代码更简单,并且更易于使用更少的嵌套循环进行读取。正如我们所看到的,整个训练过程是使用矩阵代数在25行代码下编写的。 447 | 448 | 正如在[在一个简单的ocr系统中设计决策方案](#在一个简单的ocr系统中设计决策方案)中提到的,保存ANN的权重可以确保当服务器关闭或由于任何原因突然崩溃时,我们不会失去之前的训练状态。我们通过JSON文件来保存这些权重。在启动时,OCR将ANN之前保存权重加载到内存。保存功能不是由OCR在内部调用,而是由服务器决定何时执行保存。在我们的例子中,服务器在每次更新后保存权重。这是一个快速和简单的解决方案,但它不是最佳的,因为写入磁盘是耗时的。这也防止我们处理多个并发请求,因为没有机制阻止对同一文件的同时写入。在更复杂的服务器中,可以在关闭时或者每隔几分钟进行一次保存,使用某种形式的锁定或时间戳协议来确保没有数据丢失。 449 | 450 | ```python 451 | def save(self): 452 | if not self._use_file: 453 | return 454 | 455 | json_neural_network = { 456 | "theta1":[np_mat.tolist()[0] for np_mat in self.theta1], 457 | "theta2":[np_mat.tolist()[0] for np_mat in self.theta2], 458 | "b1":self.input_layer_bias[0].tolist()[0], 459 | "b2":self.hidden_layer_bias[0].tolist()[0] 460 | }; 461 | with open(OCRNeuralNetwork.NN_FILE_PATH,'w') as nnFile: 462 | json.dump(json_neural_network, nnFile) 463 | 464 | def _load(self): 465 | if not self._use_file: 466 | return 467 | 468 | with open(OCRNeuralNetwork.NN_FILE_PATH) as nnFile: 469 | nn = json.load(nnFile) 470 | self.theta1 = [np.array(li) for li in nn['theta1']] 471 | self.theta2 = [np.array(li) for li in nn['theta2']] 472 | self.input_layer_bias = [np.array(nn['b1'][0])] 473 | self.hidden_layer_bias = [np.array(nn['b2'][0])] 474 | ``` 475 | 476 | ## 总结 477 | 现在我们已经了解了AI,ANN,反向传播和构建端到端OCR系统,让我们回顾一下本章的重点和大纲。 478 | 479 | 在本章的开始,我们介绍了AI,ANNs和我们将要实现的内容的大致的背景。我们讨论了什么是AI,以及一些使用它的例子。我们看到,AI本质上是一组算法或问题解决方法,可以以类似于人类的方式提供问题的答案。然后我们了解前馈ANN的结构。我们了解到,在给定节点处计算输出就像对前面节点的输出的乘积及其连接权重求和一样简单。我们通过首先格式化输入并将数据分割成训练和验证集来讨论如何使用ANN。 480 | 481 | 在我们有了一些背景知识后,我们开始计划创建一个基于Web的客户端 - 服务器系统,它将处理用户对训练或测试OCR的请求。然后我们讨论了客户端如何将绘制的像素解释为数组,并向OCR服务器执行HTTP请求以执行训练或测试。我们还讨论了我们的简单服务器读取请求以及如何通过测试几个隐藏节点计数的性能来设计ANN。我们完成了反向传播核心的训练和测试代码。 482 | 483 | 虽然我们已经构建了一个看起来像模像样的OCR系统,但是本章只是简单地描述了OCR系统的工作原理。更复杂的OCR系统会包含预处理的输入,会使用混合ML算法,会具有更复杂的设计阶段或更多进一步的优化。 484 | -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/Z.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/Z.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/damage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/damage.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/damage_distribution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/damage_distribution.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/gamma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/gamma.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/int_px_150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/int_px_150.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/pmf_eq1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/pmf_eq1.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/pmf_eq2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/pmf_eq2.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/pmf_eq3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/pmf_eq3.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/pmf_eq4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/pmf_eq4.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/probs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/probs.png -------------------------------------------------------------------------------- /决策采样器_A_Rejection_Sampler/image/sum_p.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/决策采样器_A_Rejection_Sampler/image/sum_p.png -------------------------------------------------------------------------------- /持续集成系统 A Continuous Integration System/image/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/持续集成系统 A Continuous Integration System/image/diagram.png -------------------------------------------------------------------------------- /持续集成系统 A Continuous Integration System/持续集成系统.md: -------------------------------------------------------------------------------- 1 | # 持续集成系统 2 | 3 | Malini Das 是一个致力于改善编码速度(当然是在保证代码安全的前提下),并不断寻求交叉编程的解决方案的软件工程师。她曾以工具工程师的身份供职于Mozilla,现在她在Twitch工作。可以通过关注Malini的[Twitter](https://twitter.com/malinidas) 或是她的[blog](http://malinidas.com/)来了解她的最新动态. 4 | 5 | ## 什么是持续集成系统 6 | 7 | 在软件开发的过程中,我们需要一种方式来保证每一个新功能都能稳定的实现,每一个Bug都能按照预期得到修复。通常来讲这种方式就是对代码进行测试。多数情况下,开发人员会在开发环境中直接进行测试来确保功能实现的完整和稳定,很少有人会有时间在每一种可能的运行环境中都进行测试。进一步来讲,随着开发的不断进行,需要进行的测试不断的增加,在开发环境中对代码进行完全的测试的可行性也随之变得越来越低。持续集成系统的出现,正是为了解决这种开发中的困境。 8 | 9 | 持续集成(CI)系统是专门用来对新代码进行测试的系统。当一段新的代码被提交时,持续集成系统的作用就是确保这些新代码不会导致之前测试样例的失败。要实现这样的功能,就要求持续集成系统可以获取到新更改的代码,自动完成测试,并生成测试报告。同时,持续集成系统还需要保证良好的稳定性。也就是说,当系统的任何一部分出现错误甚至崩溃时,整个系统应该可以从上一次中断的地方重新恢复运行。 10 | 11 | 这个系统同样需要均衡负载的能力,这样一来当提交新版本的时间比运行测试的时间还要短的时候,我们仍然可以保证在一个合理的时间内获得测试的结果。我们可以通过向多线程分发测试样例,并行化运行他们来实现这一点。本项目中将介绍一个小型可拓展的极简分布式持续集成系统。 12 | 13 | ## 注意事项及相关说明 14 | 15 | 在本项目中使用Git作为进行测试的代码托管系统。我们只会调用标准的代码管理指令,所以,如果你并不熟悉Git的操作,但对于使用其他像svn或者Mercurial这样的版本控制系统(VCS)很熟悉,那么你也可以继续跟随下面的操作进行开发试验。 16 | 17 | 出于代码长度的限制及单元测试的要求,我简化了测试样例搜索的机制。我们将*仅仅*运行在名为`tests`的文件夹中的测试样例。 18 | 19 | 通常来讲,持续集成系统监听的应该是远程代码托管库的变化。但是为了方便起见,在我们的示例之中,我们选择监听本地的代码库文件来代替远程文件。 20 | 21 | 持续集成系统并不是必须按照固定的时间表执行。当然你也可以设定成每一次或几次提交时自动运行。在我们的例子中,我们将CI设定为定期运行。也就是说,如果我们设定CI系统设定为5秒钟运行一次,那么每隔5秒系统就会对5秒内最近的一次提交进行测试。不论这5秒内发生了多少次提交,系统只会对最后一次提交的结果进行一次测试。 22 | 23 | CI系统旨在监听代码库中的变化。在实际中使用的CI系统可以通过代码库的通知来获取提交信息。例如,在Github中提供了专门的“提交钩子”.在这种模型中CI系统的会被Github中设置的通知URL对应的服务器唤醒进行相应的响应。但是这种模型在我们本地的试验环境中太过复杂了,所以我们使用了观察者模型。在这种模型中系统主动检测代码变化而不是等待代码管理库的通知。 24 | 25 | CI系统还需要一个报告形式(比如一个网页),这样触发测试的人将测试的结果提交给CI的结果组件,其他项目中的参与者就可以直接查看到相应的结果。 26 | 27 | 注意,在我们的项目中,只是讨论了众多CI系统框架中的一种。在这种框架中,我们将我们的项目简化成了三个主要组成部分。 28 | 29 | ## 引言 30 | 31 | 最基础的持续集成系统分为三个部分:监听器,测样例调度器,和测试运行器。 32 | 首先监听器会监视代码库,当发生提交时,监听器会通知调度器。之后,样例调度器会分配测试运行器完成对应提交版本号的测试。 33 | 34 | 这三部分的组合方式有很多。我们可以将他们全部运行在一台电脑的同一个线程之中。但是这样一来,我们的CI系统就会缺少了处理大负载的能力,当很多的提交带来了大量的测试内容时,这种方案非常容易引起工作的积压。同时这种方案的容错率非常低,一旦运行该系统的计算机发生故障或是断电,没有后备的系统完成中断的工作。我们希望我们的CI系统应该根据需求尽可能的同时完成多项测试工作,并且在机器发生意外停机时有很好的后备运行方案。 35 | 36 | 为了构建一个负载能力强并且容错率又高的CI系统,在本项目中,上述的每一个组件都以独立的进程运行。每个进程之间完全独立,并且每种线程可以同时运行多个实例。在很多的测试工作需要同时展开时这种方案会带来非常大的便利。 我们可以在不同的线程上同时运行多个测试运行器的实例,每个测试运行器独立工作,这样就可以有效的解决测试队列积压的问题。 37 | 38 | 在本项目中这些组件虽然是相互独立的运行在单独的线程上,但是线程之间可以通过套接字进行通信,这样我们就可以在互联网中的不同主机上分别运行这些进程。我们会为每一个进程分配一个地址/端口,这样每个进程之间就可以通过向分配到的地址发送消息来互相通信。 39 | 40 | 通过分布式的架构,我们可以做到在硬件发生错误时即时的进行处理。我们可以把监听器,测样例调度器,和测试运行器分别运行在不同的机器上,他们可以通过网络保持相互通信。当他们之中的任何一个发生问题时,我们可以安排一台新的主机上线运行发生问题的进程。这样一来这个系统就会有非常高的容错率。 41 | 42 | 在本项目中,并没有包含自动恢复的代码。自动恢复的功能去取决于你使用的分布式系统的结构。在实际的使用中,CI系统通常运行在支持故障信息转移(举个例子,当分布式系统中的一个机器发生故障,我们设定好的后备机器会自动接手中断的工作)的分布式系统之中。 43 | 44 | 为了方便测试我们的系统,在本项目中我们将会在本地手动的触发一些进程来模拟分布式的环境。 45 | 46 | ## 项目的文件结构 47 | 48 | 项目中每个组件的Python文件结构如下:监听器 \newline(`repo_observer.py`),测试样例调度器(`dispatcher.py`),测试运行器 \newline (`test_runner.py`)。上述每个线程之间通过套接字通信,我们将用于实现通信功能的代码统一的放在 helpers.py 中。这样就可以让每个组件直接从这个文件中导入相关功能,而不用再每个组件中重复的写这段代码。 49 | 50 | 另外,我们还用到了bash脚本。这些脚本用来执行一些简单的bash和git的操作,直接通过bash脚本要比利用Python提供的系统级别的模块(比如,os或者subprocess之类的)要更方便一些。 51 | 52 | 最后,我们还建立了一个`tests`目录来存放我们需要CI系统运行的测试样例。在这个目录中包含两个用于测试的样例,其中一个样例模拟了样例通过时的情况,另一个则模拟了失败时的情况。 53 | 54 | ## 初始设置 55 | 56 | 虽然我们的CI系统是为分布式的运行而设计的,但是为了在理解CI系统运行原理的过程中不受网络因素的影响,我们会在同一台计算机上运行所有的组件。当然,如果你想要试一试分布式的运行环境,你也可以将每一个组件分别运行到不同的主机上。 57 | 58 | 持续集成系统通过监听代码的变动来触发测试,所以在开始之前我们需要设置一个用于监听的代码库。 59 | 60 | 我们称这个用于测试的项目为 `test_repo`: 61 | 62 | ```bash 63 | $ mkdir test_repo 64 | $ cd test_repo 65 | $ git init 66 | ``` 67 | 68 | 监听器模块通过检查commit(提交)来进行代码更新的监听,所以我们至少需要一次的commit才能进行监听器模块的测试。 69 | 70 | 将`tests`文件夹拷贝到`test_repo`中,然后提交: 71 | 72 | ```bash 73 | $ cp -r /this/directory/tests /path/to/test_repo/ 74 | $ cd /path/to/test\_repo 75 | $ git add tests/ 76 | $ git commit -m ”add tests” 77 | ``` 78 | 79 | 现在,在我们测试用的代码仓库中的master分支上有了一次可以用来测试的提交。 80 | 81 | 监听器组件需要一份单独的代码拷贝来检测新的提交。让我们从master分支做一份代码拷贝,起名为`test_repo_clone_obs`: 82 | 83 | ```bash 84 | $ git clone /path/to/test_repo test_repo_clone_obs 85 | ``` 86 | 87 | 测试运行器同样需要一个自己的代码拷贝,这样它才能在commit发生时运行相关的测试。我们同样从master分支做一份代码拷贝,并起名为`test_repo_clone_runner`: 88 | 89 | ```bash 90 | $ git clone /path/to/test_repo test_repo_clone_runner 91 | ``` 92 | 93 | ## 组件 94 | 95 | ### 监听器(`repo_observer.py`) 96 | 97 | 监听器的任务是监听代码库中的改动,并在发现改动是通知测试样例分配器。为了保证我们的CI系统与各种版本控制系统(并不是所有的VCS都有内置的通知系统)都能够兼容,我们设定CI系统定时检查代码库是否有新的提交,而不是等待VCS在代码提交时发送通知。 98 | 99 | 监听器会定时轮询代码库,当有新的提交时,监听器会向分配器推送需要运行测试的代码的版本ID。监听器的轮询过程是:首先,在监听器的储存空间中得到当前的提交版本;其次,将本地库更新至这个版本;最后,将这个版本与远程库最近一次的提交ID进行比对。这样,监听器中本地的当前版本与远程的最新版本不一致时就判定为发生了新的提交。在我们的CI系统中,监听器只会向分配器推送最近的一次提交。这意味着,如果在一次的轮询周期内发生了两次提交,监听器只会为最近的一次运行测试。通常来讲,CI系统会为自上一次更新以来的每一次的提交运行相应的测试。但是为了简单起见,这次我们搭建的CI系统采取了仅为最后一次提交运行测试的方案。 100 | 101 | 监听器必须清楚自己监听的到底是哪一个代码库,我们之前已经在`/path/to/test_repo_clone_obs`建立了一份用于监听的代码拷贝。我们的监听器会使用这份拷贝进行检测。为了监听器能够使用这份拷贝,我们在调用`repo_observer.py`时会传入这一份代码拷贝的路径。监听器会利用这份拷贝从主仓库中拉取最新的代码。 102 | 103 | 同样,我们还需要为监听器提供测试用例分配器的地址,这样监听器推送的消息才能传递到分配器中。在运行监听器时,可以通过命令行参数`--dispatcher-server`来传递分配器的地址。如果不手动传入地址,分配器的默认地址取值为:`localhost:8888`。 104 | 105 | ```python 106 | def poll(): 107 | parser = argparse.ArgumentParser() 108 | parser.add_argument("--dispatcher-server", 109 | help="dispatcher host:port, " \ 110 | "by default it uses localhost:8888", 111 | default="localhost:8888", 112 | action="store") 113 | parser.add_argument("repo", metavar="REPO", type=str, 114 | help="path to the repository this will observe") 115 | args = parser.parse_args() 116 | dispatcher_host, dispatcher_port = args.dispatcher_server.split(":") 117 | ``` 118 | 119 | 当运行监听器脚本时,会直接从`poll()`开始运行。这个函数会将命令行的参数传递进来,并开始一个无限的while循环。这个while循环会定期的检查代码库的变化。这个循环中所做的第一个工作就是运行Bash脚本`update_repo.sh`[^bash]。 120 | 121 | [^bash]: 这里没有使用python指令而是使用Bash脚本,是因为我们需要执行一些像检查文件是否存在,创建文件以及使用Git指令这样的操作。相比Python的实现方式,shell脚本更加直接而简单。另外,Python还提供了很多跨平台的模块方便我们的使用。比如,Python的内置模块`os`可以用来操作文件系统,GitPython模块可以提供Git的调用方法,但是使用这些方法不免显得有些舍近求远。 122 | 123 | ```python 124 | while True: 125 | try: 126 | # call the bash script that will update the repo and check 127 | # for changes. If there's a change, it will drop a .commit_id file 128 | # with the latest commit in the current working directory 129 | subprocess.check_output(["./update_repo.sh", args.repo]) 130 | except subprocess.CalledProcessError as e: 131 | raise Exception("Could not update and check repository. " + 132 | "Reason: %s" % e.output) 133 | ``` 134 | 135 | `update_repo.sh`用于识别新的提交并通知监听器。它首先记录当前所在的提交ID,然后拉取最新的代码,接着检查最新的提交ID。如果当前的版本ID与最新的匹配,说明代码没有变动,所以监听器不会作出任何响应。但是,如果提交ID间存在不同,就意味着有新了新的提交。这时,`update_repo.sh`会创建一个叫`.commit_id`的文件来记录最新的提价ID。 136 | 137 | `update_repo.sh`的细分步骤如下: 138 | 139 | 首先,我们的脚本源自于一个叫`run_or_fail.sh`的文件。`run_or_fail.sh`提供了一些shell脚本的辅助函数。通过这些函数我们可以运行指定的脚本并可以在运行出错时输出错误信息。 140 | 141 | ```bash 142 | #!/bin/bash 143 | 144 | source run_or_fail.sh 145 | ``` 146 | 147 | 接下来,我们的脚本会试图删除`.commit_id`文件。因为`repo_observer.py`会不断循环的调用`updaterepo.sh`,如果在上一次的调用中产生了`.commit_id`文件,并且其中储存的版本ID我们在上一次轮询中已经完成了测试,就会造成混乱。所以我们在每次都会先删除上一次的`.commit_id`文件,以免产生混乱。 148 | 149 | ```bash 150 | bash rm -f .commit_id 151 | ``` 152 | 153 | 在删除了文件之后(在文件已经存在的情况下),脚本会检查我们监听的代码库是否存在,再把`.commit_id`更新到最近的一次提交,保证`.commit_id`文件与代码库提交ID之间的同步。 154 | 155 | ```bash 156 | run_or_fail "Repository folder not found!" pushd $1 1> /dev/null 157 | run_or_fail "Could not reset git" git reset --hard HEAD 158 | ``` 159 | 160 | 再之后,读取git的日志,将其中最后一次的提交ID解析出来。 161 | 162 | ```bash 163 | COMMIT=$(run_or_fail "Could not call 'git log' on repository" git log -n1) 164 | if [ $? != 0 ]; then 165 | echo "Could not call 'git log' on repository" 166 | exit 1 167 | fi 168 | COMMIT_ID=`echo $COMMIT | awk '{ print $2 }'` 169 | ``` 170 | 171 | 接下来,拉取代码库,获取最近所有的更改,并得到最新的提交ID。 172 | 173 | ```bash 174 | run_or_fail "Could not pull from repository" git pull 175 | COMMIT=$(run_or_fail "Could not call 'git log' on repository" git log -n1) 176 | if [ $? != 0 ]; then 177 | echo "Could not call 'git log' on repository" 178 | exit 1 179 | fi 180 | NEW_COMMIT_ID=`echo $COMMIT | awk '{ print $2 }'` 181 | ``` 182 | 183 | 最后,如果新得到的提交ID与上一次的ID不匹配,我们就知道在两次轮询间发生了新的提交,所以我们的脚本应该将新的提交ID储存在.commit\_id文件中。 184 | 185 | ```bash 186 | # if the id changed, then write it to a file 187 | if [ $NEW_COMMIT_ID != $COMMIT_ID ]; then 188 | popd 1> /dev/null 189 | echo $NEW_COMMIT_ID > .commit_id 190 | fi 191 | ``` 192 | 193 | 当`repo_observer.py`中的`update_repo.sh`脚本运行街数后,监听器会检查`.commit_id`是否存在。如果文件存在,我们就知道在上一次的轮询后又发生了新的提交,我们需要通知测试样例调度器来开始测试。监听器会通过连接并发送一个'status'请求来检查调度器服务的运行状态,以保证它处在可以正常接受指令的状态正常工作状态。 194 | 195 | ```python 196 | if os.path.isfile(".commit_id"): 197 | try: 198 | response = helpers.communicate(dispatcher_host, 199 | int(dispatcher_port), 200 | "status") 201 | except socket.error as e: 202 | raise Exception("Could not communicate with dispatcher server: %s" % e) 203 | ``` 204 | 205 | 如果调度器返回一个“OK”,监听器就会读取`.commit_id`文件中最新的提交ID,并使用`dispatch:`请求将ID发送到调度器中。监听器会每个5秒发送一次指令。如果发生任何错误,监听器同样会每隔5s进行一次重试。 206 | 207 | ```python 208 | if response == "OK": 209 | commit = "" 210 | with open(".commit_id", "r") as f: 211 | commit = f.readline() 212 | response = helpers.communicate(dispatcher_host, 213 | int(dispatcher_port), 214 | "dispatch:%s" % commit) 215 | if response != "OK": 216 | raise Exception("Could not dispatch the test: %s" % 217 | response) 218 | print "dispatched!" 219 | else: 220 | raise Exception("Could not dispatch the test: %s" % 221 | response) 222 | time.sleep(5) 223 | ``` 224 | 如果你不使用\newline `KeyboardInterrupt` (Ctrl+c)终止监听器发送进程或发送终止信号,监听器会永远重复这一操作。 225 | 226 | # 测试样例分配器(`dispatcher.py`) 227 | 228 | 测试样例分配器是一个用来为测试运行器分配测试任务的独立进程。他会在一个指定端口监听来自代码库监听器及测试运行器的请求。分配器允许测试运行器主动注册,当监听器发送一个提交ID时,它会将测试工作分配给一个已经注册的测试运行器。同时,它还可以平稳的处理测试运行器遇到的各种问题,当一个运行器发生故障,它可以立即将该运行器运行测试的提交ID重新分配给一个新的测试运行器。 229 | 230 | `dispatch.py`脚本从`serve`函数开始运行。首先,它会解析你设定的分配器的地址及端口: 231 | 232 | ```python 233 | def serve(): 234 | parser = argparse.ArgumentParser() 235 | parser.add_argument("--host", 236 | help="dispatcher's host, by default it uses localhost", 237 | default="localhost", 238 | action="store") 239 | parser.add_argument("--port", 240 | help="dispatcher's port, by default it uses 8888", 241 | default=8888, 242 | action="store") 243 | args = parser.parse_args() 244 | ``` 245 | 246 | 这里我们会开启分配器进程以及一个`runner_checker`函数进程,和一个`redistribute`函数进程。 247 | 248 | ```python 249 | server = ThreadingTCPServer((args.host, int(args.port)), DispatcherHandler) 250 | print `serving on %s:%s` % (args.host, int(args.port)) 251 | 252 | ... 253 | 254 | runner_heartbeat = threading.Thread(target=runner_checker, args=(server,)) 255 | redistributor = threading.Thread(target=redistribute, args=(server,)) 256 | try: 257 | runner_heartbeat.start() 258 | redistributor.start() 259 | # Activate the server; this will keep running until you 260 | # interrupt the program with Ctrl+C or Cmd+C 261 | server.serve_forever() 262 | except (KeyboardInterrupt, Exception): 263 | # if any exception occurs, kill the thread 264 | server.dead = True 265 | runner_heartbeat.join() 266 | redistributor.join() 267 | 268 | ``` 269 | 270 | `runner_checker`函数会定期的ping每一个注册在位的运行器,来确保他们都处于正常工作的状态。如果有运行器没有响应,该函数就会将其从注册的运行器池中删除,并且之前分配给他的提交ID会被重新分配给一个新的可用的运行器。函数会在`pending_commits`变量中记录运行受到运行器失去响应影响的提交ID 271 | 272 | ```python 273 | def runner_checker(server): 274 | def manage_commit_lists(runner): 275 | for commit, assigned_runner in server.dispatched_commits.iteritems(): 276 | if assigned_runner == runner: 277 | del server.dispatched_commits[commit] 278 | server.pending_commits.append(commit) 279 | break 280 | server.runners.remove(runner) 281 | while not server.dead: 282 | time.sleep(1) 283 | for runner in server.runners: 284 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 285 | try: 286 | response = helpers.communicate(runner["host"], 287 | int(runner["port"]), 288 | "ping") 289 | if response != "pong": 290 | print "removing runner %s" % runner 291 | manage_commit_lists(runner) 292 | except socket.error as e: 293 | manage_commit_lists(runner) 294 | ``` 295 | 296 | `redistribute`用来将`pending_commits`中记录的提交ID进行重新分配。`redistribute`运行时会不断的检查`pending_commits`文件,一旦发现`pending_commits`中存在提交ID,函数会调用 `dispatch_tests`方法来分配这个提交ID。 297 | 298 | ```python 299 | def redistribute(server): 300 | while not server.dead: 301 | for commit in server.pending_commits: 302 | print "running redistribute" 303 | print server.pending_commits 304 | dispatch_tests(server, commit) 305 | time.sleep(5) 306 | ``` 307 | 308 | `dispatch_tests`函数用来从已注册的运行器池中返回一个可用的运行器。如果得到了一个可用的运行器,函数会发送一个带有提交ID的运行测试指令。如果当前没有可用的运行器,函数会在2s的休眠之后重复上述过程。如果分配成功了,函数会在`dispatched_commits`变量中记录提交ID及该提交ID的测试正在由哪一个运行器运行。如果提交ID在`pending_commits`中,`dispatch_tests`函数会在重新分配后将提交ID从`pending_commits`中删除。 309 | 310 | ```python 311 | def dispatch_tests(server, commit_id): 312 | # NOTE: usually we don't run this forever 313 | while True: 314 | print "trying to dispatch to runners" 315 | for runner in server.runners: 316 | response = helpers.communicate(runner["host"], 317 | int(runner["port"]), 318 | "runtest:%s" % commit_id) 319 | if response == "OK": 320 | print "adding id %s" % commit_id 321 | server.dispatched_commits[commit_id] = runner 322 | if commit_id in server.pending_commits: 323 | server.pending_commits.remove(commit_id) 324 | return 325 | time.sleep(2) 326 | ``` 327 | 328 | 分配器服务用到了标准库中的一个叫`SocketServer`的非常简单的网络服务器模块。`SocketServer`模块中有四种基本的服务器类型:`TCP`, `UDP`, `UnixStreamServer`和`UnixDatagramServer`。为了保证我们的数据传输连续稳定,我们使用基于TCP协议的套接字(UPD并不能保证数据的稳定和连续)。 329 | 330 | `SocketServer`中提供的默认的`TCPServer`最多只支持同时维持一个会话。所以当分配器与一个运行器建立会话后,就无法再与监听器建立连接了。此时来自监听器的会话只能等待第一个会话完成并断开连接才能建立与分配器的连接。这对于我们的项目而言并不是非常理想,在我们预想中,分配器应该直接而迅速的同时与所有运行器及监听器进行通信。 331 | 332 | 为了使我们的分配器可以同时维护多个连接,我们使用了一个自定义的类`ThreadingTCPServer`来为默认的`SocketServer`类增加多线程运行的功能。也就是说无论何时分配器接收到连接请求,他都会新建一个进程来处理这个会话。这就使分配器同时维护多个连接成为了可能。 333 | 334 | ```python 335 | class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 336 | runners = [] # Keeps track of test runner pool 337 | dead = False # Indicate to other threads that we are no longer running 338 | dispatched_commits = {} # Keeps track of commits we dispatched 339 | pending_commits = [] # Keeps track of commits we have yet to dispatch 340 | ``` 341 | 342 | 分配器会为每一个请求定义了处理函数。在继承自`SocketServer`和`BaseRequestHandler`两个类的`DispatcherHandler`类中定义了处理的方法。基类要求我们定义一个随时可以处理链接请求的函数。我们将这个函数的自定义内容写在`DispatcherHandler`中,并且确保在每一次请求出现是,这个函数能够被调用。这个函数会不断地监听发来的请(`self.request`会携带请求信息),并解析请求中的指令。 343 | 344 | ```python 345 | class DispatcherHandler(SocketServer.BaseRequestHandler): 346 | """ 347 | The RequestHandler class for our dispatcher. 348 | This will dispatch test runners against the incoming commit 349 | and handle their requests and test results 350 | """ 351 | command_re = re.compile(r"(\w+)(:.+)*") 352 | BUF_SIZE = 1024 353 | def handle(self): 354 | self.data = self.request.recv(self.BUF_SIZE).strip() 355 | command_groups = self.command_re.match(self.data) 356 | if not command_groups: 357 | self.request.sendall("Invalid command") 358 | return 359 | command = command_groups.group(1) 360 | ``` 361 | 362 | 这个函数可以处理如下指令:`status`, `register`, `dispatch`, 以及 `results`。其中`status`函数用来检测分配器服务是否处于运行状态。 363 | 364 | ```python 365 | if command == "status": 366 | print "in status" 367 | self.request.sendall("OK") 368 | ``` 369 | 370 | 为了让分配器的功能生效,我们需要注册至少一个运行器。当注册器被调用时,为了确保在需要发送提交ID触发测试时能准确的找到对应的运行器,会在一个列表中保存下运行器的“地址:端口”数据(运行器的数据会被保存在一个叫`ThreadingTCPServer`的对象中)。 371 | 372 | ```python 373 | elif command == "register": 374 | # Add this test runner to our pool 375 | print "register" 376 | address = command_groups.group(2) 377 | host, port = re.findall(r":(\w*)", address) 378 | runner = {"host": host, "port":port} 379 | self.server.runners.append(runner) 380 | self.request.sendall("OK") 381 | ``` 382 | 383 | `dispatch` is used by the repository observer to dispatch a test runner 384 | against a commit. The format of this command is `dispatch:`. 385 | The dispatcher parses out the commit ID from this message and sends it 386 | to the test runner. 387 | 388 | 389 | `dispatch` 390 | 391 | ```python 392 | elif command == "dispatch": 393 | print "going to dispatch" 394 | commit_id = command_groups.group(2)[1:] 395 | if not self.server.runners: 396 | self.request.sendall("No runners are registered") 397 | else: 398 | # The coordinator can trust us to dispatch the test 399 | self.request.sendall("OK") 400 | dispatch_tests(self.server, commit_id) 401 | ``` 402 | 403 | `results`指令会由测试运行器在上报测试结果是调用。此命令的用法为`results::: `。``用于标识测试报告对应的提交ID。``用于计算结果数据使用需要多大的缓冲区。最后,``中是实际报告信息。 404 | 405 | ```python 406 | elif command == "results": 407 | print "got test results" 408 | results = command_groups.group(2)[1:] 409 | results = results.split(":") 410 | commit_id = results[0] 411 | length_msg = int(results[1]) 412 | # 3 is the number of ":" in the sent command 413 | remaining_buffer = self.BUF_SIZE - \ 414 | (len(command) + len(commit_id) + len(results[1]) + 3) 415 | if length_msg > remaining_buffer: 416 | self.data += self.request.recv(length_msg - remaining_buffer).strip() 417 | del self.server.dispatched_commits[commit_id] 418 | if not os.path.exists("test_results"): 419 | os.makedirs("test_results") 420 | with open("test_results/%s" % commit_id, "w") as f: 421 | data = self.data.split(":")[3:] 422 | data = "\n".join(data) 423 | f.write(data) 424 | self.request.sendall("OK") 425 | ``` 426 | 427 | ### 测试运行器( `test_runner.py `) 428 | 429 | 测试运行器负责对给定的提交ID运行测试,并上报测试结果。它仅会与分配器通信,分配器负责为其提供需要运行测试的提交ID,并且会接收测试结果报告。 430 | 431 | `test_runner.py`文件会以启动测试运行器服务的`serve`函数为入口,并启动一个线程来运行`dispatcher_checker`函数。由于此启动过程与`repo_observer.py`和`dispatcher.py`的启动过程非常相似,因此我们在这里就不再赘述。 432 | 433 | `dispatcher_checker`函数每五秒对分配器执行一次ping操作,以确保它仍然在正常运行。这个操作主要是出于资源管理上的考虑。如果对应的分配器挂了,就需要将测试运行器也关闭。 否则测试运行器就只能空跑,及接收不到新的任务也无法提交之前任务产生报告。 434 | 435 | ```python 436 | def dispatcher_checker(server): 437 | while not server.dead: 438 | time.sleep(5) 439 | if (time.time() - server.last_communication) > 10: 440 | try: 441 | response = helpers.communicate( 442 | server.dispatcher_server["host"], 443 | int(server.dispatcher_server["port"]), 444 | "status") 445 | if response != "OK": 446 | print "Dispatcher is no longer functional" 447 | server.shutdown() 448 | return 449 | except socket.error as e: 450 | print "Can't communicate with dispatcher: %s" % e 451 | server.shutdown() 452 | return 453 | ``` 454 | 455 | 测试运行器的服务于分配器相同都是`ThreadingTCPServer` ,它需要多线程运行的是因为分配器既会向它下发提交ID,也可能在测试运行的期间的ping它。 456 | 457 | ```python 458 | class ThreadingTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): 459 | dispatcher_server = None # Holds the dispatcher server host/port information 460 | last_communication = None # Keeps track of last communication from dispatcher 461 | busy = False # Status flag 462 | dead = False # Status flag 463 | ``` 464 | 465 | 整个通信流是从分配器向测试运行器发送需要运行测试的提交ID开始的。如果测试运行器的状态可以运行测试,它会发送确认消息回分配器,然后关闭第一个连接。为了使测试运行器在跑测试的同时还能接受来自分配器的其他请求,它会单独启动一个进程来运行测试。 466 | 467 | 468 | 这样,当分配器在测试运行器正在运行测试的时候发来一个请求(比如一个ping请求), 测试运行器的测试跑在另一个进程上,运行器服务本身仍然可以在作出响应。这样测试运行器就可支持同时运行多个任务了。还有一种替代多线程运行的设计是在分配器与测试运行器间建立一个长连接。 但这样会在分配器端消耗大量的内存来维持连接,另外这种方式还对网络有强依赖。 如果网络一旦产生波动(比如突然的断线)就会对系统造成破坏。 469 | 470 | 471 | 测试运行器会从分配器接收到两种消息。 第一种是`ping`消息 ,分配器用这个消息来验证测试运行器是否仍处于活跃状态。 472 | 473 | 474 | ```python 475 | class TestHandler(SocketServer.BaseRequestHandler): 476 | ... 477 | 478 | def handle(self): 479 | .... 480 | if command == "ping": 481 | print "pinged" 482 | self.server.last_communication = time.time() 483 | self.request.sendall("pong") 484 | ``` 485 | 486 | 另一个是`runtest`,它的格式是`runtest:` 。这条指令用于分配器下发需要测试的提交ID。当接收到runtest时,测试运行器将检查当前是否有正在运行的测试。如果有,它会给分配器返回`BUSY`的响应。如果没有,它会返回`OK`,将其状态设置为busy并运行其`run_tests`函数。 487 | 488 | ```python 489 | elif command == "runtest": 490 | print "got runtest command: am I busy? %s" % self.server.busy 491 | if self.server.busy: 492 | self.request.sendall("BUSY") 493 | else: 494 | self.request.sendall("OK") 495 | print "running" 496 | commit_id = command_groups.group(2)[1:] 497 | self.server.busy = True 498 | self.run_tests(commit_id, 499 | self.server.repo_folder) 500 | self.server.busy = False 501 | 502 | ``` 503 | 504 | 这个函数会调用一个叫`test_runner_script.sh` 的shell脚本,该脚本会将代码更新为给定的提交ID。脚本返回后,如果代码库已经被成功的更新,运行器会使用unittest运行测试并将结果收集到一个文件中。测试运行完毕后,测试运行器将读入结果报告文件,并将报告发送给调度程序。 505 | 506 | 507 | ```python 508 | def run_tests(self, commit_id, repo_folder): 509 | # update repo 510 | output = subprocess.check_output(["./test_runner_script.sh", 511 | repo_folder, commit_id]) 512 | print output 513 | # run the tests 514 | test_folder = os.path.join(repo_folder, "tests") 515 | suite = unittest.TestLoader().discover(test_folder) 516 | result_file = open("results", "w") 517 | unittest.TextTestRunner(result_file).run(suite) 518 | result_file.close() 519 | result_file = open("results", "r") 520 | # give the dispatcher the results 521 | output = result_file.read() 522 | helpers.communicate(self.server.dispatcher_server["host"], 523 | int(self.server.dispatcher_server["port"]), 524 | "results:%s:%s:%s" % (commit_id, len(output), output)) 525 | ``` 526 | 527 | 这是`test_runner_script.sh`的内容 : 528 | 529 | ```bash 530 | #!/bin/bash 531 | REPO=$1 532 | COMMIT=$2 533 | source run_or_fail.sh 534 | run_or_fail "Repository folder not found" pushd "$REPO" 1> /dev/null 535 | run_or_fail "Could not clean repository" git clean -d -f -x 536 | run_or_fail "Could not call git pull" git pull 537 | run_or_fail "Could not update to given commit hash" git reset --hard "$COMMIT" 538 | ``` 539 | 540 | 要运行`test_runner.py` ,必须将其指向存储库的副本。你可以使用我们先前创建的`/path/to/test_repo test_repo_clone_runner`副本作为启动参数。默认情况下, `test_runner.py`将在localhost的8900-9000端口上启动,并尝试连接到`localhost:8888`上的调度程序服务器。你通过可以一些可选参数来更改这些值。`--host`和`--port`参数用于指定运行测试运行器服务器地址和端口, `--dispatcher-server`参数指定调度程序的地址。 541 | 542 | ### 控制流程图 543 | 544 | 下图是该系统的概述图。图中假设所有三个文件( `repo_observer.py` , `dispatcher.py`和`test_runner.py` )都已在运行,并描述了每个进程在新的提交发生时所采取的操作。 545 | 546 | ![](./image/diagram.png) 547 | 548 | ### 运行代码 549 | 550 | 我们可以在本地运行这个简单的CI系统,为每个进程使用不同的终端shell。我们首先启动分配器,它默认运行在端口8888上: 551 | 552 | ```bash 553 | $ python dispatcher.py 554 | ``` 555 | 556 | 开一个新的的shell,我们启动测试运行器(这样它就可以在分配器中注册了): 557 | 558 | 559 | ```bash 560 | $ python test_runner.py 561 | ``` 562 | 563 | 测试运行器将自动为自己分配端口,范围为8900-9000。你可以根据需求尽可能多起几个测试运行器。 564 | 565 | 最后,在另一个新shell中,让我们启动代码库监听器: 566 | 567 | 568 | ```bash 569 | $ python repo_observer.py --dispatcher-server=localhost:8888 570 | ``` 571 | 572 | 现在万事俱备,让我们触发一些测试吧玩一下吧!根据设计我们需要创建一个新的提交来触发测试。切换到你的主代码仓库中, 随便改点什么: 573 | 574 | ```bash 575 | $ cd /path/to/test_repo 576 | $ touch new_file 577 | $ git add new_file 578 | $ git commit -m"new file" new_file 579 | ``` 580 | 581 | 然后`repo_observer.py`识别到有一个新的提交产生了,之后通知分配器。你可以在它们各自的shell窗口中查看它们的运行日志。当分配器收到测试结果,它就会将它们存储在此代码库中的`test_results/`文件夹中,并使用提交ID作为文件名。 582 | 583 | ### 错误处理 584 | 585 | 该CI系统中包括一些简单的错误处理。 586 | 587 | 如果你将`test_runner.py`进程杀掉, `dispatcher.py`将确定该运行器将会识别出这个节点已经不再活跃,并将其从运行器池中移除。 588 | 589 | 你也可以模拟网络或系统故障,在测试运行器执行测试的时候将它杀死。这时,分配器会识别到运行器已经挂了,它会将挂掉的运行器从运行器池中移除,并将这个运行器之前在执行的任务分配给池中其他的运行器。 590 | 591 | 如果你杀掉分配器,那么监听器会直接报错。测试运行器也会发现分配器不再运行,并自动关闭。 592 | 593 | ### 结论 594 | 595 | 596 | 通过逐个分析各个进程中的不同功能,我们对构建了一个分布式的持续集成系统的有了一些基本的认识。通过套接字请求实现进程间的通信,我们的CI系统可以分布式的运行在不同的机器上,这增强了我们的系统可靠性和可扩展性。 597 | 598 | 这套CI系统现在的功能仍然非常简单,你还可以发挥自己的才能对它进行各种扩展以实现更多功能。以下是一些改进建议: 599 | 600 | ## 对每次提交自动运行测试 601 | 当前系统将定期检查是否有新的提交并对最近的一次提交运行测试。这个设计可以改为每次提交都触发测试。你可以修改定期检查程序,获取在两次轮询中发生的所有提交来实现这个功能。 602 | 603 | ## 更智能的运行器 604 | 如果测试运行器检测到分配器没有响应,则它将停止运行。当测试运行器正在运行测试时,也会立即关闭!如果测试运行器可以有一段时的等待期或者长期运行(如果你并不在乎它对资源的占用)来等待分配器恢复可能会更好一些。这样当分配器恢复时,运行器既可以将之前执行的测试的报告重新发回分配器。这样可以避免因分配器故障而引起的重复任务,在对每一个提交都执行测试时,这将很大程度上节约运行器资源。 605 | 606 | ## 报告展示 607 | 在真正的CI系统中,测试报告一般会发送到一个单独的报告服务。在报告系统中,人们可以查看报告详情,或是设置一些通知规则,在遇到故障或其他一些特殊的情况下通知相关人员。你可以为我的CI系统创建一个独立的报告进程,替换掉分配器的报告收集功能。这个新的进程可以是一个Web服务(或链接到一个Web服务上), 这样我们就可以在网页上直接在线查看测试报告,甚至可以用一个邮件服务器来实现测试失败时的提醒。 608 | 609 | ## 测试运行器管理器 610 | 在当前的系统中,我们必须手动运行`test_runner.py`文件来启动测试运行器。你可以创建一个测试运行器管理器进程,通过这个进程来管理查看所有运行器上的负载和来自分配器的请求,对运行器的数量进行相应的调整。这个进程会接受所有的测试任务,根据任务启动测试运行器,并在任务少的时候减少运行器的实例。 611 | 612 | 遵循这些建议,你可以使这个简单的CI系统更加健壮并且容错率更高,并且具有与其他系统(比如一个网页版的报告查看器)集成的能力。 613 | 614 | 如果你希望了解现在的持续集成系统可以实现到什么样的灵活性,我建议你去看看[Jenkins]() ,这是一个用Java编写的非常强大的开源CI系统。它提供了一个基本的CI系统,同时也允许使用插件进行扩展。你可以[通过GitHub]()访问其源代码。另一个推荐的项目是[Travis CI]() ,它是用Ruby编写的,其源代码也可以[通过GitHub]()得到。 615 | 616 | 这是了解CI系统如何工作以及如何自己构建CI系统的尝试。现在你应该对制作一个可靠的分布式系统所需的内容有了更深入的了解,希望你可以利用这些知识开发更复杂的解决方案。 -------------------------------------------------------------------------------- /术语库/computer_general.csv: -------------------------------------------------------------------------------- 1 | en,zh 2 | Atari,雅达利 3 | boost up to 6x,性能提升多达6倍 4 | Bureau of Ordnance Computation Project,军需计算专案局 5 | Juraszek,游拉舍克 6 | often in many different countries,经常在许多不同国家 7 | """unless specified as below"" and ""unless specified below""",=unless otherwise specified below 除非下文另有规定 8 | a heterogeneous computing environment,异构计算环境 9 | a profiling system,特征描述系统 10 | Add to Favourites,加入收藏夹 11 | adequate performance,卓越性能 12 | authenticate,鉴别 13 | Authorization Code Status,授权编号状态 14 | 图文混排,Image-Text wrapping 15 | background,背景 16 | bells & whistles,额外特性 17 | bezel,挡板条 18 | binary,二进制码 19 | buffer,缓冲区 20 | build version,编译版本 21 | business professionals,商业专业人员/商务专业人员 22 | Cabbage Patch,"“包心菜娃娃,菜斑玩偶""" 23 | cassette unit,盒式磁带机 24 | certificate's issuer digest number,认证发行者摘号 25 | clear screen retention straps,固定屏幕的透明带 26 | Cleo,蔻丽电视广告奖 27 | communication exception,通信异常 28 | Compagnie de Montres Longines Francillon S. A,Longines Watches Company 29 | Configuration,設定/配置 30 | data streaming,数据流 31 | download,下载 32 | DRC,灾难复原中心 33 | Edsgar Dijkstra,埃德斯格·迪克斯特拉 34 | elite competition,精英赛/顶级赛事 35 | encryption,加密,编密码,加密术 36 | enhancement application,增补程序 37 | firm handshake of trust,以诚相待 38 | flagship product,王牌产品 39 | flash,快闪式存储器 40 | For duration,持续一段时间 41 | Fragmentation,分工/细化 42 | robot exclusion headers,网络爬虫排除协议的头文件 43 | set correlation,定制关联 44 | shipping key,固定卡;定位片;定位卡;防震卡等 45 | step-by-step guides,步进式向导 46 | tab items and skins,选项卡的内容和皮肤 47 | tab-delimited,定位分隔的 48 | Thank you. Your request has been sent. (in Cantonese),Spoken and Written Cantonese 49 | Throughput,传输率 50 | thumbnail image,拇指图 / 缩略图 51 | time to insight,洞悉时间/辨明某种情况真正性质的时间 52 | to six decimal places,到小数点后六位 53 | top site,顶级网站 54 | troubleshooting,故障排除 55 | unmatched customer support,无与伦比的客户支持 56 | Vector-based,基于矢量 57 | WAN Link,广域网链接 58 | website outage,网站停运 59 | wrong recursive to date format,日期格式递归错误 60 | WWID,全球识别号 61 | x86 instruction set,x86指令集 62 | 文档的排版,documentary typesetting 63 | Atari,雅达利 64 | boost up to 6x,性能提升多达6倍 65 | Bureau of Ordnance Computation Project,军需计算专案局 66 | Juraszek,游拉舍克 67 | often in many different countries,经常在许多不同国家 68 | """unless specified as below"" and ""unless specified below""",=unless otherwise specified below 除非下文另有规定 69 | a heterogeneous computing environment,异构计算环境 70 | a profiling system,特征描述系统 71 | Add to Favourites,加入收藏夹 72 | adequate performance,卓越性能 73 | authenticate,鉴别 74 | Authorization Code Status,授权编号状态 75 | 图文混排,Image-Text wrapping 76 | background,背景 77 | bells & whistles,额外特性 78 | bezel,挡板条 79 | ground-breaking,破天荒 80 | hatch,FYI 81 | Helmut Schreyer,施和睦 82 | binary,二进制码 83 | buffer,缓冲区 84 | build version,编译版本 85 | business professionals,商业专业人员/商务专业人员 86 | Cabbage Patch,"“包心菜娃娃,菜斑玩偶""" 87 | cassette unit,盒式磁带机 88 | certificate's issuer digest number,认证发行者摘号 89 | clear screen retention straps,固定屏幕的透明带 90 | Cleo,蔻丽电视广告奖 91 | communication exception,通信异常 92 | Compagnie de Montres Longines Francillon S. A,Longines Watches Company 93 | Configuration,設定/配置 94 | data streaming,数据流 95 | download,下载 96 | DRC,灾难复原中心 97 | Help Resources,求助资源 98 | iconify on away,离开时缩小为图标显示 99 | IDNEX (or idnex),一类互联网子域名 100 | Illiac Suite for String Quartet,<<伊利雅克組曲>>弦乐四重奏 101 | Initiate,启动 102 | Integrated Device Manufacturers (IDM),整合组件制造商;集成器件制造商 103 | Internet access,"互联网接入/ 網路連線/互連網連線, 網路, 存取(網際)網路/上網" 104 | intuitively,直观地 105 | Issue,分配 106 | Journal of Computer Calisthenics and Orthodontia,窦卜大夫的电脑健身操与整齿学报 107 | key uploading pin pad specifications,上载密匙的个人密码输入器规格 108 | Lejaren,乐杰尔恩 109 | local name,本地名称 110 | Lyons Teashop Company,里昂茶室电脑公司 111 | Mobile Performance Personified,个性化/人性化移动性能 112 | multithreading,"多线程, 多线程技术" 113 | Murray Hill,莫雷山 114 | National Institutes of Health Clinic Center,国家卫生研究院临床研究中心 115 | Edsgar Dijkstra,埃德斯格·迪克斯特拉 116 | elite competition,精英赛/顶级赛事 117 | encryption,加密,编密码,加密术 118 | enhancement application,增补程序 119 | firm handshake of trust,以诚相待 120 | flagship product,王牌产品 121 | flash,快闪式存储器 122 | For duration,持续一段时间 123 | Fragmentation,分工/细化 124 | navagation,导航 125 | online presence,访问量 126 | out-of-the-box applications,打开即用的应用程序 127 | Outgoing COM Port,輸出COM埠(繁)/输出COM端口(串口)(简) 128 | over,通过 129 | overt authentication feature/tamper evident construction,公开验证功能/防窃启显示设计 130 | photos slideshow,照片幻灯片演示 131 | pick up,接收 = receive 132 | Play it forward,再过一段时间看看(你将发现。。。) 133 | Polished Worksheets,Polished Worksheets 134 | powering products,(以...)武装产品 135 | premium business tools,优质商务管理工具 136 | provider factory,provider factory 类 137 | push the envelope,挑战极限 138 | PYCCKÈÉ,俄语 139 | rails,导轨 140 | Rate Code Filter,速率码滤波器 141 | rating loader,评级装载机 142 | retire,撤除 143 | -------------------------------------------------------------------------------- /术语库/computer_hardware.csv: -------------------------------------------------------------------------------- 1 | en,zh 2 | market,面向通用市场 3 | pre-tailoring,准备工作(过程) 4 | (module) blanks,"填充模块,假模块" 5 | a highly parallel problem,高度并行的问题 6 | a marketing presentation,销售/推介演示文稿 7 | apparent swap space,显态交换分区保留极限 8 | backplane,背板,底板 9 | balls of both agents,这些信号必须与坐落在系统总线上的两个晶体元件的适当的接触头相接 10 | beefy,强健的/稳健的,强大而稳健的(业务) 11 | Carry Over Recovery,存续资料恢复 12 | complement,补码 13 | concatenated volumes,连接卷 14 | Dock,坞站/扩展坞 15 | dockable windows,可停靠窗口 16 | endorser,背书器 17 | facility,功能 18 | full stripe,全条带 19 | funding of,提供资金/经费 20 | higher yielding transistors,成品率较高的晶体管 21 | independent temperature monitor,独立温度监测器 22 | insofar as,鉴于/由于 23 | it's much more subtle than that,情况并不十分明显 24 | Manufactured for,委托制造方;制造委托方;定制方 25 | mapped to,相当于 26 | Multitag and Extended Cap,多标签读取及扩展功能 27 | phising,网络钓鱼/網路釣魚 28 | plotter,绘图仪 29 | Power cap seal,电源(密)封盖 30 | preferred embodiment,最佳设计形式/首选实现方案/首选配置 31 | removable devices,卸除式裝置 (traditional)/可移动设备 (simplified) 32 | Scale,刻度 33 | searched for the minimum number,(在子部件/零件中)追求最低限度的有害物质含量 34 | Short of,由于没有 35 | streamline portable performance,改进便携工作环境的编辑性能 36 | terminating transistor,终接晶体管/端接晶体管 37 | The best example of a standard that we'd like to emulate with blades is around P,对于刀片服务器,PCI标准是我们所要仿效的最好例子. 38 | threading,线程通讯 39 | Tomcat memory realms,Tomcat Memory Realm 的身份验证模式 40 | touchable area,可触区/触感区 41 | touchy connector,难以使用的连接器 42 | Unified Back Plate,一体式背板 43 | USB devices,USB 裝置 (traditional)/USB 设备 (simplified) 44 | Vehicle dock,车用固定扩充座,车载底座 45 | which could permit cooling to about twice that power density.,(在芯片的背面注入液体)可使冷却功率密度(上限)翻倍 46 | market,面向通用市场 47 | pre-tailoring,准备工作(过程) 48 | (module) blanks,"填充模块,假模块" 49 | a highly parallel problem,高度并行的问题 50 | a marketing presentation,销售/推介演示文稿 51 | apparent swap space,显态交换分区保留极限 52 | backplane,背板,底板 53 | balls of both agents,这些信号必须与坐落在系统总线上的两个晶体元件的适当的接触头相接 54 | beefy,强健的/稳健的,强大而稳健的(业务) 55 | Carry Over Recovery,存续资料恢复 56 | complement,补码 57 | concatenated volumes,连接卷 58 | Dock,坞站/扩展坞 59 | dockable windows,可停靠窗口 60 | endorser,背书器 61 | facility,功能 62 | full stripe,全条带 63 | funding of,提供资金/经费 64 | higher yielding transistors,成品率较高的晶体管 65 | independent temperature monitor,独立温度监测器 66 | insofar as,鉴于/由于 67 | it's much more subtle than that,情况并不十分明显 68 | Manufactured for,委托制造方;制造委托方;定制方 69 | mapped to,相当于 70 | Multitag and Extended Cap,多标签读取及扩展功能 71 | phising,网络钓鱼/網路釣魚 72 | plotter,绘图仪 73 | Power cap seal,电源(密)封盖 74 | preferred embodiment,最佳设计形式/首选实现方案/首选配置 75 | removable devices,卸除式裝置 (traditional)/可移动设备 (simplified) 76 | Scale,刻度 77 | searched for the minimum number,(在子部件/零件中)追求最低限度的有害物质含量 78 | Short of,由于没有 79 | streamline portable performance,改进便携工作环境的编辑性能 80 | terminating transistor,终接晶体管/端接晶体管 81 | The best example of a standard that we'd like to emulate with blades is around P,对于刀片服务器,PCI标准是我们所要仿效的最好例子. 82 | threading,线程通讯 83 | Tomcat memory realms,Tomcat Memory Realm 的身份验证模式 84 | touchable area,可触区/触感区 85 | touchy connector,难以使用的连接器 86 | -------------------------------------------------------------------------------- /术语库/computer_software.csv: -------------------------------------------------------------------------------- 1 | en,zh 2 | be inline with,插入 3 | engine-neutral,与引擎无关的 4 | externalize,显示 5 | flush,刷新 6 | global processes and retention schedules,全球/全局流程和保留时间表 7 | iterating through,逐一重复执行 8 | Mapping,映射 9 | midlet,移动信息设备小程序 10 | stickers and magnets,不干胶和磁贴 11 | the scanning and inventory pieces are all COM-based,扫描和记录增补都是基于COM的 12 | turn loose on your database,应用到您的数据库上 13 | """slow time"" conversation",非实时会话 14 | a 4 x 3 aspect ratio,4x3 宽高比 15 | a bulk-mail house,承揽群发邮件业务的公司 16 | a keyboard-pattern projector,键盘图案显示激光器 17 | a notion of components,组件的概念 18 | a site license,团体许可证 19 | a steep learning curve 陡峭的学习曲线 ?,雇员们面临着艰巨的学习任务 20 | ability to take early discounts and improved price terms for e-payments,能改善电子付款的价格条件之即早降价的能力 21 | across,横向 22 | Addin,插件 23 | advocacy group,倡导组织 24 | aggregates over an hour or a day.,一個小時或一整天的綜合資訊 25 | ambiguity,模糊性/不确定性 26 | an IPSec VPN client,IPSec VPN客户机/客户程序 27 | Anchored Objects,锚定/固定物件 28 | approval routing,许可路由 29 | archiving old messages out of the primary backup set,把旧邮件归档到主备份集以外的位置 30 | artificial intelligence,人工智能 31 | "As is so often the case,",情况3往往是这样 32 | asynchrony,异步操作 33 | authoring content,创作内容 34 | authoring language,编辑语言,写作语言,编写语言 35 | Authorization Line,确认行 36 | Automatic case off/on,关闭/开启 自动大小写字体设置 37 | autowire feature,自动绑定功能 38 | Availability Date,到货日 39 | 代码域管理器,FYI 40 | Bank control key,银行控制代码 41 | Bank of America,美国银行 42 | KPIHierarchy,KPI层级 43 | KPISchedule,KPI进度表 44 | label placement,标记,注记 45 | Last Cycle Count Date,上次循环计数日期 46 | Last Facility,最后设备 47 | Last N Hours,最后n小时 48 | Last N Measurements,最后 n 次测量 49 | LastReactivatedBy,最新启用者 50 | LastUnitCompleted,最后单元完成 51 | LastUnitStart,最后单元开始 52 | lieutenants,副队长 53 | line management,部门管理人员/班子;生产线管理人员/班子 54 | line-item,一行项目 55 | liquidizing,清仓 56 | lock,关闭/封锁 57 | Machine Integrator,机器整合器 58 | Machine Portal,机器接入(引导)程序 59 | make this disclaimer,作出此免责声明 60 | map and normalize,列出对照及规范 61 | Mapped->映射???,映射 62 | maps,映射 63 | Marker Size,标记大小 64 | massaging,数据调理(精处理) 65 | Matamata Piako,马塔马塔-皮亚科 66 | Min FIFO,存货最短FIFO日期 67 | minor and major,中度 重大 68 | Mobility,移动软件 69 | Money Green,钞票绿 70 | Most often there are “human engineering” elements to these,最常见的情况是这些攻击融入了“人体工程学”要素 71 | move up the evolutionary ladder,推陈出新,再上一层楼 72 | multiscape,多视角 73 | name in file,文件中的名称 74 | navigates a narrow channel of expertise,所涵盖的技术面比较窄 75 | Next Cycle Count,下一次循环计数 76 | NO WARRANTIES FOR THE SOFTWARE.,软件无担保声明 77 | "No, not this time",否,暂时不 78 | numbered and unnumbered,定长,不定长 79 | nurturing,(...用法的)训练/培训/辅导 80 | OASIS Open Document Format for Office Applications (OpenDocument) TC,OASIS 办公应用程序开放文档标准 (OpenDocument) 技术委员会 81 | odd deals,零零散散的交易 82 | On Recess,休息/休假 83 | On Reserve,预留(量) 84 | one on top of another,依序把一项建构在另一项之上 85 | open ??? signed off attendance ????,开放的 / 有签字出勤 86 | open points,悬而未决的问题 87 | operational cost base,运行成本 88 | OprSequenceNo,操作序列号 89 | outside the application,在应用外执行转换 90 | Page 1 at 1 Front,第一页打印在第一张纸的正面 91 | parents,父類 92 | parse the XML in SOAP,把XML解析成SOAP或其他... 93 | parser/resolver,此解析器非彼解析器 94 | partial specialization,偏特化、部分特化 95 | partner,合作伙伴 96 | patch,补丁 97 | patch prioritization,补丁优选 98 | path,道路 99 | patient IDs,病人标识 100 | peculiarities,与众不同 101 | People Productivity Program,People Productivity/人力(資源)效能程式 102 | period-end accruals,期末帐项调整/期末财务结算 103 | persist,保存 104 | pipeline management,(业务)渠道管理 105 | Point,节点 106 | point of care,医护点 107 | pointing to AMP’d content beyond,指向采用AMP技术的内容页面 108 | battlecard,教战卡 109 | bean,程序节 110 | Points file,要点说明书 111 | port,移植版本 112 | portal,门户 113 | positive authentication,有效验证 114 | Print Scale,打印比例 115 | probation logs,缓刑记录 116 | profile level,在简档级别 117 | property bag,属性包 118 | proven,已證明的 119 | pseudocode,伪代码/伪码 120 | push-phase operation content,推动阶段运行内容 121 | Quantity In Active,可用库存(量) 122 | r aggressive growth engine,进取(……)的推动因素 123 | Best After,最佳滞后日 124 | Bin,纸盒 125 | bit-wise copy,按位拷贝 126 | boat,蒸发皿[盘]/船形器皿 127 | borough of Queens,皇后区 128 | Boundless in scope,这种想法,这种设想 无所不能 129 | breaking a sweat,不费吹灰之力 130 | Browser Only PC,仅安装浏览器的个人计算机 131 | random fluctuations in minute-by-minute indicators,每一分钟改变一次显示数据的指示器(显示数据的)随机波动 132 | be inline with,插入 133 | engine-neutral,与引擎无关的 134 | externalize,显示 135 | flush,刷新 136 | global processes and retention schedules,全球/全局流程和保留时间表 137 | iterating through,逐一重复执行 138 | Mapping,映射 139 | creating an adapter from ACORD to the proprietary format,创建一个从ACORD到专有格式的配接器 140 | bucketize,归类/分类 141 | business development director,业务开发主管 142 | By the end of the decade,到本十年末 143 | Cache,高速缓存 144 | capacity systems,高容量系统 145 | carry through,利用、通过…传达 146 | case assignment,项目/任务委派 147 | Catching disk hogs,谁在霸占磁盘? 148 | CGI,computer generated imagery 149 | channel business,渠道业务 150 | ChartSet,图表集 151 | midlet,移动信息设备小程序 152 | stickers and magnets,不干胶和磁贴 153 | the scanning and inventory pieces are all COM-based,扫描和记录增补都是基于COM的 154 | turn loose on your database,应用到您的数据库上 155 | """slow time"" conversation",非实时会话 156 | a 4 x 3 aspect ratio,4x3 宽高比 157 | a bulk-mail house,承揽群发邮件业务的公司 158 | a keyboard-pattern projector,键盘图案显示激光器 159 | a notion of components,组件的概念 160 | a site license,团体许可证 161 | a steep learning curve 陡峭的学习曲线 ?,雇员们面临着艰巨的学习任务 162 | ability to take early discounts and improved price terms for e-payments,能改善电子付款的价格条件之即早降价的能力 163 | across,横向 164 | Creve Coeur,克里夫库尔 165 | Cross Distribution,交叉分配 166 | cumbersome,麻烦 167 | custom and bound images,定制和绑定图像 168 | Custom B2B Applications,自定义的B2B应用程序/应用软件/应用系统 169 | Cycle Count Flag,循环计数标志 170 | Dashboard,仪表板,含指示转盘、分隔间,控制装置 171 | data-access smarts,数据访问智能 172 | DC Stat program,哥伦比亚特区统计计划(项目) 173 | checked exception,受控例外(或异常);查验例外(异常) 174 | circular dependencies,循环依赖 175 | clique,(最大完全)子图 176 | Cloud services,云服务 177 | codify,标准化,= standardize/normalize 178 | colour reduction,色彩深度缩减; 色彩减弱;减色 179 | combo box,组合框 180 | command train,指令串 181 | communication,通信/联系 182 | Grid Usage Processing,用电处理 183 | Harmonized Tariff,协调关税 184 | has been extended to Java-based rules engines,已扩充成基于Java的规则引擎 185 | Employee Valid Date,员工有效日期 186 | Addin,插件 187 | advocacy group,倡导组织 188 | aggregates over an hour or a day.,一個小時或一整天的綜合資訊 189 | ambiguity,模糊性/不确定性 190 | an IPSec VPN client,IPSec VPN客户机/客户程序 191 | dead band,死区 192 | Debit No.,借方编号 193 | Default Function,默认功能 194 | Default Over Receive %,默认值与接收量之比 195 | defeasable logic,容错逻辑 / 可取消逻辑 196 | defect,转向他处寻购 197 | Defines the date to filter against.,定义过滤用的日期 198 | Defines the filter to AND.,定义该过滤器为AND 199 | deserialize,解串 200 | destringify,解(散) 字串 201 | Determination Strategy Used,所用判断规则 202 | determines,认为/认定 203 | Deutsche Bahn Aktiengesellschaft,德国铁路股份公司 204 | digitalia,软件 205 | Discontinue On,停止日期 206 | dispatcher,分派器 207 | Communidad Valencian,科穆尼达 巴伦西亚 208 | Components are tags on steroids.,组件是增强型标记 209 | conditional report,附有条件的报告 210 | Contact Apriso,与Apriso联系 211 | Content Class,FYI 212 | contextual tab,上下文选项卡 213 | cookie function,cookie功能 214 | corporate paradigm,企业经营模式(理念) 215 | crash recovery,崩溃恢复 / 灾难恢复 216 | Create disc from disc image,从/根据磁盘镜像创建磁盘 217 | Heartbeat,律动测试 218 | Hierachy Archive,分级存档 219 | high level concepts,高层次概念 220 | Host System Reverse Location,主机系统反向寻址 221 | I AM UP TO MY NECK IN WORK,我忙得不可开交 222 | I'm making a beeline outta here,我径直逃走(走开/避开) 223 | IBM Integration and Application Infrastructure program,IBM Integration and Application Infrastructure(IBM 整合及應用基礎架構)程式 224 | identify...to,把...与...对应起来 225 | image import,图像导入 226 | improve the business-process discipline,业务流程规矩/规则 227 | in business,做生意/做买卖 228 | inbound queues,入站/接收队列 229 | inclusion,引入 230 | Indicator,指示符 231 | inline asm/inline assembly,内嵌汇编 232 | Re-explode,再分解 233 | "Reading hundred volumes of books, traveling thousand miles of roads.",读万卷书,行万里路 234 | Red Seas parting,红海裂谷 235 | refined version,完善版本 236 | reliable messaging,可靠传讯 237 | Repeat offenders,屡犯者 238 | replication features,复制特性 239 | required items,必需项目/所需项目;必需项/所需项 240 | reset,重置 241 | Reset,重置 242 | resident structure,常驻/驻留结构 243 | resistive bend sensors,电阻式弯曲传感器 244 | restrictive,FYR 245 | Retired,不再使用(的) 246 | Rework Time,返工时间 247 | RFID,无线射频识别 248 | enabling contact down the supply chain,实现供应链跟踪管理 249 | enterpriselike,企业级的 250 | EPC tag,产品电子代码标签 251 | Equal Spread,均等分布 252 | "ESPRIT, STP",工程软件生产用户界面,可视化软件开发 253 | estimated costs of carried over cases for the next year,结转到明年的案子的估计成本 254 | Event Handler,事件处理程序 255 | ex. No longer have laptop,例如,没有便携式电脑了 256 | Expected Start,预期开始时间 257 | expense purchase tax,将购买/采购税纳入开支/成本 258 | exploits and vulnerabilities,可被利用的地方和易受攻击的缺陷 259 | Explosion,分解 260 | extreme sports /X-game,极限运动 261 | 踢毽子用英语怎么说?,To kick the shuttlecock or to play shuttlecock 262 | factory class,工厂类 263 | factory promt,厂家提示 264 | feed rates,收件率/接收率 265 | Anchored Objects,锚定/固定物件 266 | approval routing,许可路由 267 | archiving old messages out of the primary backup set,把旧邮件归档到主备份集以外的位置 268 | artificial intelligence,人工智能 269 | "As is so often the case,",情况3往往是这样 270 | asynchrony,异步操作 271 | authoring content,创作内容 272 | authoring language,编辑语言,写作语言,编写语言 273 | Authorization Line,确认行 274 | Automatic case off/on,关闭/开启 自动大小写字体设置 275 | autowire feature,自动绑定功能 276 | Availability Date,到货日 277 | 代码域管理器,FYI 278 | Bank control key,银行控制代码 279 | Bank of America,美国银行 280 | disposition,配置 281 | Distribution Template,分发/分配模板 282 | District Court,(联邦)地方法院 283 | domain experience,专业经验, 284 | draw a box,画一个框 285 | Dual crossover,对偶交联;对偶联接;对偶交叉;交联;(通过缆线)对接等 286 | Dump,转储 287 | duplicating the original content in the proper order,按适当顺序重新组成原有内容 288 | Duration Remaining,剩余时间 289 | Dwarf-based,基于 Dwarf 的 290 | early adopters,洞烛先机而率先采用的客户 291 | Earned Hours,累积的带薪休假时间。 292 | ECO,Engineering Change Order 293 | Employee Facility,员工设施 294 | inline/inlined/inlining,内联 295 | insurance policies,保单 296 | integrate processes within your client’s business,把流程集成到客户的业务中 297 | Integration Composite Application Network,整合的组合式应用网络 298 | Intelligent bursting,智能分发 299 | Intuitive user interface,直观的用户界面 300 | invalidate entry,废止条目; 废止输入; 废除条目; 废除输入 301 | Inventory serial is picked,存货序列已被选用 302 | is deprecated,不推荐使用,已过时 303 | IS NOT FAULT TOLERANT,不能容錯 304 | is not what it might be,(the data quality)不象原来预期/设想的那样(好) 305 | IsCycled,已循环 306 | Issuing Bank,放款银行 307 | It was for a side project,那是用于非正式的项目 308 | IT-enabled,基于 IT 的服务 309 | Java Classes,java类 310 | Java profiling,Java评测/评析 311 | rich targets,诱人的 312 | FI,FI 313 | Field,字段 314 | filter,筛选器 315 | floating palette,浮动选项板/飘浮功能板 316 | for use by their employees in the field,供该公司与此领域相关的职工使用 317 | front-sided,正面 318 | full,全套/整套 319 | full time equivalents,全时制工作人员 320 | function with,可以和。。。一起使用 321 | functional box libraries,函数框库 322 | functional processes,功能流程/功能型流程 323 | fuzzy logic,模糊逻辑 324 | game theoretical advantage,博弈论优势 325 | Gavleborgs Lan,FYI 326 | genealogy,谱系 327 | keep everything ’just in case’,啥都留下 ..... 以防万一 328 | keep it with them,随身携带 329 | Keep Modeled On Entity's Data,保留建模实体的数据 330 | Keyed on,linked to 331 | KPIBenchmark,KPI基准指标 332 | rolling target periods,滚动目标周期 333 | rolls up,纳入,并入 334 | RotatingWorkSchedule,轮班工作计划 335 | Rotation Key,轮替关键 336 | routing dispositions,路由配备 337 | Row,行 338 | sales report by sub,下属的销售报告 339 | Sample Plan,抽样计划 340 | scalar,标量 341 | scriptable,可编指令码的/可编脚本的 342 | Serial Key,序列号 343 | Serial Number work in process,工单序号 344 | service (data),维护(数据,verb.) 345 | shift,转变 346 | Shorthand-Aided Rapid Keyboarding,速记辅助快速键盘输入 347 | sign methods and workspaces.,签写方法和工作区 348 | Sixth Pacific Rim International Conference on Artificial Intelligence,第六届泛太平洋人工智能国际会议 349 | Generalization,泛化 350 | Generating Images On-the-Fly with ASP.NET and GDI+,使用 asp.net 和 gdi+ 生成即时动态图像 351 | geographic dispersion of data from the source site,(可以将)源站点的数据分散到不同的地理区域 352 | getting way popular,越来越受欢迎 353 | GL account,general ledger account 354 | Gradient,背景色渐变 355 | graphical cues,图形提示 356 | so conform,符合/與。。。一致 357 | SOA meets the real world,SOA进入实际应用阶段 358 | Social Software,社交软件 359 | sort indicator,排序箭头符号 360 | Soul-t'ukpyolsi,首尔(汉城)特别市 361 | spyware,间谍软件 362 | Start Plastics Data,启动塑料业数据 363 | starting point,启始点 364 | Status Valid On,状态有效/生效日期 365 | stocks of cash,库存现金/现金储备 366 | story,文章 367 | stringify,字符串化 368 | style conflict resolution,样式冲突解决(方案) 369 | success,顺利/成功执行 370 | Success With Warnings.,成功发出警报 371 | SUK,SUK码 372 | tables,表格 373 | take the next step to on-demand intelligence,to=going to/towards 374 | taller-than-wide layout,纵向版面/竖式版面 375 | TCO,总体拥有成本 376 | team dynamics,团队活力 377 | Temp badge,临时卡 378 | tertiary,一年三次 379 | Tethering的表达,共享(移动电话的)网络资源 380 | that encourage hedging through higher stockholdings,减轻是否增加库存的疑虑以安然避险 381 | that the original applications were developed in,从开发原来应用程序所基于的特定平台 382 | the enabling technology base,能发挥效用的技术基础 383 | the perceived benefit of float built into the delays,迟延付款让人误认为会帶來好处 384 | the settings as applied to a 100-percent diagram,根据100%(比例的)图表所应用的设置 385 | this is communicated back from the RADIUS server to the switch,RADIUS服务器将这一情况通知交换机 386 | tile,图块 387 | Time Picker,时间选择器 388 | to explode,激增 389 | To Pick,选用(量) 390 | To Put,投入(量) 391 | To put this in financial terms,以/拿财务术语来说 392 | token,令牌 393 | top-down,自上向下的 394 | Total Processed,总加工量 395 | Total Run Effort,总运行尝试次数 396 | transient data packets,临时数据包 397 | transit,转换 398 | troubleshooting,故障排除 399 | TTRowVersionStamp,tt行版本时间戳 400 | turn this off and on,红外线数据传输 401 | U.S. federal rules of Civil Procedure effective 01-DEC-2006,2006年12月1日生效的《美国联邦民事诉讼规则》 402 | ultimate definition of insanity in this business,真是疯狂到了家 403 | unraveling,卸载 404 | UOM,计量单位 405 | Upstream Spectrum,上行频谱 406 | use XML as the semantic data standard for services and middleware,把XML用作服务的语义数据标准和中间件 407 | user rights,用户权限 408 | users on the business side,商业用户 409 | utilities,工具/实用程序 410 | Valle d'Arosta,瓦莱达奥斯塔大区 411 | Volo View Express 2,Volo View Express 2 412 | want to go from mess to less?,想弃繁从简吗? 413 | Waoroa,沃罗阿 414 | Watch Wizard,监视向导 415 | we just open a door to see the EJB,我们只是开启了一道沟通EJB之门 416 | Web Service Transaction Language,Web服务事务语言 417 | Web Site Translation,网站翻译 418 | website localization,网站本地化 419 | wet tissue,湿(的)组织 420 | whitespace character,空白字符(空白字元) 421 | WI For Std Operation,标准作业WI检索完毕 422 | Wip content Quantities,工单内容数量 423 | Wip order,工单 424 | Wireless Zero Config,无线零配置 \ 无线网络零配置 425 | Wizard,向导(程序) 426 | WLAN Autoconfig,无线局域网(WLAN)自动配置 427 | WONG WAI YEE,汪惠仪 428 | Work adds on,附加工作 429 | Work Period Break,工间休息 430 | Work with millions of items,处理数百万个项目 431 | Xtensions,扩展插件 432 | z-order,Z-顺序 433 | Ziutek,约瑟夫 434 | Zizzz Disney,瞌睡虫迪士尼 435 | \'open standards\' architecture,“开放标准”架构 436 | -------------------------------------------------------------------------------- /术语库/computer_system.csv: -------------------------------------------------------------------------------- 1 | en,zh 2 | control panel,控制面板 3 | Disk Cost,硬盘用度 4 | end-to-end chain,connection/link 5 | Flexible Authentication via Secure Tunneling,通过安全隧道的灵活验证 6 | G.Bis,ADSL2 7 | Often these changes will be automated or implicit,自动或隐式地 8 | overnight batch jobs,夜间(进行的分)批处理工作 9 | turn the tables on,扭转局面/转败为胜 10 | what-if scenarios,假设分析方法/方案 11 | wireless backhaul,无线回传 12 | "a ""Day Zero"" attack",初始攻击 13 | a branch facility,子网 14 | a collection of standards-based TCP enhancements,一组基于标准的TCP增强功能 15 | a meaningless exercise,一項無意義的舉動 16 | a named server,命名服务器 17 | a PBX overlay,(整个)PBX系统/网络 18 | "a total coverage of 19,000 feet",总覆盖半径达19000英尺 19 | activate new orders,着手工程施工或技术调试/着手实施订单工程 20 | add to be the sum of the original waveform,原本信号波形的总和 21 | addressing,涉及/涉足 22 | adopt a mobile relationship,建立一种在可移动服务方面的合作关系 23 | animal swing door,可任意出入的推拉门 24 | Any wonder why some executives prefer to pick on IT budgets?,难怪有些主管将注意力集中在IT预算上 25 | approach vendor selection with very fine granularity,仔细选择供货商 26 | around,在...周围 27 | as needs dictate,这些都是(具体)需要/求决定的 28 | At a higher level,进而言之 29 | at least two acceptable access point signals,可接收来自至少两个接入点的信号 30 | back-off delays,等待延迟 31 | "Bad, bad server. No donut for you.",坏,坏,坏服务器。不给你多纳圈吃。 32 | bandwidth rate,带宽速率 33 | Bluetooth profiles,蓝牙应用规范 34 | bog down,琵....篊ㄓ 35 | bridge,网桥/桥接器 36 | "bring spinning, meaning random-access, advantages to archive and backup",(全息技术)能够为存档和备份带来旋转即随机存取的优点 37 | broken RSS-feed,中断/断开 38 | business case,商业理由 39 | business enabler,业务促成因素/业务推动力(量) 40 | but at this point it's every vendor for itself.,"到目前为止, 各厂商都是各自为政" 41 | but don't get trapped by proprietary systems.,"不过,千万不要被这些专有系统捆住了手脚" 42 | cabinet device numbers,"i personally lean to 压缩工具数量, relative disk..." 43 | captured,检测 44 | career-limiting move,自限前途的举动 45 | cell sleeping,(蜂窝)小区休眠 46 | Centre of Excellence,高级研发中心 47 | chargeback,跟踪 48 | colocate,并置/共置 49 | computer problem,计算机问题 50 | "configuration, provisioning",配置,自动配置 51 | connection pins,连接插脚 52 | control packet,控制分组 53 | Cookie,Cookie 54 | copper-fed,通过铜线传输的/铜线连接的 55 | cross-pollination,相互承认 56 | crossover cable,交叉电缆/交叉线/交越电缆 57 | customer-requested solution of interest,客戶要求的相关解決方案 58 | data extraction without detection,盗取数据 59 | desktop disruption,桌面系统崩溃 60 | develop trending information,收集/制作 61 | dial-up connection,拨号连接/拨接/撥接/撥接上網/撥號連線 62 | discrete multitone line coding,离散多音线路编码 63 | distribution routine,分发例(行)程(序) 64 | don’t ever ask for another one,就不要寻求(采用)另一个 65 | each copper subscriber pair,每对用户铜线 66 | embarrassingly parallel applications,~{!82;RWF=PP!9~}(embarrassingly parallel) ~{5DS&SC3LJ=~} 67 | empirical evidence,经验证据 68 | "engineering product realization, data services",工程产品实现、数据服务 69 | enlist,纳入/涵盖 70 | Entry Profile,输入配置文件 71 | even if Web sites may take longer to render,即使网站速度可能会变慢 72 | exit profile,皌竚ゅン 73 | explicit,显式控制 74 | explicit processors,显式处理器 75 | extends the benefits of ××× to×××,将以太网的益处扩展到XXX 76 | fabric switches,光纤交换机 77 | find a silver lining in the security cloud,...(终于)在安全技术的迷云/雾中看到了一线希望 78 | fluid-power,流体动力 79 | flush image cache,刷新图片缓存 80 | for managing backups from multiple PCs,管理多PC备份 81 | found a home,找到了立足之地 82 | this coincides suspiciously with,这似乎与...吻合 83 | "this has very much been a bootstrap operation,",這就一直都是可以自行運作的項目 84 | thumb memory,U盘/优盘 85 | Timer algorithm,计时器算法 86 | To be unique to ...,为。。。所独有 87 | To only run three ports off a 48-port switch is overkill,对于一个拥有48个端口的交换机,如果你只开通其中三个端口,无疑是一种杀鸡用宰牛刀的行为 88 | tolerance,容許 89 | top-line,年销售额 90 | total addressable market,潜在市场总额/有开发价值的市场总额 91 | Trigger Questions,探究式問題 92 | turnaround time,周期 93 | uplink-downlink ratio,上行(链路)速度与下行(链路)速度之比 94 | VLAN declarations,VLAN声明 95 | wants to be clear--,想让大众知道/想明白无误地告诉大众 96 | where calls reach them,在哪里接电话 97 | wiring closet,布线/配线室 98 | work around ...,暂时避开/绕过(那些问题) 99 | control panel,控制面板 100 | Disk Cost,硬盘用度 101 | end-to-end chain,connection/link 102 | Flexible Authentication via Secure Tunneling,通过安全隧道的灵活验证 103 | G.Bis,ADSL2 104 | Often these changes will be automated or implicit,自动或隐式地 105 | overnight batch jobs,夜间(进行的分)批处理工作 106 | turn the tables on,扭转局面/转败为胜 107 | what-if scenarios,假设分析方法/方案 108 | wireless backhaul,无线回传 109 | "a ""Day Zero"" attack",初始攻击 110 | a branch facility,子网 111 | a collection of standards-based TCP enhancements,一组基于标准的TCP增强功能 112 | a meaningless exercise,一項無意義的舉動 113 | a named server,命名服务器 114 | a PBX overlay,(整个)PBX系统/网络 115 | "a total coverage of 19,000 feet",总覆盖半径达19000英尺 116 | activate new orders,着手工程施工或技术调试/着手实施订单工程 117 | add to be the sum of the original waveform,原本信号波形的总和 118 | addressing,涉及/涉足 119 | adopt a mobile relationship,建立一种在可移动服务方面的合作关系 120 | animal swing door,可任意出入的推拉门 121 | Any wonder why some executives prefer to pick on IT budgets?,难怪有些主管将注意力集中在IT预算上 122 | approach vendor selection with very fine granularity,仔细选择供货商 123 | around,在...周围 124 | as needs dictate,这些都是(具体)需要/求决定的 125 | At a higher level,进而言之 126 | at least two acceptable access point signals,可接收来自至少两个接入点的信号 127 | back-off delays,等待延迟 128 | "Bad, bad server. No donut for you.",坏,坏,坏服务器。不给你多纳圈吃。 129 | bandwidth rate,带宽速率 130 | Bluetooth profiles,蓝牙应用规范 131 | bog down,琵....篊ㄓ 132 | bridge,网桥/桥接器 133 | "bring spinning, meaning random-access, advantages to archive and backup",(全息技术)能够为存档和备份带来旋转即随机存取的优点 134 | broken RSS-feed,中断/断开 135 | business case,商业理由 136 | business enabler,业务促成因素/业务推动力(量) 137 | but at this point it's every vendor for itself.,"到目前为止, 各厂商都是各自为政" 138 | but don't get trapped by proprietary systems.,"不过,千万不要被这些专有系统捆住了手脚" 139 | gateway,简体:网关; 繁體:閘道器 140 | GEO Site Failover,GEO网站故障转移 141 | geometry of signals,信号的构造(模式,模型,特征) 142 | gets around,gets around ... by = 以...解决问题 143 | give a rip about,关心 144 | "Global, Interoperable Broadband Wireless Networks: Extending WiMAX Technology to",全球交互式无线宽带网络:将WiMAX技术扩展到...... 145 | go out,發送出去 146 | greenfield sites,开发中的新区 147 | handshaking,(建立)同步(信息或数据)交换 148 | Have Disk,从磁盘安装 149 | have to assume,你必须意/认识到/你必须按。。。作打算 150 | having a better feel for,更好地了解 151 | heavyweight,庞大而复杂 152 | high level of sophistication,先进技术 153 | home gateway,家庭网关/家用信关 154 | hooks,勾子 155 | Horizons have narrowed for,前景不乐观 156 | host switch,主交换机 157 | hotspot,热点 158 | in a more commercial way,用一种更商业化的方式 159 | increase speed and reach,增加速率和覆盖范围 160 | inline-powered devices,联机受电设备/装置 161 | installation media,安装媒体 162 | IP peering,IP对等操作 163 | Is All Incoming E-mails,收到/发来的所有电子邮件 164 | It has become a victim of its own success,受其功效之累 165 | IT recycling dumpsters,废旧IT设备回收站 166 | jailed account,被限制更改目录的账户 167 | Kahn Consulting,卡恩咨询公司 168 | Kyodo Life Insurance,Kyobo人寿保险公司 169 | large office sites,大型集中办公场所 170 | lengthy processes,需要很长时间才能完成的流程 171 | Light Reload,轻量重载 轻量刷新 172 | line of site,视距 173 | locating a recipient's certificate,查找/查核收件方证书 174 | log,运行日志 175 | mainframe,主機 176 | manual scripts,人工编写的脚本 177 | map out,规划/制订 178 | matrix board,矩阵接线板 179 | mobile-tagging technology,手机标签技术 180 | monolithic storage arrays,整体式存储阵列 181 | more informative customer service,更丰富的客户服务 182 | moving forward with,与。。。一同前进(进步)/转译:一直在开发/推行(公开源代码的某些公司) 183 | nail,彻底了解/掌握/解决了...问题 184 | National Telephone Cooperative Association(NTCA),国家电话协作协会 185 | Navigation (RHS),右侧导航条 186 | negotiate,权衡/比较 187 | network retainability,网络的可保持性 188 | network-wide level statistics,全网(络级)统计 189 | No guidance for next year has been given.,没有提供明年的收益预期数字 190 | offering,提供 191 | on a block rate,整体费率/一口价费率/统一费率 192 | other attributes,其它特长 193 | output,结果 194 | payload,有效负荷 195 | peripheral,不明显 196 | personally identifiable,可辨认个人身份的 197 | PNG settings,PNG设置 198 | Policy,策略 199 | polybicarbonate,聚碳酸酯 200 | port map,端口映射 201 | power provisions,供电设备 202 | program,程序 203 | propositions marketing manager,市场策划经理 204 | prospecting activity reports,潜在客户开发活动报告 205 | provide the same bulk bandwidth,提供相同的海量带宽 206 | Psli3 (Computer system),Psli3 207 | PTT,电信公司 208 | "radiate out toward windows, but not beyond.",向外辐射(传播)至窗口,但不出窗口(窗框) 209 | radio frequency (RF) chain,射频链路 210 | radio relay towers,无线电转播台/站/塔 211 | raise a serious red flag for,给。。。发出一个危险(警告)信号 212 | ratified ...for its Basic Security Profile,作为最基本的安全特征 213 | rationalize,合理化 214 | reconverge,重新收敛 215 | reduced sign-on,减少登录次数 216 | Remote desktop and application delivery,远程桌面和应用(程序)分发 217 | represent sth as sth,"把...表达(表示,表现, 描绘)为..." 218 | RF fingerprinting,射频(RF)指纹识别技术 219 | "rip, mix, burn and share'",扒取、混成、烧碟和分享 220 | routing,路由选择 221 | rubber duck antenna,对讲机天线 222 | run ...for extended periods,让...超时运行... 223 | run naked,轻装上路/阵 224 | running VoIP calls off-net,经营(互连)网外语音IP电话业务 225 | Sandpiper Bay,Sandpiper Bay is a 27-hole Myrtle Beach golf course that offers a classic design 226 | Secure e-mail was once a solution looking for a problem.,安全电邮曾一度是没有要针对(解决)的问题的一种解决方案(办法)/安全电邮曾一度处于有庙无和尚的状态/安全电子邮件方案曾一度无用武之地 227 | security cameras,监控摄像机 228 | security progress,安全进程 229 | service policing,服务监管 230 | significant scale,规模大 231 | so far,迄今为止 232 | So much for the theory.,"好吧,谈理论到此为止." 233 | SOAP calls,SOAP调用 234 | software block,软件模块 235 | software hooks,软件挂钩 236 | spoofed address,假地址/盗用别人的地址 237 | spreadsheet calculations,报表计算 238 | spur-of-the-moment,"一时冲动的, 立即的, 不加思索的" 239 | step up to,升级/换代至 240 | strong mutual authentication,强相互验证 241 | suffer with,苦于 242 | supplied as either server software or an appliance,作为服务器软件或硬件装置提供的 243 | tab,标签、标签页或选项卡 244 | take their heads out of the sand,正视现实 245 | telecom groups,众多电信(集团)公司 246 | telephone handset,电话机 247 | terminal sessions to an AS/400,与AS/400(小型机)的终端对话/会话 248 | test mobiles,测试手机 249 | that,ê 250 | that ties in the remaining Ethernet nodes,连接其余Ethernet节点的 251 | That's smart.,那才是明智的做法 252 | the band,带子 253 | The language is purposefully kept vague to account for,因此其语言都有目的地保持宽泛,以便涵盖... 254 | The last remaining old-style IT mechanical hold outs;,现世(“硕果”)仅存的老式机械IT老顽固 255 | the scanty cost/capacity data,大容量下的低成本(费用)or 容量大费用低 256 | the technological push for Voice over IP [VoIP],VoIP技術的推進(動) 257 | they are just symptoms of something grander,它们只是冰山一角 258 | thin,(分布/布网)稀疏的 259 | -------------------------------------------------------------------------------- /简易web服务器 A simple web server/http-cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/简易web服务器 A simple web server/http-cycle.png -------------------------------------------------------------------------------- /简易web服务器 A simple web server/http-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/简易web服务器 A simple web server/http-request.png -------------------------------------------------------------------------------- /简易web服务器 A simple web server/http-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HT524/500LineorLess_CN/d852dc120c6e97ab89fff1fd9ea956c563eafb65/简易web服务器 A simple web server/http-response.png -------------------------------------------------------------------------------- /简易web服务器 A simple web server/简易web服务器.md: -------------------------------------------------------------------------------- 1 | [Greg Wilson](https://twitter.com/gvwilson)是 Software Carpentry, 一个科学家和工程师的计算技巧速成班,的创始人。他在工业界和学术界工作了三十余年,是好几本计算相关图书的作者或编辑,包括了 2008 年 Jolt 图书奖得主 *Beautiful Code* 和 *开源软件架构* 的前两卷。1993 年,Greg 获得了爱丁堡大学的计算机博士学位。 2 | 3 | 4 | 5 | ## 简介 6 | 7 | 在过去的二十多年里,网络改变了社会的各个方面,但它的核心却改动不多。大多数系统仍然遵循着 Tim Berners-Lee 在 25 年前所制定的规则。尤其是,大多数 Web 服务器仍旧以相同的方式处理着相同的数据,一如既往。 8 | 9 | 本章节将探讨他们如何实现。与此同时,本章节还将探讨开发者如何创建增加新特性而不需要重写的软件系统。 10 | 11 | 12 | ## 背景 13 | 14 | 几乎所有的网络程序都运行在一类叫做 互联网协议(IP)的通信标准上。这类协议中,我们涉及的是传输控制协议(TCP/IP),该协议使得计算机间通信类似于读写文件。 15 | 16 | 17 | 程序通过套接字使用 IP 协议进行通信。每个套接字是点对点通信信道的一端,正如电话机是一次电话通信的一端。一个套接字包含着一个 IP 地址,该地址确定了一台确定的机器和该机器上的一个端口号。IP 地址包含了四个八位数字,比如 `174.136.14.108`;域名系统将这些数字与字符相匹配,比如 `aosabook.org`,以便于记忆。 18 | 19 | 20 | 端口号码是 0 - 65535 之间的一个随机数,唯一确定了主机上的套接字。(如果说 IP 地址像一家公司的电话号码,那么端口号就像是分机号。)端口 0 - 1023 预留给操作系统使用;任何人都可以使用剩下的端口。 21 | 22 | 超文本传输协议(HTTP)描述了程序通过 IP 协议交换数据的一种方法。HTTP 协议刻意设计得简单: 客户端通过套接字发送一个请求,指定请求的东西,服务器在响应中返回一些数据(如下图)。该数据或许复制自硬盘上的文件,或许由程序动态生成,或是二者的混合。 23 | 24 | ![The HTTP Cycle](./http-cycle.png) 25 | 26 | 关于 HTTP 请求,最重要的地方在于,它仅由文本组成。任何有意愿的程序都可以对其进行创建或解析。不过,为了被正确地解析,文本中必须包含下图所展示的部分。 27 | 28 | ![An HTTP Request](./http-request.png) 29 | 30 | (注: 'sp':空格, 'cr lf':换行) 31 | HTTP 方法大多是 GET(请求信息)或者 POST(提交表单或上传文件)。统一资源定位器(URL)确定了客户端所请求的文件路径,一般位于硬盘上,比如 `/research/experiments.html`, 但是(接下来才是关键),如何处理完全取决于服务器。HTTP 版本一般是 "HTTP/1.0" 或 "HTTP/1.1" ; 二者之间的差异对我们来说并不重要。 32 | 33 | HTTP 首部(Headers)是一组键值对,如同下面这三行: 34 | 35 | ``` 36 | Accept: text/html 37 | Accept-Language: en, fr 38 | If-Modified-Since: 16-May-2005 39 | ``` 40 | 41 | 不同于哈希表中的键,HTTP 首部中,键可以出现任意多次。这将允许请求做一些事,例如指定愿意接收多种类型的内容。 42 | 43 | 最后,请求的主体是与请求关联的任何数据。这个应用于通过表单提交数据,上传文件等。首部的末尾和主体的开头之间必须由一个空行,以声明首部的结束。 44 | 45 | 首部中, `Content-Lenght` 告诉服务器在请求主体中有多少字节需要被读取。 46 | 47 | HTTP 响应的格式与 HTTP 请求类似: 48 | 49 | ![An HTTP Response](./http-response.png) 50 | 51 | 版本号,首部,主体有着相同的格式和意义。状态码是一个数字,用来指示在处理请求时所发生的事情: 200 意味着 "一切工作正常",404 意味着 "没有找到",其他状态码也分别有着各自的含义。 状态词以易读的形式重复着上述信息,比如 "一切正常" 或是 "没有找到"。 52 | 53 | 本节中,我们只需要了解关于 HTTP 的两件事情。 54 | 55 | 第一,HTTP 是无状态的: 每个请求自行处理,服务器在两个请求之间不会记住任何东西。如果应用想要跟踪一些信息,比如用户的身份,它必须自己实现。 56 | 57 | 实现的方法通常使用 cookie, 这是服务器发送到客户端的短字符串,之后由客户端返回给服务器。当用户执行一些函数,需要在多个请求之间保存状态时,服务器会创建一个新的 cookie,将它存储在数据库中,然后发送给浏览器。每次浏览器返回 cookie,服务器通过 cookie 寻找关于用户行为的信息。 58 | 59 | 我们需要了解的第二点是,可以填充参数以提供更多的信息。比如说,如果我们使用搜索引擎,我们需要指定关键词。我们可以将这些附加到 URL 路径中,但更应该是在 URL 中附加参数。我们在 URL 后附加 '?' ,之后是以 '&' 分隔的键值对('key=value')。比如说,URL `http://www.google.ca?q=Python` 要求谷歌查询关于 Python 的页面: 键是字母 'q',值是 'Python'。长一点的查询 `http://www.google.ca/search?q=Python&client=Firefox`,告诉谷歌我们在使用 Firefox,诸如此类。我们可以传输任何参数,不过,哪些参数需要注意,如何解释这些参数,完全取决于网站上运行的程序。 60 | 61 | 当然,如果 '?' 和 '&' 用作特殊字符,必然有方法加以避免,正如必须有方法将一个双引号字符放置在由双引号分隔的字符串内。URL 编码标准使用 '%' 后跟两位代码表示特殊字符,并使用 '+' 字符替代空格。因此,我们使用 URL `http://www.google.ca/search?q=grade+%3D+A%2B` 在谷歌中搜索 "grade = A+"(注意空格)。 62 | 63 | 打开套接字,构建 HTTP 请求,解析响应极其乏味,因此大多数用户使用库来做大部分工作。Python 附带了一个这样的库,叫做 `urllib2`(因为它是之前的库 `urllib` 的代替者),但是它暴露了许多大多数用户不关心的东西。相比于 `urllib2`,`Requests` 库是一个更加易于使用的选择。接下来是一个例子,使用 Requests 下载来自 AOSA book 站点的一个页面。 64 | 65 | ```python 66 | import requests 67 | response = requests.get('http://aosabook.org/en/500L/web-server/testpage.html') 68 | print 'status code:', response.status_code 69 | print 'content length:', response.headers['content-length'] 70 | print response.text 71 | ``` 72 | 73 | ``` 74 | status code: 200 75 | content length: 61 76 | 77 | 78 |

Test page.

79 | 80 | 81 | ``` 82 | 83 | `requests.get` 向服务器发送一个 HTTP GET 请求,返回一个包含响应的对象。该对象的 `status_code` 是响应的状态码;它的 `content_length` 是响应数据的字节数; `text` 是真正的数据(在这个例子中,是一个 HTML 页面)。 84 | 85 | ## Hello, Web 86 | 87 | 现在,我们已经为编写我们第一个简单的 Web 服务器做好了准备。基本思想很简单: 88 | 89 | 1. 等待用户连接我们的站点并发送一个 HTTP 请求; 90 | 2. 解析请求; 91 | 3. 计算出它所请求的; 92 | 4. 获取数据(或动态生成); 93 | 5. 格式化数据为 HTML; 94 | 6. 返回数据。 95 | 96 | 步骤 1, 2, 6 都是从一个应用程序到另一个,Python 标准库有一个 'BaseHTTPServer' 模块,为我们实现这部分。我们只需要关心步骤 3 - 5,这也是我们在下面的小程序中所做的。 97 | 98 | ``` 99 | import BaseHTTPServer 100 | class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 101 | '''Handle HTTP requests by returning a fixed 'page'.''' 102 | # Page to send back. 103 | Page = '''\ 104 | 105 | 106 |

Hello, web!

107 | 108 | 109 | ''' 110 | # Handle a GET request. 111 | def do_GET(self): 112 | self.send_response(200) 113 | self.send_header("Content-Type", "text/html") 114 | self.send_header("Content-Length", str(len(self.Page))) 115 | self.end_headers() 116 | self.wfile.write(self.Page) 117 | #---------------------------------------------------------------------- 118 | if __name__ == '__main__': 119 | serverAddress = ('', 8080) 120 | server = BaseHTTPServer.HTTPServer(serverAddress, RequestHandler) 121 | server.serve_forever() 122 | ``` 123 | 124 | 库里面的 `BaseHTTPRequestHandler` 类负责解析传进来的 HTTP 请求,并判断请求包含的方法。如果方法是 GET, 类将调用 `do_GET` 方法。我们的类 `RequestHandler` 重写了该方法以动态生成一个简单的页面: 文本页面存储在类级别变量中,我们将在发送给客户端 200 响应码,首部 `Content-Type` 字段以告诉客户端将返回的数据解析为 HTML,页面长度之后发送它。(`end_headers` 方法调用 插入空行以分隔首部和页面本身。) 125 | 126 | 然而 `RequestHandler` 并非故事的所有: 我们仍需要最后的三行来真正启动服务器。第一行以一个元组定义了服务器地址: 空字符串表示 "在当前主机上运行", 8080 标识了端口。接下来我们以地址和我们的请求处理类名作为参数创建了 `BaseHTTPServer.HTTPServer` 的一个实例,然后要求它一直运行(这意味着它将一直运行直至我们使用 'Ctrl - C' 杀掉它)。 127 | 128 | 如果我们在命令行中运行这个程序,它将不会显示任何东西: 129 | 130 | ``` 131 | $ python server.py 132 | ``` 133 | 134 | 如果我们在浏览器中访问 `http://localhost:8080`, 我们将在浏览器中看到: 135 | 136 | ``` 137 | Hello, web! 138 | ``` 139 | 140 | 同时在 shell 中: 141 | 142 | ``` 143 | 127.0.0.1 - - [24/Feb/2014 10:26:28] "GET / HTTP/1.1" 200 - 144 | 127.0.0.1 - - [24/Feb/2014 10:26:28] "GET /favicon.ico HTTP/1.1" 200 - 145 | ``` 146 | 147 | 第一行很简单: 因为我们没有要求一个特定的文件,浏览器便请求 '/'(任何正常工作服务器的根目录)。第二行出现是因为浏览器自动发送第二个请求,请求一个叫做 '/favicon.ico' 的图像文件,如果存在,将在地址栏显示为一个图标。 148 | 149 | 150 | ## 展示一些值 151 | 152 | 让我们修改我们的 Web 服务器以展示一些包含在 HTTP 请求中的值。(在调试时,我们会经常这样做,所以不妨先做一些练习。)为了保持代码整洁,我们将分别创建网页生成和发送。 153 | 154 | 155 | ```python 156 | class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 157 | # ...page template... 158 | def do_GET(self): 159 | page = self.create_page() 160 | self.send_page(page) 161 | def create_page(self): 162 | # ...fill in... 163 | def send_page(self, page): 164 | # ...fill in... 165 | ``` 166 | 167 | `send_page` 比之前的多很多。 168 | 169 | ```python 170 | def send_page(self, page): 171 | self.send_response(200) 172 | self.send_header("Content-type", "text/html") 173 | self.send_header("Content-Length", str(len(page))) 174 | self.end_headers() 175 | self.wfile.write(page) 176 | ``` 177 | 178 | 我们想要展示的页面的模板只是一个字符串,包含着一个有一些占位符的表格。 179 | 180 | ```python 181 | Page = '''\ 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 |
Header Value
Date and time {date_time}
Client host {client_host}
Client port {client_port}s
Command {command}
Path {path}
192 | 193 | 194 | ''' 195 | ``` 196 | 197 | 填充表格的方法如下: 198 | 199 | ```python 200 | def create_page(self): 201 | values = { 202 | 'date_time' : self.date_time_string(), 203 | 'client_host' : self.client_address[0], 204 | 'client_port' : self.client_address[1], 205 | 'command' : self.command, 206 | 'path' : self.path 207 | } 208 | page = self.Page.format(**values) 209 | return page 210 | ``` 211 | 212 | 该程序的主体并没有改变:正如之前,它以地址和请求处理程序作为参数,创建了一个 `HTTPServer` 类的实例,然后一直处理请求。如果我们运行它,然后用浏览器发送一个请求给 `http://localhost:8000/something.html` ,我们将得到: 213 | 214 | ``` 215 | Date and time Mon, 24 Feb 2014 17:17:12 GMT 216 | Client host 127.0.0.1 217 | Client port 54548 218 | Command GET 219 | Path /something.html 220 | ``` 221 | 222 | 注意到,我们没有得到一个 404 错误,即使 `something.html` 页面并不存在。这是因为 Web 服务器只是一个程序,当它收到请求时,会做它所需要的任何事情: 返回之前请求提到的文件,提供一个随机选取的维基百科页面,或者我们编程时让它做的任何事情。 223 | 224 | ## 提供静态页面 225 | 226 | 显然,接下来的步骤是提供静态文件,取代动态生成。我们将重写 `do_GET`。 227 | 228 | ```python 229 | def do_GET(self): 230 | try: 231 | # Figure out what exactly is being requested. 232 | full_path = os.getcwd() + self.path 233 | # It doesn't exist... 234 | if not os.path.exists(full_path): 235 | raise ServerException("'{0}' not found".format(self.path)) 236 | # ...it's a file... 237 | elif os.path.isfile(full_path): 238 | self.handle_file(full_path) 239 | # ...it's something we don't handle. 240 | else: 241 | raise ServerException("Unknown object '{0}'".format(self.path)) 242 | # Handle errors. 243 | except Exception as msg: 244 | self.handle_error(msg) 245 | ``` 246 | 247 | 上述方法假设允许程序使用所在路径(就是使用 `os.getcwd` 所得到的)下的任意文件提供服务。它会结合 URL 提供的路径(总是以 '/' 开始,`BaseHTTPServer` 会自动将它放入 `self.path`),以获取用户想要的文件的路径。如果文件不存在,或者路径并不指向文件,上述方法将通过获取并抛出异常来报告错误。另一方面,如果路径匹配到文件,`do_GET` 方法将调用辅助方法 `handle_file` 来读取并返回内容。辅助方法仅读取文件,然后调用 `send_content` 将文件内容返回给客户端: 248 | 249 | ```python 250 | def handle_file(self, full_path): 251 | try: 252 | with open(full_path, 'rb') as reader: 253 | content = reader.read() 254 | self.send_content(content) 255 | except IOError as msg: 256 | msg = "'{0}' cannot be read: {1}".format(self.path, msg) 257 | self.handle_error(msg) 258 | ``` 259 | 260 | 请注意,我们以二进制方式打开文件—由 'rb' 中 'b' 标识,这样 Python 不会改变看起来像 Windows 行结束的字节序列。同时,请注意,在使用文件提供服务时,将整个文件读入内存在真实生活中并不合适,视频文件大小可能是好几G。处理上述情况已经超出了本章的范围。我们接下来编写错误处理方法和错误处理页面模板来结束本节。 261 | 262 | ```python 263 | Error_Page = """\ 264 | 265 | 266 |

Error accessing {path}

267 |

{msg}

268 | 269 | 270 | """ 271 | def handle_error(self, msg): 272 | content = self.Error_Page.format(path=self.path, msg=msg) 273 | self.send_content(content) 274 | ``` 275 | 276 | 如果我们不仔细观察,程序似乎正常运行。问题在于它总是返回 200 状态码,即使所请求的页面并不存在。是的,返回的页面包含着错误信息,但因为浏览器读不懂英文,它并不知道请求实际失败了。为了使错误明确,我们需要修改 `handle_error` 和 `send_content` 如下: 277 | 278 | ```python 279 | # Handle unknown objects. 280 | def handle_error(self, msg): 281 | content = self.Error_Page.format(path=self.path, msg=msg) 282 | self.send_content(content, 404) 283 | # Send actual content. 284 | def send_content(self, content, status=200): 285 | self.send_response(status) 286 | self.send_header("Content-type", "text/html") 287 | self.send_header("Content-Length", str(len(content))) 288 | self.end_headers() 289 | self.wfile.write(content) 290 | ``` 291 | 292 | 注意,当文件找不到时,我们并没有抛出 `ServerException` 异常,而是生成一个错误页面。`ServerException` 意味着服务器内部错误,即,我们弄错了。另一方面,当用户遇到错误时,此处即,请求了一个不存在的文件的 URL 时,由 `handle_error` 生成错误页面。 293 | 294 | ## 目录列表 295 | 296 | 下一步,我们将教会服务器,在 URL 中,路径代表目录而不是文件时,展示一个目录内容的列表。我们甚至可以更进一步,让程序在目录中寻找 `index.html` 文件来展示。不过在 `do_GET` 中构建这些方法或许是一个错误,因为生成的方法将会是很多 `if` 语句混杂在一起来控制特殊行为。正确的方案是退一步,解决一个更一般的问题:弄清楚如何处理一个 URL。 297 | 298 | ```python 299 | def do_GET(self): 300 | try: 301 | # Figure out what exactly is being requested. 302 | self.full_path = os.getcwd() + self.path 303 | # Figure out how to handle it. 304 | for case in self.Cases: 305 | handler = case() 306 | if handler.test(self): 307 | handler.act(self) 308 | break 309 | # Handle errors. 310 | except Exception as msg: 311 | self.handle_error(msg) 312 | ``` 313 | 314 | 第一步完全相同:弄清楚请求的完整路径。之后,代码看起来就不同了。这个版本循环遍历一组存储在列表中的情况,而不是一组内嵌的测试。每种情况都是一个有着两个方法的对象,`test` 告诉我们是否能够处理请求,`act`,实际上进行处理。一旦我们发现正确的情况,我们让它处理请求并跳出循环。这三个类重复之前的服务器的行为: 315 | 316 | ```python 317 | class case_no_file(object): 318 | '''File or directory does not exist.''' 319 | def test(self, handler): 320 | return not os.path.exists(handler.full_path) 321 | def act(self, handler): 322 | raise ServerException("'{0}' not found".format(handler.path)) 323 | class case_existing_file(object): 324 | '''File exists.''' 325 | def test(self, handler): 326 | return os.path.isfile(handler.full_path) 327 | def act(self, handler): 328 | handler.handle_file(handler.full_path) 329 | class case_always_fail(object): 330 | '''Base case if nothing else worked.''' 331 | def test(self, handler): 332 | return True 333 | def act(self, handler): 334 | raise ServerException("Unknown object '{0}'".format(handler.path)) 335 | ``` 336 | 337 | 这是我们在 `RequestHandler` 类开头,如何构建事件处理程序列表: 338 | 339 | ```python 340 | class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 341 | ''' 342 | If the requested path maps to a file, that file is served. 343 | If anything goes wrong, an error page is constructed. 344 | ''' 345 | Cases = [case_no_file(), 346 | case_existing_file(), 347 | case_always_fail()] 348 | ...everything else as before... 349 | ``` 350 | 351 | 现在,表面上我们的服务器更加复杂了,而不是简洁。文件从 74 行变成 99 行,并有了一个额外的,没有任何新功能的间接层。不过当我们回到本节最初提出的任务::教会服务器为一个目录请求,在 `index.html` 存在时返回 `index.html`, 不存在时返回目录内容列表,好处随之出现。前者的处理程序如下: 352 | 353 | ```python 354 | class case_directory_index_file(object): 355 | '''Serve index.html page for a directory.''' 356 | def index_path(self, handler): 357 | return os.path.join(handler.full_path, 'index.html') 358 | def test(self, handler): 359 | return os.path.isdir(handler.full_path) and \ 360 | os.path.isfile(self.index_path(handler)) 361 | def act(self, handler): 362 | handler.handle_file(self.index_path(handler)) 363 | ``` 364 | 365 | 接下来,辅助方法 `index_path` 构造 `index.html` 文件的路径;将它放进事件处理程序以防止主类 `RequestHandler` 的杂乱。`test` 检查路径是否是一个包含 `index.html` 页面的目录,`act` 要求主请求处理程序返回这个页面。`RequestHandler` 所需的唯一变化是将一个 `case_directory_index_file` 类加入我们的 `Cases` 列表: 366 | 367 | ```python 368 | Cases = [case_no_file(), 369 | case_existing_file(), 370 | case_directory_index_file(), 371 | case_always_fail()] 372 | ``` 373 | 374 | 要是目录不包含一个 `index.html` 页面呢?`test` 和之前的一个一致,可是,`act` 方法呢?它应该变成什么样? 375 | 376 | ```python 377 | class case_directory_no_index_file(object): 378 | '''Serve listing for a directory without an index.html page.''' 379 | def index_path(self, handler): 380 | return os.path.join(handler.full_path, 'index.html') 381 | def test(self, handler): 382 | return os.path.isdir(handler.full_path) and \ 383 | not os.path.isfile(self.index_path(handler)) 384 | def act(self, handler): 385 | ??? 386 | ``` 387 | 388 | 似乎我们把自己逼进了墙角。逻辑上讲,`act` 方法应该创建并返回目录列表,但我们现存的代码不允许那样::`RequestHandler.do_GET` 调用 `act` 方法,却不期望它返回一个值。让我们暂时为 `RequestHandler` 增加一个方法以生成目录列表,然后使用事件处理器的 `act` 方法调用它: 389 | 390 | ```python 391 | class case_directory_no_index_file(object): 392 | '''Serve listing for a directory without an index.html page.''' 393 | # ...index_path and test as above... 394 | def act(self, handler): 395 | handler.list_dir(handler.full_path) 396 | class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 397 | # ...all the other code... 398 | # How to display a directory listing. 399 | Listing_Page = '''\ 400 | 401 | 402 |
    403 | {0} 404 |
405 | 406 | 407 | ''' 408 | def list_dir(self, full_path): 409 | try: 410 | entries = os.listdir(full_path) 411 | bullets = ['
  • {0}
  • '.format(e) 412 | for e in entries if not e.startswith('.')] 413 | page = self.Listing_Page.format('\n'.join(bullets)) 414 | self.send_content(page) 415 | except OSError as msg: 416 | msg = "'{0}' cannot be listed: {1}".format(self.path, msg) 417 | self.handle_error(msg) 418 | ``` 419 | 420 | ## CGI 协议 421 | 422 | 理所当然,大多数人不想为了添加新的功能而编辑 web 服务器的源代码。为了将他们从编辑源码拯救出来,服务器一般都支持一种叫做公共网关接口(CGI)的机制,它为 web 服务器提供了一个标准的方式来运行外部程序,以响应请求。例如,假设我们想要服务器可以在一个 HTML 页面上展示本地时间,我们可以在一个只有几行代码的独立程序中实现: 423 | 424 | ```python 425 | from datetime import datetime 426 | print '''\ 427 | 428 | 429 |

    Generated {0}

    430 | 431 | '''.format(datetime.now()) 432 | ``` 433 | 434 | 为了让 web 服务器运行这个程序,我们添加了下面的事件处理器: 435 | 436 | ```python 437 | class case_cgi_file(object): 438 | '''Something runnable.''' 439 | def test(self, handler): 440 | return os.path.isfile(handler.full_path) and \ 441 | handler.full_path.endswith('.py') 442 | def act(self, handler): 443 | handler.run_cgi(handler.full_path) 444 | ``` 445 | 446 | `test` 很简单::文件路径是否以 `.py` 结尾?是的话,`RequestHandler` 将运行上面的独立程序。 447 | 448 | ```python 449 | def run_cgi(self, full_path): 450 | cmd = "python " + full_path 451 | child_stdin, child_stdout = os.popen2(cmd) 452 | child_stdin.close() 453 | data = child_stdout.read() 454 | child_stdout.close() 455 | self.send_content(data) 456 | ``` 457 | 458 | 这是非常不安全的: 如果有人知道了我们服务器上一个 Python 文件的路径,我们将不得不允许他运行该程序,而没有考虑,它有权限访问哪些数据,它是否包含一个死循环,或者二者之外。 459 | 460 | 扫清上述隐患,核心理念很简单: 461 | 462 | 1. 在一个子进程中运行该程序。 463 | 2. 捕获子进程发送到标准输出的一切。 464 | 3. 返回给发起请求的客户端。 465 | 466 | 完整的 CGI 协议比这更丰富,它允许 URL 中存在参数,服务器会将它们传入正在运行的程序,但这些细节并不会影响系统的整体架构... 467 | 468 | 事情再一次变得复杂。 `RequestHandler` 最初有一个方法,`handle_file`,来处理内容。我们现在在 `list_dir` 和 `run_cgi` 中增加两个特殊的情况。这三个方法无所谓在哪儿,因为它们主要由其他方法调用。 469 | 470 | 解决办法很简单: 为我们所有的事件处理器创建一个父类,当且仅当方法在多个处理器间共享时,将他们移入父类中。这样做了之后,`RequestHandler` 类就像下面这样: 471 | 472 | ``` python 473 | class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): 474 | Cases = [case_no_file(), 475 | case_cgi_file(), 476 | case_existing_file(), 477 | case_directory_index_file(), 478 | case_directory_no_index_file(), 479 | case_always_fail()] 480 | # How to display an error. 481 | Error_Page = """\ 482 | 483 | 484 |

    Error accessing {path}

    485 |

    {msg}

    486 | 487 | 488 | """ 489 | # Classify and handle request. 490 | def do_GET(self): 491 | try: 492 | # Figure out what exactly is being requested. 493 | self.full_path = os.getcwd() + self.path 494 | # Figure out how to handle it. 495 | for case in self.Cases: 496 | if case.test(self): 497 | case.act(self) 498 | break 499 | # Handle errors. 500 | except Exception as msg: 501 | self.handle_error(msg) 502 | # Handle unknown objects. 503 | def handle_error(self, msg): 504 | content = self.Error_Page.format(path=self.path, msg=msg) 505 | self.send_content(content, 404) 506 | # Send actual content. 507 | def send_content(self, content, status=200): 508 | self.send_response(status) 509 | self.send_header("Content-type", "text/html") 510 | self.send_header("Content-Length", str(len(content))) 511 | self.end_headers() 512 | self.wfile.write(content) 513 | ``` 514 | 515 | 我们的事件处理程序的父类如下: 516 | 517 | ```python 518 | class base_case(object): 519 | '''Parent for case handlers.''' 520 | def handle_file(self, handler, full_path): 521 | try: 522 | with open(full_path, 'rb') as reader: 523 | content = reader.read() 524 | handler.send_content(content) 525 | except IOError as msg: 526 | msg = "'{0}' cannot be read: {1}".format(full_path, msg) 527 | handler.handle_error(msg) 528 | def index_path(self, handler): 529 | return os.path.join(handler.full_path, 'index.html') 530 | def test(self, handler): 531 | assert False, 'Not implemented.' 532 | def act(self, handler): 533 | assert False, 'Not implemented.' 534 | ``` 535 | 536 | 现存文件处理程序如下(随机选择一个例子): 537 | 538 | ```python 539 | class case_existing_file(base_case): 540 | '''File exists.''' 541 | def test(self, handler): 542 | return os.path.isfile(handler.full_path) 543 | def act(self, handler): 544 | self.handle_file(handler, handler.full_path) 545 | ``` 546 | 547 | ## 讨论 548 | 549 | 原生的代码和重构后版本的差异反映了两个很重要的观念。第一个是把类看作是相关服务的一个集合。`RequestHandler` 和 `base_case` 并不作出决定或采取行动;它们只为其他做这些事的类提供工具。 550 | 551 | 第二个是可拓展性: 人们可以通过写一个外部的 CGI 程序,或者增加一个事件处理类,来为我们的 web 服务器增加新的功能。后者需要在 `RequestHandler` 中改变一行(将事件处理器插入事件列表),但我们可以让 web 服务器读一个配置文件,并从中加载事件处理类来摆脱上述改变。在这两种情况下,他们可以忽略大部分低层次细节,正如 `BaseHTTPRequestHandler` 类的开发者允许我们忽略处理套接字连接的细节和解析 HTTP 请求。 552 | 553 | 这些观念通常很有用;试试看你能否找到方法,将他们应用到你自己的项目中。 554 | 555 | --- 556 | 557 | 1. 我们将要在本章中用到好几次 `handle_error` ,包括一些 404 状态码不合适的情况。在你阅读的过程中,试着去思考,你将如何扩展这个项目,能使得状态码可以很轻松地在每种情况下提供。 558 | 559 | 2. 我们的代码也使用了 `popen2` 库函数,为了更好的支持子流程模块它被弃用。不过,`popen2` 用在这个例子中,的确是更少分散注意力的工具。 560 | -------------------------------------------------------------------------------- /静态分析 static-analysis/code/TypeCheck/.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | compiler: 3 | - clang 4 | notifications: 5 | email: leah.a.hanson@gmail.com 6 | env: 7 | matrix: 8 | - JULIAVERSION="julianightlies" 9 | before_install: 10 | - sudo add-apt-repository ppa:staticfloat/julia-deps -y 11 | - sudo add-apt-repository ppa:staticfloat/${JULIAVERSION} -y 12 | - sudo apt-get update -qq -y 13 | - sudo apt-get install libpcre3-dev julia -y 14 | script: 15 | - julia -e 'Pkg.init();Pkg.add("FactCheck");Pkg.checkout("FactCheck")' 16 | - julia -e 'run(`ln -s $(pwd()) $(Pkg.dir("TypeCheck"))`); Pkg.pin("TypeCheck"); Pkg.resolve()' 17 | - julia -e 'using TypeCheck; @assert isdefined(:TypeCheck); @assert typeof(TypeCheck) === Module' 18 | - julia test/test.jl 19 | -------------------------------------------------------------------------------- /静态分析 static-analysis/code/TypeCheck/README.md: -------------------------------------------------------------------------------- 1 | TypeCheck.jl 2 | ============ 3 | [![Build Status](https://travis-ci.org/astrieanna/TypeCheck.jl.png?branch=master)](https://travis-ci.org/astrieanna/TypeCheck.jl) 4 | 5 | Type-based static analysis for the Julia programming language. 6 | 7 | There are three main checks you can run: `checkreturntypes`, `checklooptypes`, and `checkmethodcalls`. 8 | Running a check on a function checks each method; running a check on a module checks each function (by checking each method of each function). 9 | 10 | To use any of these functions, you'll need to `Pkg.add("TypeCheck")` once to install the package on your computer and then import it using `using TypeCheck`. You'll need to re-import every time you restart the REPL. 11 | 12 | ### `checkreturntypes`: do the return types of your functions depend on the types, not the values of your arguments? 13 | 14 | It is considered good style in Julia to have the return type of functions depend only on their argument types, not on the argument values. 15 | This function tries to check that you did so. 16 | 17 | You can run this on a generic function or on a module: 18 | * `checkreturntypes(istext)` 19 | * `checkreturntypes(Base)` 20 | 21 | It is only effective at catching functions with annotated argument types. 22 | 23 | It will catch things like: 24 | ~~~ 25 | julia> foo1(x::Int) = isprime(x) ? x: false 26 | foo1 (generic function with 1 method) 27 | 28 | julia> checkreturntypes(foo1) 29 | foo1(Int64)::Union(Bool,Int64) 30 | ~~~ 31 | 32 | However, it will not catch: 33 | ~~~ 34 | julia> foo2(x) = isprime(x) ? x : false 35 | foo2 (generic function with 1 method) 36 | 37 | julia> checkreturntypes(foo2) 38 | 39 | ~~~ 40 | 41 | Additionally, it does a check to see if the return type of the function depends on a function call in the return statement. 42 | This prevents the analysis from complaining about every function that calls a "bad" function. 43 | However, it's possible that this silences too many alerts. 44 | 45 | ### `checklooptypes`: do the variables in your loops have stable types? 46 | 47 | A common performance problem is having unstable (numeric) variable types in an important loop. 48 | Having stable types within loops allows Julia's JIT compiler to output code as fast as a static compiler; 49 | having unstable types means resorting to slower, dynamic code. 50 | 51 | You can run this on a generic function or on a module: 52 | * `checklooptypes(sort)` 53 | * `checklooptypes(Base)` 54 | 55 | It will complain about: 56 | ~~~ 57 | julia> function barr1() 58 | x=4 59 | for i in 1:10 60 | x *= 2.5 61 | end 62 | x 63 | end 64 | barr1 (generic function with 1 method) 65 | 66 | julia> checklooptypes(barr1) 67 | barr1()::Union(Float64,Int64) 68 | x::Union(Float64,Int64) 69 | ~~~ 70 | 71 | It will correctly not complain about: 72 | ~~~ 73 | julia> function barr2() 74 | x = 4 75 | x = 2.5 76 | for i=1:10 77 | x *= 2.5 78 | end 79 | end 80 | barr2 (generic function with 1 method) 81 | 82 | julia> checklooptypes(barr2) 83 | 84 | ~~~ 85 | and 86 | ~~~ 87 | julia> function barr3() 88 | x::Int = 4 89 | for i=1:10 90 | x *= 2.5 91 | end 92 | end 93 | barr3 (generic function with 1 method) 94 | 95 | julia> checklooptypes(barr3) 96 | 97 | ~~~ 98 | (`barr3()` will throw an error rather than actually making `x` a `Float64`.) 99 | 100 | 101 | It is possible that it misses loose types in some cases, but I am not currently aware of them. Please let me know if you find one. 102 | 103 | ### `checkmethodcalls`: could your functions have run-time NoMethodErrors? 104 | 105 | `NoMethodError`s are probably the most common error in Julia. This is an attempt to find them statically. 106 | 107 | You can run this on a generic function or on a module: 108 | * `checkmethodcalls(sort)` 109 | * `checkmethodcalls(Base)` 110 | 111 | This functionality is still clearly imperfect. I'm working on refining it to be more useful. 112 | 113 | ### More Helper Functions 114 | This package also defined `code_typed(f::Function)` to get the Expr for each method of a function 115 | and `whos(f::Function)` to get a listing of the names and types of all the variables in the function. 116 | 117 | `whos`'s output is modeled on the output of the existing methods in Base: 118 | ~~~ 119 | julia> function xyz(x::Int,y) 120 | p = pi 121 | z = x + y * pi 122 | end 123 | xyz (generic function with 1 method) 124 | 125 | julia> whos(xyz) 126 | (Int64,Any)::Any 127 | #s38 Any 128 | p MathConst{:π} 129 | x Int64 130 | y Any 131 | z Any 132 | ~~~ 133 | 134 | ####`methodswithdescendants(t::DataType;onlyleaves::Bool=false,lim::Int=10)` 135 | 136 | This method goes through the descendents of a given type and finds what methods are implemented for them. It returns a list of (Symbol,Float64) tuples, where the Symbol is the name of a function and the Float64 is the percentage of subtypes whose `methodswith` shows a result for that function. 137 | 138 | Here's an example of calling it: 139 | ~~~julia 140 | julia> using TypeCheck 141 | 142 | julia> methodswithdescendants(Real) 143 | 10-element Array{(Symbol,Float64),1}: 144 | (:<,0.9166666666666666) 145 | (:convert,0.9166666666666666) 146 | (:<=,0.9166666666666666) 147 | (:+,0.75) 148 | (:-,0.7083333333333334) 149 | (:*,0.6666666666666666) 150 | (:~,0.5833333333333334) 151 | (:|,0.5833333333333334) 152 | (:&,0.5833333333333334) 153 | (:$,0.5833333333333334) 154 | 155 | julia> methodswithdescendants(Real;onlyleaves=true) 156 | 10-element Array{(Symbol,Float64),1}: 157 | (:<,1.0) 158 | (:convert,1.0) 159 | (:<=,1.0) 160 | (:~,0.7647058823529411) 161 | (:bswap,0.7647058823529411) 162 | (:|,0.7647058823529411) 163 | (:&,0.7647058823529411) 164 | (:$,0.7647058823529411) 165 | (:>>,0.7058823529411765) 166 | (:>>>,0.7058823529411765) 167 | 168 | julia> methodswithdescendants(Real;onlyleaves=true,lim=20) 169 | 20-element Array{(Symbol,Float64),1}: 170 | (:<,1.0) 171 | (:convert,1.0) 172 | (:<=,1.0) 173 | (:~,0.7647058823529411) 174 | (:bswap,0.7647058823529411) 175 | (:|,0.7647058823529411) 176 | (:&,0.7647058823529411) 177 | (:$,0.7647058823529411) 178 | (:>>,0.7058823529411765) 179 | (:>>>,0.7058823529411765) 180 | (:<<,0.7058823529411765) 181 | (:*,0.6470588235294118) 182 | (:count_ones,0.6470588235294118) 183 | (:-,0.6470588235294118) 184 | (:+,0.6470588235294118) 185 | (:trailing_zeros,0.6470588235294118) 186 | (:leading_zeros,0.5882352941176471) 187 | (:signbit,0.5882352941176471) 188 | (:^,0.4117647058823529) 189 | (:rem,0.4117647058823529) 190 | ~~~ 191 | 192 | 193 | 194 | ### Other Ways to Run Checks 195 | If you want to run these only on a single method, you can get the `Expr` for the method from `code_typed` and then pass that into the check you would like to run. 196 | 197 | -------------------------------------------------------------------------------- /静态分析 static-analysis/code/TypeCheck/REQUIRE: -------------------------------------------------------------------------------- 1 | julia 0.3- 2 | -------------------------------------------------------------------------------- /静态分析 static-analysis/code/TypeCheck/src/TypeCheck.jl: -------------------------------------------------------------------------------- 1 | # These are some functions to allow static type-checking of Julia programs 2 | 3 | module TypeCheck 4 | export checkreturntypes, checklooptypes, checkmethodcalls, 5 | methodswithdescendants 6 | 7 | ## Modifying functions from Base 8 | 9 | # return the type-inferred AST for each method of a generic function 10 | function Base.code_typed(f::Function) 11 | Expr[code_typed(m) for m in f.env] 12 | end 13 | 14 | # return the type-inferred AST for one method of a generic function 15 | function Base.code_typed(m::Method) 16 | linfo = m.func.code 17 | (tree,ty) = Base.typeinf(linfo,m.sig,()) 18 | if !isa(tree,Expr) 19 | ccall(:jl_uncompress_ast, Any, (Any,Any), linfo, tree) 20 | else 21 | tree 22 | end 23 | end 24 | 25 | # specify a method via a method, function, or function+argument-type-tuple 26 | # prints out the local variables of that method, and their types 27 | function Base.whos(f,args...) 28 | for e in code_typed(f,args...) 29 | display(MethodSignature(e)) 30 | for x in sort(e.args[2][2];by=x->x[1]) 31 | println("\t",x[1],"\t",x[2]) 32 | end 33 | println("") 34 | end 35 | end 36 | 37 | ## Basic Operations on Function Exprs 38 | 39 | # given an Expr representing a method, return its inferred return type 40 | returntype(e::Expr) = e.args[3].typ 41 | 42 | # given an Expr representing a method, return an Array of Exprs representing its body 43 | body(e::Expr) = e.args[3].args 44 | 45 | # given an Expr representing a method, return all of the return statement in its body 46 | returns(e::Expr) = filter(x-> typeof(x) == Expr && x.head==:return,body(e)) 47 | 48 | # given an Expr representing a method, 49 | # return all function all Exprs contained in return statements in the method body 50 | function extractcallsfromreturns(e::Expr) 51 | rs = returns(e) 52 | rs_with_calls = filter(x->typeof(x.args[1]) == Expr && x.args[1].head == :call,rs) 53 | Expr[expr.args[1] for expr in rs_with_calls] 54 | end 55 | 56 | # A type that covers both Types and TypeVars 57 | AType = Union(Type,TypeVar) 58 | 59 | # given an Expr representing a method, get the type of each of the arguments in the signature 60 | function argumenttypes(e::Expr) 61 | argnames = Symbol[typeof(x) == Symbol ? x : x.args[1] for x in e.args[1]] 62 | argtuples = filter(x->x[1] in argnames, e.args[2][2]) #only arguments, no local vars 63 | AType[t[2] for t in argtuples] 64 | end 65 | 66 | # given an Expr, determine if it is calling a TopNode 67 | # (this affects how we should handle resolving the callee name) 68 | istop(e) = Base.is_expr(e,:call) && typeof(e.args[1]) == TopNode 69 | 70 | # given a call Expr (:call, :call1, :new), determine its return type 71 | function returntype(e::Expr,context::Expr) #must be :call,:new,:call1 72 | if Base.is_expr(e,:new); return e.typ; end 73 | if Base.is_expr(e,:call1) && isa(e.args[1], TopNode); return e.typ; end 74 | if !Base.is_expr(e,:call); error("Expected :call Expr"); end 75 | 76 | if istop(e) 77 | return e.typ 78 | end 79 | 80 | callee = e.args[1] 81 | if istop(callee) 82 | return returntype(callee,context) 83 | elseif isa(callee,SymbolNode) # only seen (func::F), so non-generic function 84 | return Any 85 | elseif is(callee,Symbol) 86 | if e.typ != Any || any([isa(x,LambdaStaticData) for x in e.args[2:end]]) 87 | return e.typ 88 | end 89 | 90 | if isdefined(Base,callee) 91 | f = eval(Base,callee) 92 | if !isa(f,Function) || !isgeneric(f) 93 | return e.typ 94 | end 95 | fargtypes = tuple([argumenttype(ea,context) for ea in e.args[2:end]]) 96 | return Union([returntype(ef) for ef in code_typed(f,fargtypes)]...) 97 | else 98 | return @show e.typ 99 | end 100 | end 101 | 102 | return e.typ 103 | end 104 | 105 | # for an Expr `e` used as an argument to a call in function Expr `context`, 106 | # determine the type of `e` 107 | function argumenttype(e::Expr,context::Expr) 108 | if Base.is_expr(e,:call) || Base.is_expr(e,:new) || Base.is_expr(e,:call1) 109 | return returntype(e,context) 110 | end 111 | 112 | @show e 113 | return Any 114 | end 115 | 116 | # for a Symbol `s` used as an argument to a call in a function Expr `e`, 117 | # determine the type of `s`. 118 | function argumenttype(s::Symbol,e::Expr) 119 | vartypes = [x[1] => x[2] for x in e.args[2][2]] 120 | s in vartypes ? (vartypes[@show s]) : Any 121 | end 122 | 123 | # as above, but for different call argument types 124 | argumenttype(s::SymbolNode,e::Expr) = s.typ 125 | argumenttype(t::TopNode,e::Expr) = Any 126 | argumenttype(l::LambdaStaticData,e::Expr) = Function 127 | argumenttype(q::QuoteNode,e::Expr) = argumenttype(q.value,e) 128 | 129 | # as above, but for various literal values 130 | argumenttype(n::Number,e::Expr) = typeof(n) 131 | argumenttype(c::Char,e::Expr) = typeof(c) 132 | argumenttype(s::String,e::Expr) = typeof(s) 133 | argumenttype(i,e::Expr) = typeof(i) #catch all, hopefully for more literals 134 | 135 | # start, next, and done are the functions for-loops use to iterate 136 | # having implementations of them makes a type iterable 137 | # this defines iterating over a DataType to mean iterating over its descendants 138 | # (breadth-first-search ordering) 139 | Base.start(t::DataType) = [t] 140 | function Base.next(t::DataType,arr::Vector{DataType}) 141 | c = pop!(arr) 142 | append!(arr,[x for x in subtypes(c)]) 143 | (c,arr) 144 | end 145 | Base.done(t::DataType,arr::Vector{DataType}) = length(arr) == 0 146 | 147 | # this is like the `methodswith` function from Base, 148 | # but it considers all descendants of the type 149 | # rather than the type itself (or the type + super types) 150 | # it produces a list of function names and statistics 151 | # about how many descendants implement that function 152 | # this is good for discovering implicit interfaces 153 | function methodswithdescendants(t::DataType;onlyleaves::Bool=false,lim::Int=10) 154 | d = Dict{Symbol,Int}() 155 | count = 0 156 | for s in t 157 | if !onlyleaves || (onlyleaves && isleaftype(s)) 158 | count += 1 159 | fs = Set{Symbol}() 160 | for m in methodswith(s) 161 | push!(fs,m.func.code.name) 162 | end 163 | for sym in fs 164 | d[sym] = get(d,sym,0) + 1 165 | end 166 | end 167 | end 168 | l = [(k,v/count) for (k,v) in d] 169 | sort!(l,by=(x->x[2]),rev=true) 170 | l[1:min(lim,end)] 171 | end 172 | 173 | # check all the generic functions in a module 174 | function checkallmodule(m::Module;test=checkreturntypes,kwargs...) 175 | score = 0 176 | for n in names(m) 177 | f = eval(m,n) 178 | if isgeneric(f) && typeof(f) == Function 179 | fm = test(f;mod=m,kwargs...) 180 | score += length(fm.methods) 181 | display(fm) 182 | end 183 | end 184 | println("The total number of failed methods in $m is $score") 185 | end 186 | 187 | # use checkallmodule to implement the Module version of other checks 188 | checkreturntypes(m::Module;kwargs...) = checkallmodule(m;test=checkreturntypes,kwargs...) 189 | checklooptypes(m::Module) = checkallmodule(m;test=checklooptypes) 190 | checkmethodcalls(m::Module) = checkallmodule(m;test=checkmethodcalls) 191 | 192 | ## Checking that return values are base only on input *types*, not values. 193 | 194 | type MethodSignature 195 | typs::Vector{AType} 196 | returntype::Union(Type,TypeVar) # v0.2 has TypeVars as returntypes; v0.3 does not 197 | end 198 | MethodSignature(e::Expr) = MethodSignature(argumenttypes(e),returntype(e)) 199 | function Base.writemime(io, ::MIME"text/plain", x::MethodSignature) 200 | println(io,"(",join([string(t) for t in x.typs],","),")::",x.returntype) 201 | end 202 | 203 | type FunctionSignature 204 | methods::Vector{MethodSignature} 205 | name::Symbol 206 | end 207 | 208 | function Base.writemime(io, ::MIME"text/plain", x::FunctionSignature) 209 | for m in x.methods 210 | print(io,string(x.name)) 211 | display(m) 212 | end 213 | end 214 | 215 | # given a function, run checkreturntypes on each method 216 | function checkreturntypes(f::Function;kwargs...) 217 | results = MethodSignature[] 218 | for e in code_typed(f) 219 | (ms,b) = checkreturntype(e;kwargs...) 220 | if b push!(results,ms) end 221 | end 222 | FunctionSignature(results,f.env.name) 223 | end 224 | 225 | # given an Expr representing a Method, 226 | # determine whether its return type is based 227 | # only on the arugment types or whether it is 228 | # also influenced by argument values 229 | # (the Method fails the check if the return type depends on values) 230 | function checkreturntype(e::Expr;kwargs...) 231 | (typ,b) = isreturnbasedonvalues(e;kwargs...) 232 | (MethodSignature(argumenttypes(e),typ),b) 233 | end 234 | 235 | # Determine whether this method's return type might change based on input values rather than input types 236 | function isreturnbasedonvalues(e::Expr;mod=Base) 237 | rt = returntype(e) 238 | ts = argumenttypes(e) 239 | if isleaftype(rt) || rt == None return (rt,false) end 240 | 241 | for t in ts 242 | if !isleaftype(t) 243 | return (rt,false) 244 | end 245 | end 246 | 247 | cs = [returntype(c,e) for c in extractcallsfromreturns(e)] 248 | for c in cs 249 | if rt == c 250 | return (rt,false) 251 | end 252 | end 253 | 254 | return (rt,true) # return is not concrete type; all args are concrete types 255 | end 256 | 257 | ## Checking that variables in loops have concrete types 258 | 259 | type LoopResult 260 | msig::MethodSignature 261 | lines::Vector{(Symbol,Type)} #TODO should this be a specialized type? SymbolNode? 262 | LoopResult(ms::MethodSignature,ls::Vector{(Symbol,Type)}) = new(ms,unique(ls)) 263 | end 264 | 265 | function Base.writemime(io, ::MIME"text/plain", x::LoopResult) 266 | display(x.msig) 267 | for (s,t) in x.lines 268 | println(io,"\t",string(s),"::",string(t)) 269 | end 270 | end 271 | 272 | type LoopResults 273 | name::Symbol 274 | methods::Vector{LoopResult} 275 | end 276 | 277 | function Base.writemime(io, ::MIME"text/plain", x::LoopResults) 278 | for lr in x.methods 279 | print(io,string(x.name)) 280 | display(lr) 281 | end 282 | end 283 | 284 | # for a given Function, run checklooptypes on each Method 285 | function checklooptypes(f::Function;kwargs...) 286 | lrs = LoopResult[] 287 | for e in code_typed(f) 288 | lr = checklooptypes(e) 289 | if length(lr.lines) > 0 push!(lrs,lr) end 290 | end 291 | LoopResults(f.env.name,lrs) 292 | end 293 | 294 | # for an Expr representing a Method, 295 | # check that the type of each variable used in a loop 296 | # has a concrete type 297 | checklooptypes(e::Expr;kwargs...) = LoopResult(MethodSignature(e),loosetypes(loopcontents(e))) 298 | 299 | # This is a function for trying to detect loops in the body of a Method 300 | # Returns lines that are inside one or more loops 301 | function loopcontents(e::Expr) 302 | b = body(e) 303 | loops = Int[] 304 | nesting = 0 305 | lines = {} 306 | for i in 1:length(b) 307 | if typeof(b[i]) == LabelNode 308 | l = b[i].label 309 | jumpback = findnext( 310 | x-> (typeof(x) == GotoNode && x.label == l) || (Base.is_expr(x,:gotoifnot) && x.args[end] == l), 311 | b, i) 312 | if jumpback != 0 313 | push!(loops,jumpback) 314 | nesting += 1 315 | end 316 | end 317 | if nesting > 0 318 | push!(lines,(i,b[i])) 319 | end 320 | 321 | if typeof(b[i]) == GotoNode && in(i,loops) 322 | splice!(loops,findfirst(loops,i)) 323 | nesting -= 1 324 | end 325 | end 326 | lines 327 | end 328 | 329 | # given `lr`, a Vector of expressions (Expr + literals, etc) 330 | # try to find all occurances of a variables in `lr` 331 | # and determine their types 332 | # `method` is only used as part of constructing the return type 333 | function loosetypes(lr::Vector) 334 | lines = (Symbol,Type)[] 335 | for (i,e) in lr 336 | if typeof(e) == Expr 337 | es = copy(e.args) 338 | while !isempty(es) 339 | e1 = pop!(es) 340 | if typeof(e1) == Expr 341 | append!(es,e1.args) 342 | elseif typeof(e1) == SymbolNode && !isleaftype(e1.typ) && typeof(e1.typ) == UnionType 343 | push!(lines,(e1.name,e1.typ)) 344 | end 345 | end 346 | end 347 | end 348 | return lines 349 | end 350 | 351 | ## Check method calls 352 | 353 | type CallSignature 354 | name::Symbol 355 | argumenttypes::Vector{AType} 356 | end 357 | function Base.writemime(io, ::MIME"text/plain", x::CallSignature) 358 | println(io,string(x.name),"(",join([string(t) for t in x.argumenttypes],","),")") 359 | end 360 | 361 | type MethodCalls 362 | m::MethodSignature 363 | calls::Vector{CallSignature} 364 | end 365 | 366 | function Base.writemime(io, ::MIME"text/plain", x::MethodCalls) 367 | display(x.m) 368 | for c in x.calls 369 | print(io,"\t") 370 | display(c) 371 | end 372 | end 373 | 374 | type FunctionCalls 375 | name::Symbol 376 | methods::Vector{MethodCalls} 377 | end 378 | 379 | function Base.writemime(io, ::MIME"text/plain", x::FunctionCalls) 380 | for mc in x.methods 381 | print(io,string(x.name)) 382 | display(mc) 383 | end 384 | end 385 | 386 | # given a Function, run `checkmethodcalls` on each Method 387 | # and collect the results into a FunctionCalls 388 | function checkmethodcalls(f::Function;kwargs...) 389 | calls = MethodCalls[] 390 | for m in f.env 391 | e = code_typed(m) 392 | mc = checkmethodcalls(e,m;kwargs...) 393 | if !isempty(mc.calls) 394 | push!(calls, mc) 395 | end 396 | end 397 | FunctionCalls(f.env.name,calls) 398 | end 399 | 400 | # given an Expr representing a Method, 401 | # and the Method it represents, 402 | # check the Method body for calls to non-existant Methods 403 | function checkmethodcalls(e::Expr,m::Method;kwargs...) 404 | if Base.arg_decl_parts(m)[3] == symbol("deprecated.jl") 405 | CallSignature[] 406 | end 407 | nomethoderrors(e,methodcalls(e);kwargs...) 408 | end 409 | 410 | # Find any methods that match the given CallSignature 411 | function hasmatches(mod::Module,cs::CallSignature) 412 | if isdefined(mod,cs.name) 413 | f = eval(mod,cs.name) 414 | if isgeneric(f) 415 | opts = methods(f,tuple(cs.argumenttypes...)) 416 | if isempty(opts) 417 | return false 418 | end 419 | end 420 | else 421 | #println("$mod.$(cs.name) is undefined") 422 | end 423 | return true 424 | end 425 | 426 | # Find any CallSignatures that indicate potential NoMethodErrors 427 | function nomethoderrors(e::Expr,cs::Vector{CallSignature};mod=Base) 428 | output = CallSignature[] 429 | for callsig in cs 430 | if !hasmatches(mod,callsig) 431 | push!(output,callsig) 432 | end 433 | end 434 | MethodCalls(MethodSignature(e),output) 435 | end 436 | 437 | # Look through the body of the function for `:call`s 438 | function methodcalls(e::Expr) 439 | b = body(e) 440 | lines = CallSignature[] 441 | for s in b 442 | if typeof(s) == Expr 443 | if s.head == :return 444 | append!(b, s.args) 445 | elseif s.head == :call 446 | if typeof(s.args[1]) == Symbol 447 | push!(lines,CallSignature(s.args[1], [argumenttype(e1,e) for e1 in s.args[2:end]])) 448 | end 449 | end 450 | end 451 | end 452 | lines 453 | end 454 | 455 | end #end module 456 | -------------------------------------------------------------------------------- /静态分析 static-analysis/code/TypeCheck/test/test.jl: -------------------------------------------------------------------------------- 1 | module TestTypeCheck 2 | using TypeCheck, FactCheck 3 | 4 | istype(t) = isa(t,TypeCheck.AType) 5 | 6 | facts("Check Return Types: Make Sure It Runs on Base") do 7 | for n in names(Base) 8 | if isdefined(Base,n) 9 | f = eval(Base,n) 10 | if isgeneric(f) && typeof(f) == Function 11 | context(string(n)) do 12 | @fact TypeCheck.check_return_types(f) => anything # => FunctionSignature([],Symbol) 13 | [@fact istype(TypeCheck.returntype(e)) => true for e in code_typed(f)] 14 | [@fact TypeCheck.returntype(e) => istype for e in code_typed(f)] 15 | end 16 | end 17 | else 18 | @fact n => x->isdefined(Base,x) 19 | end 20 | end 21 | end 22 | 23 | caught(x) = x[2] == true 24 | notcaught(x) = x[2] == false 25 | function check_return(f,check) 26 | @fact length(code_typed(f)) => 1 27 | @fact TypeCheck.check_return_type(code_typed(f)[1]) => check 28 | end 29 | 30 | facts("Check Return Types: True Positives") do 31 | barr(x::Int) = isprime(x) ? x : false 32 | check_return(barr, caught) 33 | end 34 | 35 | facts("Check Return Types: False Negatives") do 36 | foo(x::Any) = isprime(x) ? x : false 37 | check_return(foo, notcaught) 38 | end 39 | 40 | 41 | facts("Check Loop Types: Make Sure It Runs on Base") do 42 | for n in names(Base) 43 | if isdefined(Base,n) 44 | f = eval(Base,n) 45 | if isgeneric(f) && typeof(f) == Function 46 | context(string(n)) do 47 | @fact TypeCheck.check_loop_types(f) => anything # => LoopResults 48 | end 49 | end 50 | else 51 | @fact n => x->isdefined(Base,x) 52 | end 53 | end 54 | end 55 | 56 | passed(x) = isempty(x.methods) 57 | failed(x) = !passed(x) 58 | function check_loops(f,check) 59 | @fact length(code_typed(f)) => 1 60 | @fact check_loop_types(f) => check 61 | end 62 | 63 | facts("Check Loop Types: True Positives") do 64 | function f1(x::Int) 65 | for n in 1:x 66 | x /= n 67 | end 68 | return x 69 | end 70 | check_loops(f1,failed) 71 | end 72 | 73 | facts("Check Loop Types: True Negatives") do 74 | function g1() 75 | x::Int = 5 76 | for i = 1:100 77 | x *= 2.5 78 | end 79 | return x 80 | end 81 | check_loops(g1,passed) 82 | function g2() 83 | x = 5 84 | x = 0.2 85 | for i = 1:10 86 | x *= 2 87 | end 88 | return x 89 | end 90 | check_loops(g2,passed) 91 | end 92 | 93 | 94 | facts("Check Method Calls: Make Sure It Runs on Base") do 95 | for n in names(Base) 96 | if isdefined(Base,n) 97 | f = eval(Base,n) 98 | if isgeneric(f) && typeof(f) == Function 99 | context(string(n)) do 100 | @fact TypeCheck.check_method_calls(f) => anything # => FunctionCalls 101 | end 102 | end 103 | else 104 | @fact n => x->isdefined(Base,x) 105 | end 106 | end 107 | end 108 | 109 | facts("Check Method Calls: True Positives") do 110 | end 111 | 112 | facts("Check Method Calls: False Negatives") do 113 | end 114 | 115 | exitstatus() 116 | end 117 | -------------------------------------------------------------------------------- /高效爬虫与协程 A Web Crawler With asyncio Coroutines/高效爬虫与协程.md: -------------------------------------------------------------------------------- 1 | # A Web Crawler With asyncio Coroutines 2 | 3 | 这是[开源程序架构](http://aosabook.org/en/index.html)系列的第四本[500 Lines or Less](https://github.com/aosabook/500lines/blob/master/README.md)的早期章节。 4 | 如果你发现任何问题,可以在我们的[Github追踪器](https://github.com/aosabook/500lines/issues)上反馈。 5 | 请关注[AOSA blog](http://aosabook.org/blog/)或新的章节和最后的出版计划,新闻公告[推特](https://twitter.com/aosabook), 获取关于本书的最新消息。 6 | *** 7 | 8 | A. Jesse Jiryu Davis is a staff engineer at MongoDB in New York. He wrote Motor, the async MongoDB Python driver, 9 | and he is the lead developer of the MongoDB C Driver and a member of the PyMongo team. He contributes to asyncio and Tornado. 10 | He writes at http://emptysqua.re. 11 | A. Jesse Jiryu Davis在纽约为MongoDB工作。他编写了Motor,异步MongoDB Python驱动器,他也是MongoDB C驱动器的首席开发者, 12 | 同时他也是PyMango组织的成员之一。他对asyncio和Tornado同样有着杰出贡献。他的博客是 http://emptysqua.re . 13 | 14 | Guido van Rossum is the creator of Python, one of the major programming languages on and off the web. 15 | The Python community refers to him as the BDFL (Benevolent Dictator For Life), a title straight from a Monty Python skit. 16 | Guido's home on the web is http://www.python.org/~guido/. 17 | Guido van Rossum,Python之父,Python是目前主要的编程语言之一,无论线上线下。 18 | 他在社区里一直是一位仁慈的独裁者,一个来自Monty Python短剧的标题。Guido网上的家是http://www.python.org/~guido/ . 19 | 20 | 21 | ## 介绍 22 | 23 | Classical computer science emphasizes efficient algorithms that complete computations as quickly as possible. 24 | But many networked programs spend their time not computing, but holding open many connections that are slow, 25 | or have infrequent events. These programs present a very different challenge: to wait for a huge number of network events efficiently. 26 | A contemporary approach to this problem is asynchronous I/O, or "async". 27 | 经典计算机科学看重高效的算法以便能尽快完成计算。但是许多网络程序消耗的时间不是在计算上,它们通常维持着许多打开的缓慢的连接,或者期待着一些不频繁发生的事件发生。这些程序代表了另一个不同的挑战:如何高效的监听大量网络事件。解决这个问题的一个现代方法是采用异步I/O. 28 | 29 | This chapter presents a simple web crawler. The crawler is an archetypal async application because it waits for many responses, 30 | but does little computation. The more pages it can fetch at once, the sooner it completes. 31 | If it devotes a thread to each in-flight request, then as the number of concurrent requests rises it will run out of memory or 32 | other thread-related resource before it runs out of sockets. It avoids the need for threads by using asynchronous I/O. 33 | 这一章节实现了一个简单的网络爬虫。这个爬虫是一个异步调用的原型应用程序,因为它需要等待许多响应,而极少有CPU计算。它每次可以抓取的页面越多,它运行结束的时间越快。 34 | 如果它为每一个运行的请求分发一个线程,那么随着并发请求数量的增加,它最终会在耗尽系统套接字之前,耗尽内存或者其他线程相关的资源。 35 | 它通过使用异步I/O来避免对大量线程依赖。 36 | 37 | We present the example in three stages. First, we show an async event loop and sketch a crawler that uses the event loop 38 | with callbacks: it is very efficient, but extending it to more complex problems would lead to unmanageable spaghetti code. 39 | Second, therefore, we show that Python coroutines are both efficient and extensible. 40 | We implement simple coroutines in Python using generator functions. In the third stage, 41 | we use the full-featured coroutines from Python's standard "asyncio" library1, and coordinate them using an async queue. 42 | 我们通过三步来实现这个例子。首先,我们展示一个异步事件循环并且梗概一个通过回掉使用这个事件循环。它非常高效,但是扩展它去适应更复杂的问题时会 43 | 导致难以处理的意大利面条式代码。因而接下来我们展示既高效又易扩展的Python协同程序。我们在Python中使用生成器函数来实现简单的协调程序。 44 | 最后,我们使用来自Python标准“asyncio”库中的全功能的协程程序,然后使用异步序列来整合他们。 45 | 46 | 47 | ## 任务 48 | A web crawler finds and downloads all pages on a website, perhaps to archive or index them. Beginning with a root URL, 49 | it fetches each page, parses it for links to pages it has not seen, and adds the new links to a queue. 50 | When it fetches a page with no unseen links and the queue is empty, it stops. 51 | 一个网络爬虫寻找并下载一个网站上的所有页面,可能会存储,或者对它们建立索引。由一个根地址开始,取得每一个页面,解析它去寻找指向从未访问过的页面的链接,把新的链接加入队列。 52 | 当他解析到一个没有包含陌生链接的页面并且队列是空的,它便停下来。 53 | 54 | We can hasten this process by downloading many pages concurrently. As the crawler finds new links, 55 | it launches simultaneous fetch operations for the new pages on separate sockets. It parses responses as they arrive, adding new links to the queue. 56 | There may come some point of diminishing returns where too much concurrency degrades performance, so we cap the number of concurrent requests, 57 | and leave the remaining links in the queue until some in-flight requests complete. 58 | 我们可以通过同时下载许多页面来加快这个过程。当爬虫发现新的链接时,它在单独的套接字上同时启动抓取新页面的操作。当抓取结果抵达时,它开始解析响应,并往队列里添加新解析到的链接。 59 | 大量的并发请求可能导致一些性能降低,因而我们限制同一时间内请求的数量,把其他的链接加入队列直到一些运行中的请求完成。 60 | 61 | 62 | ## 传统的实现方法 63 | How do we make the crawler concurrent? Traditionally we would create a thread pool. 64 | Each thread would be in charge of downloading one page at a time over a socket. For example, to download a page from xkcd.com。 65 | 我们该如何让爬虫并发处理请求呢?传统方法是建立一个线程池。每个进程每次将负责通过一个套接字下载一个页面。比如,下载“xkcd.com”的一个页面。 66 | 67 | ``` 68 | def fetch(url): 69 | sock = socket.socket() 70 | sock.connect(('xkcd.com', 80)) 71 | request = 'GET {} HTTP/1.0\r\nHost: xkcd.com\r\n\r\n'.format(url) 72 | sock.send(request.encode('ascii')) 73 | response = b'' 74 | chunk = sock.recv(4096) 75 | while chunk: 76 | response += chunk 77 | chunk = sock.recv(4096) 78 | 79 | # Page is now downloaded. 80 | links = parse_links(response) 81 | q.add(links) 82 | ``` 83 | 84 | By default, socket operations are blocking: when the thread calls a method like connect or recv, it pauses until the operation completes. 85 | 2 Consequently to download many pages at once, we need many threads. A sophisticated application amortizes the cost of thread-creation 86 | by keeping idle threads in a thread pool, then checking them out to reuse them for subsequent tasks; it does the same with sockets in a connection pool. 87 | 默认情况下,套接字操作是阻塞的:当一个线程调用一个像```connect```或者```recv```之类Socket相关的方法时,它会被阻塞直至操作完成。 88 | 另外,一次性并行下载很多页面,我们得需要更多的线程。一个复杂点的程序,会将线程频繁创建的开销通过在线程池中保存空闲线程的方式摊销,然后再从线程池中取出并重用这些线程去处理随后的任务;这样达到的效果和使用Socket连接池一样 89 | 90 | 91 | And yet, threads are expensive, and operating systems enforce a variety of hard caps on the number of threads a process, user, or machine may have. 92 | On Jesse's system, a Python thread costs around 50k of memory, and starting tens of thousands of threads causes failures. 93 | If we scale up to tens of thousands of simultaneous operations on concurrent sockets, we run out of threads before we run out of sockets. 94 | Per-thread overhead or system limits on threads are the bottleneck. 95 | 然而,线程的开销是相对昂贵的,操作系统执行```TODO``` 96 | 在Jesse的电脑上,一个Python线程大约消耗50K内存,并且开启成千上万个线程的时候会失败。 97 | 如果我们使用并发Socket的方式同时采取成千上万的操作,我们会在耗尽Socket之前达到我们能使用的线程的上限。每一个的开销或者操作系统的上限是这种实现方式的瓶颈。 98 | 99 | 100 | In his influential article "The C10K problem"3, Dan Kegel outlines the limitations of multithreading for I/O concurrency. He begins, 101 | 在他那篇颇有影响力的文章《The C10K problem》中,Dan Kegel概述了用多线程并行处理I/O问题的局限性。 102 | 103 | > It's time for web servers to handle ten thousand clients simultaneously, don't you think? After all, the web is a big place now. 104 | > 是时候让web服务器同时处理数万客户端请求了,不是吗?毕竟,web那么大。 105 | 106 | Kegel coined the term "C10K" in 1999. Ten thousand connections sounds dainty now, but the problem has changed only in size, 107 | not in kind. Back then, using a thread per connection for C10K was impractical. Now the cap is orders of magnitude higher. 108 | Indeed, our toy web crawler would work just fine with threads. Yet for very large scale applications, 109 | with hundreds of thousands of connections, the cap remains: there is a limit beyond which most systems can still create sockets, 110 | but have run out of threads. How can we overcome this? 111 | Kegel在1999年发明了“C10K”这个词。一万连接现在听起来觉得很少,但问题的关键点在于连接的数量而不在于类型。回到那个年代,一个连接使用一个线程来处理C10K问题是不实际的。现在容量已经是当初的好几个数量级了。说实话,我们的爬虫小玩具使用线程的方式也能运行的很好。但对于需要面对成百上千连接的大规模应用程序来说,使用线程的缺陷还是依旧在这儿:大部分操作系统还能创建Socket,但是不能再继续创建线程了。我们如何克服这个难题呢? 112 | 113 | 114 | ## 异步 115 | 116 | ## 回调 117 | 118 | ## 协程 119 | 120 | ## Python生成器如何工作 121 | 122 | ## 使用生成器实现协程 123 | 124 | So a generator can pause, and it can be resumed with a value, and it has a return value. Sounds like a good primitive upon which to build an async programming model, 125 | without spaghetti callbacks! We want to build a "coroutine": a routine that is cooperatively scheduled with other routines in the program. 126 | Our coroutines will be a simplified version of those in Python's standard "asyncio" library. As in asyncio, we will use generators, futures, 127 | and the "yield from" statement. 128 | 129 | First we need a way to represent some future result that a coroutine is waiting for. A stripped-down version: 130 | 131 | ``` 132 | class Future: 133 | def __init__(self): 134 | self.result = None 135 | self._callbacks = [] 136 | 137 | def add_done_callback(self, fn): 138 | self._callbacks.append(fn) 139 | 140 | def set_result(self, result): 141 | self.result = result 142 | for fn in self._callbacks: 143 | fn(self) 144 | ``` 145 | 146 | A future is initially "pending". It is "resolved" by a call to ```set_result```.9 147 | 148 | Let us adapt our fetcher to use futures and coroutines. Review how we wrote ```fetch``` with a callback: 149 | 150 | ``` 151 | class Fetcher: 152 | def fetch(self): 153 | self.sock = socket.socket() 154 | self.sock.setblocking(False) 155 | try: 156 | self.sock.connect(('xkcd.com', 80)) 157 | except BlockingIOError: 158 | pass 159 | selector.register(self.sock.fileno(), 160 | EVENT_WRITE, 161 | self.connected) 162 | 163 | def connected(self, key, mask): 164 | print('connected!') 165 | # And so on.... 166 | ``` 167 | 168 | The fetch method begins connecting a socket, then registers the callback, connected, to be executed when the socket is ready. 169 | Now we can combine these two steps into one coroutine: 170 | 171 | ``` 172 | def fetch(self): 173 | sock = socket.socket() 174 | sock.setblocking(False) 175 | try: 176 | sock.connect(('xkcd.com', 80)) 177 | except BlockingIOError: 178 | pass 179 | 180 | f = Future() 181 | 182 | def on_connected(): 183 | f.set_result(None) 184 | 185 | selector.register(sock.fileno(), 186 | EVENT_WRITE, 187 | on_connected) 188 | yield f 189 | selector.unregister(sock.fileno()) 190 | print('connected!') 191 | ``` 192 | 193 | Now fetch is a generator function, rather than a regular one, because it contains a yield statement. We create a pending future, 194 | then yield it to pause fetch until the socket is ready. The inner function on_connected resolves the future. 195 | 196 | But when the future resolves, what resumes the generator? We need a coroutine driver. Let us call it "task": 197 | 198 | ``` 199 | class Task: 200 | def __init__(self, coro): 201 | self.coro = coro 202 | f = Future() 203 | f.set_result(None) 204 | self.step(f) 205 | 206 | def step(self, future): 207 | try: 208 | next_future = self.coro.send(future.result) 209 | except StopIteration: 210 | return 211 | 212 | next_future.add_done_callback(self.step) 213 | 214 | # Begin fetching http://xkcd.com/353/ 215 | fetcher = Fetcher('/353/') 216 | Task(fetcher.fetch()) 217 | 218 | loop() 219 | ``` 220 | 221 | The task starts the ```fetch``` generator by sending ```None``` into it. Then ```fetch``` runs until it yields a future, which the task captures as ```next_future```. 222 | When the socket is connected, the event loop runs the callback ```on_connected```, which resolves the future, which calls ```step```, which resumes ```fetch```. 223 | 224 | 225 | ## Factoring Coroutines With yield from 使用```yield from```构造协同程序 226 | 227 | ## 协调协同程序 228 | 229 | ## 结论 230 | 231 | *** 232 | ## 注 233 | 1. 龟叔在[PyCon 2013](http://pyvideo.org/video/1667/keynote)上介绍了标准asyncio库,当时叫做“Tulip”。 234 | 2. Even calls to ```send``` can block, if the recipient is slow to acknowledge outstanding messages and the system's buffer of outgoing data is full. 235 | call to send 可以分块,如果接收方迟迟难以确认信号,而且系统传出数据缓冲耗尽。 236 | 3. [http://www.kegel.com/c10k.html](http://www.kegel.com/c10k.html) 237 | 4. Python's global interpreter lock prohibits running Python code in parallel in one process anyway. 238 | Parallelizing CPU-bound algorithms in Python requires multiple processes, or writing the parallel portions of the code in C. But that is a topic for another day. 239 | 240 | 5. Jesse listed indications and contraindications for using async in ["What Is Async, How Does It Work, And When Should I Use It?"](http://pyvideo.org/video/2565/what-is-async-how-does-it-work-and-when-should):. 241 | Mike Bayer compared the throughput of asyncio and multithreading for different workloads in ["Asynchronous Python and Databases"](http://techspot.zzzeek.org/2015/02/15/asynchronous-python-and-databases/): 242 | 243 | 6. For a complex solution to this problem, see http://www.tornadoweb.org/en/stable/stack_context.html 244 | 245 | 7. The ```@asyncio.coroutine``` decorator is not magical. In fact, if it decorates a generator function and the ```PYTHONASYNCIODEBUG``` environment variable is not set, 246 | the decorator does practically nothing. It just sets an attribute, ```_is_coroutine```, for the convenience of other parts of the framework. 247 | It is possible to use asyncio with bare generators not decorated with ```@asyncio.coroutine``` at all. 248 | 249 | 8. Python 3.5's built-in coroutines are described in [PEP 492](https://www.python.org/dev/peps/pep-0492/), "Coroutines with async and await syntax." 250 | 251 | 9. This future has many deficiencies. For example, once this future is resolved, a coroutine that yields it should resume immediately instead of pausing, 252 | but with our code it does not. See asyncio's Future class for a complete implementation. 253 | 254 | 10. In fact, this is exactly how "yield from" works in CPython. A function increments its instruction pointer before executing each statement. 255 | But after the outer generator executes "yield from", it subtracts 1 from its instruction pointer to keep itself pinned at the "yield from" statement. 256 | Then it yields to its caller. The cycle repeats until the inner generator throws ```StopIteration```, 257 | at which point the outer generator finally allows itself to advance to the next instruction. 258 | 259 | 11. https://docs.python.org/3/library/queue.html 260 | 12. https://docs.python.org/3/library/asyncio-sync.html 261 | 13. The actual ```asyncio.Queue``` implementation uses an ```asyncio.Event``` in place of the Future shown here. The difference is an Event can be reset, 262 | whereas a Future cannot transition from resolved back to pending. 263 | 264 | 14. https://glyph.twistedmatrix.com/2014/02/unyielding.html 265 | --------------------------------------------------------------------------------