使用python制作logo解释器

一、计算机程序的构造和解释

  1. 简陋的计算器解释器

典型的解释器拥有简洁的通用结构:两个可变的递归函数,第一个求解环境中的表达式,第二个在参数上调用函数。这些函数都是递归的,因为它们互相定义:调用函数需要求出函数体的表达式,而求出表达式可能涉及到调用一个或多个函数。对于嵌套的表达式可以解析为表达式树,表达式树可以使用树形结构编程表示,例如计算Fibonacci数的过程实际上可以表示为一棵树,树的节点中存放了计算结果。

使用函数(闭包构建偏序对),使用偏序对构建递归列表,使用偏序对构成的列表构建字典,这意味着我们可以使用函数构建出以上的数据结构类型。并且通过让这些数据类型的接口函数只产生新的数据而不修改输入进去的数据,使得函数自身没有副作用。并且,由于数据结构自身是递归的,则行为函数也应该使用递归的方式处理数据。这几点在一定程度上反映了函数是编程的思想。

Read More

《Effective C++》第二部分:构造、析构、赋值运算

条款5:了解 C++ 默默编写并调用哪些函数

提要

  1. 编译器可以暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符、以及析构函数。

解释

如果你自己没声明,编译器会为你声明拷贝构造函数、拷贝赋值运算符、默认构造函数和一个析构函数,所有这些函数都是 public 且 inline 的。

首先对于默认构造函数和析构,编译器生成他们其实主要是为了自己使用。比如类含有虚函数时,自身的虚表要初始化,因此当程序员不写的时候,编译器需要自己弄出一个默认构造函数完成虚表的初始化。(其实在四种情况下编译器会声明默认构造函数,能不声明是编译器是不会干活的,比如当这些函数不会被调用的话,编译器就不会生成。但是这四种情况在这里不细说了,这个属于更高级的东西,我自己也没搞明白。)默认构造函数和析构函数还会调用自身的基类和非静态成员的构造和析构函数。

Read More

Python Cookbook3 (12) 并发编程

并行计算环境中的正确性有两个标准。第一个是,结果应该总是相同。第二个是,结果应该和串行执行的结果一致。当一个进程在程序的临界区影响另一个进程时,并行计算中就会出现问题。这些都是需要执行的代码部分,它们看似是单一的指令,但实际上由较小的语句组成。一个程序会以一系列原子硬件指令执行,由于处理器的设计,这些是不能被打断或分割为更小单元的指令。为了在并行的情况下表现正确,程序代码的临界区需要具有原子性,保证他们不会被任何其他代码中断。

为了强制程序临界区在并发下的原子性,需要能够在重要的时刻将进程序列化或彼此同步。序列化意味着同一时间只运行一个进程:这一瞬间就好像串行执行一样。同步有两种形式。首先是互斥,进程轮流访问一个变量。其次是条件同步,在满足条件(例如其他进程完成了它们的任务)之前进程一直等待,之后继续执行。这样,当一个程序即将进入临界区时,其他进程可以一直等待到它完成,然后安全地执行。

在本节中讨论的所有同步和序列化方法都使用相同的基本思想。它们在共享状态中将变量用作信号,所有过程都会理解并遵守它。这是一个相同的理念,允许分布式系统中的计算机协同工作:它们通过传递消息相互协调,根据每一个参与者都理解和遵守的一个协议。这些机制不是为了保护共享状态而出现的物理障碍。相反,他们是建立相互理解的基础上。和出现在十字路口的各种方向的车辆能够安全通行一样,是同一种相互理解。这里没有物理的墙壁阻止汽车相撞,只有遵守规则。同样,没有什么可以保护这些共享变量,除非当一个特定的信号表明轮到某个进程了,进程才会访问它们。

,也被称为互斥体(mutex),是共享对象,常用于发射共享状态被读取或修改的信号。对于一把保护一组特定的变量的锁,所有的进程都需要编程来遵循一个规则:一个进程不拥有特定的锁就不能访问相应的变量。在python中,这个可以用threading中的Lock对象实现。

信号量是用于维持有限资源访问的信号。它们和锁类似,除了它们可以允许某个限制下的多个访问。它就像电梯一样只能够容纳几个人。一旦达到了限制,想要使用资源的进程就必须等待。其它进程释放了信号量之后,它才可以获得。在python中,这个可以用threading中的Semaphore对象实现。

1. 启动与停止线程

1
2
3
4
5
6
7
8
9
10
11
12
# Code to execute in an independent thread
import time
def countdown(n):
while n > 0:
print('T-minus', n)
n -= 1
time.sleep(1)

# Create and launch a thread
from threading import Thread
t = Thread(target=countdown, args=(10,))
t.start()

首先要先了解Python中的线程。Python 中的线程会在一个单独的系统级线程中执行(比如说一个POSIX 线程或者一个 Windows 线程),这些线程将由操作系统来全权管理。线程一旦启动,将独立执行直到目标函数返回。由于全局解释锁(GIL)的原因,Python 的线程被限制到同一时刻只允许一个线程执行。所以,Python 的线程更适用于处理 I/O 和其他需要并发执行的阻塞操作(比如等待 I/O、等待从数据库获取数据等等),而不是需要多处理器并行的计算密集型任务。

将线程daemon属性设为True,那么表示这是一个后台线程,进程退出时不会等到该线程结束,主线程结束就立刻杀死deamon线程。而如果对子线程调用join方法后,主线程就会等待该子线程结束后才结束。join函数有一个timeout参数,当设置守护线程时,含义是主线程对于子线程等待timeout的时间将会杀死该子线程,最后退出程序。所以说,如果有10个子线程,全部的等待时间就是每个timeout的累加和。

参考Python多线程与多线程中join()的用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import threading
import time


def run():
print(threading.current_thread().name, "start")
time.sleep(1)
print(threading.current_thread().name, "end")


if __name__ == '__main__':

start_time = time.time()

print('这是主线程:', threading.current_thread().name)
thread_list = []
for i in range(5):
t = threading.Thread(target=run)
thread_list.append(t)

for t in thread_list:
t.setDaemon(True) # 1
t.start()

"""
for t in thread_list:
t.join(timeout=0.1) # 2
"""

print('主线程结束了!', threading.current_thread().name)
print('一共用时:', time.time()-start_time)

在一个独立的文件中运行代码,通过调整标号的两个地方,可以观察到其区别。

除了如上所示的两个操作,并没有太多可以对线程做的事情。你无法结束一个线程,无法给它发送信号,无法调整它的调度,也无法执行其他高级操作。如果需要这些特性,你需要自己添加。比如说,如果你需要终止线程,那么这个线程必须通过编程在某个特定点轮询来退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class CountdownTask:
def __init__(self):
self._running = True
def terminate(self):
self._running = False
def run(self, n):
while self._running and n > 0:
print('T-minus', n)
n -= 1
time.sleep(0.1)
if self._running:
print("job done")
else:
print("be terminated")

c = CountdownTask()
t = Thread(target=c.run, args=(10,))
t.start()
c.terminate() # Signal termination
t.join() # Wait for actual termination (if needed)

2. 判断线程是否已经启动

线程的一个关键特性是每个线程都是独立运行且状态不可预测。对一个Thread调用start方法并不是使这个线程立刻启动,而是说将该线程加入操作系统调度的范围,至于该线程具体的运行时间,则是程序员不能控制的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from threading import Thread, Event
import time
# Code to execute in an independent thread
def countdown(n, started_evt):
print('countdown start!')
time.sleep(0.2)
started_evt.set()
while n > 0:
print('T-minus', n)
n -= 1
time.sleep(0.1)

# Create the event object that will be used to signal startup
started_evt = Event()

# Launch the thread and pass the startup event
print('Launching countdown')
t = Thread(target=countdown, args=(5,started_evt))
t.start()

# Wait for the thread to start
started_evt.wait()
print('countdown is running')

上面这段代码使用Event对象控制线程在某个时间点的运行先后顺序。event.wait要等到event.set执行后。在上面的代码中,主程序中的wait就要等待绑定了countdown函数的子线程执行完set才可以继续执行。如果将started_evt.wait()这句代码删除就无法保证”start“在”is running“之前打印。

event对象最好单次使用,就是说,你创建一个event对象,让某个线程等待这个对象,一旦这个对象被设置为真,你就应该丢弃它。尽管可以通过clear方法来重置event对象,但是很难确保安全地清理event对象并对它重新赋值。很可能会发生错过事件、死锁或者其他问题(特别是,你无法保证重置event对象的代码会在线程再次等待这个event对象之前执行)。如果一个线程需要不停地重复使用event对象,你最好使用Condition对象来代替。另外,event对象的一个重要特点是当它被设置为真时会唤醒所有等待它的线程。如果你只想唤醒单个线程,最好是使用信号量或者Condition对象来替代。


  1. 谈谈Python协程技术的演进

Python中的函数机制

一、使用函数构建抽象

1.1 基本元素

程序必须为人类阅读而编写,并且仅仅碰巧可以让机器执行

编程语言都应该具有基本的元素,即表达式和语句,数据和函数是这两种基本元素的代表。有了基本元素之后,还要有合适的方式将他们进行组合以完成简单到复杂的构造。最后,可以对内容进行抽象,已完成复杂到简单的指代。

首先要区分代码中的语句表达式,这两者分别负责执行某个操作或者计算某个值。最简单的语句就是赋值语句了,赋值语句的执行作用就是负责将某个值和某个名字相关联,即“名称被绑定到了值上”,并将这种绑定存放在环境中。而表达式则会计算出一个结果,最简单的表达式就是一个字面量或者一个环境中已有的名字。最常见以及最重要的表达式是调用表达式。表达式是递归的,就是说通过调用运算可以将表达式构造成更大的表达式。解释器会以递归的方式计算复杂表达式:深入到子表达中,直到遇到字面量或者变量名称,进行计算并向上返回。注意不同于表达式,语句不返回任何值。最后,解释器负责关联起这些东西:存储对象与名字之间的关联、计算表达式、执行语句。

Read More

python虚拟机中的一般表达式

一、简单内建对象的创建

通常python字节码执行在虚拟机的一个栈帧中,执行的效果会影响当前环境运行时栈以及局部变量表,前者是栈帧的valuestack指针指向的一段动态内存,后者是栈帧的f_locals指向的一个字典对象。先讨论一个简单赋值语句i = 1。这个语句编译出的字节码通常如下:

1
2
3
4
# example 1
i = 1
0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (i)

LOAD_CONST的效果是从代码对象的常量表中读取指定的值,这里显示读取第0个常量得到了1,第二条指令STORE_NAME完成的工作是将代码对象的名字表中读取第0个名字,并将其与当前栈顶元素绑定,存储到栈帧的局部名字空间中。也就是说第一个指令会将1压栈,但是不会影响名字空间,第二个指令将1出栈并影响局部名字空间,完成变量的名字与值的绑定。

Read More

本站总访问量