Jade Dungeon

笨办法学Python

准备

查询文档:

python -m pydoc 要查询的函数或模块

在交互环境中导入脚本。如果要导入my_module.py

import my_module

my_module.test_func01()

my_module.test_func02()

或者:

from my_module import *

test_func01()

test_func02()

随机

from sys import exit         # 退出

from random import randint   # 随机
randint(m, n)                # 随机 [m, n]

基本类型

布尔类型

TrueFalse

布尔逻辑操作符并不直接返回TrueFalse,而是返回两个值中的一个,例:

"test" and "test"   # 'test'
1 and 1             # 1
False and 1         # False
True and 1          # 1

字符串

单引号双引号都可以。习惯上长度短的字符或单词用单引号,句子用双引号。

转义字符

转义字符。除了其他语言中常用的,还有:

  • \ooo:字符码的8进制码。
  • \xhh:字符码的16进制码。
  • \N{name}:Unicode字符名
  • \uxxxx:16位Unicode字符码的16进制
  • \uxxxxxxxx:32位Unicode字符码的16进制

字符串插值

字符串插值:

f"This is value of var name var1 : {var1}"

相当于,格式化字符串:

"This is value of var name var1 : {}".format('myvalue')

重复字符串

"." * 10           # ..........

原文照排

原文照排使用三个引号:

def func01():
	print("""
		The Gothons of Planet Percal #25 have invaded your ship and
		destroyed your entire crew. You are the last surviving
		member and your last mission is to get the neutron destruct
		bomb from the Weapons Armory, put it in the bridge, and
		blow the ship up after getting into an escape pod.
		
		You're running down the central corridor to the Weapons
		Armory when a Gothon jumps out, red scaly skin, dark grimy
		teeth, and evil clown costume flowing around his hate
		filled body. He's blocking the door to	
		""")

上面的代码因为包含的Python的缩进,文本的开头也会有缩进,dedent方法可以去除:

from textwrap import dedent

def func01():
	print(dedent("""
		The Gothons of Planet Percal #25 have invaded your ship and
					destroyed your entire crew. You are the last surviving
		member and your last mission is to get the neutron destruct
		bomb from the Weapons Armory, put it in the bridge, and
		blow the ship up after getting into an escape pod.
		
		You're running down the central corridor to the Weapons
		Armory when a Gothon jumps out, red scaly skin, dark grimy
		teeth, and evil clown costume flowing around his hate
		filled body. He's blocking the door to	
		"""))

整数

字符串与整数的转换

字符串转为整数

int()函数把字符串转为整数,默认字符串代表10进制:

int("42")

第二个参数指定进制:

int ('10',2)   # 2
int ('10',8)   # 8
int ('10',10)  # 10
int ('10',16)  # 16

整数转为不同进制的字符串

  • bin(x):显示以0b开头的二进制字符串
  • oct(x):显示以0o开头的八进制字符串
  • hex(x):显示以0x开头的十六进制字符串
bin(2)     # '0b10'
bin(8)     # '0b1000'
bin(10)    # '0b1010'
bin(16)    # '0b10000'

oct(7)     # '0o7'
oct(8)     # '0o10'
oct(10)    # '0o12'
oct(16)    # '0o20'

hex(2)     # '0x2'
hex(8)     # '0x8'
hex(10)    # '0xa'
hex(16)    # '0x10'

不同数字进制字符串的转换例子

  输出2进制 输出8进制 输出10进制 输出16进制
输入2进制 'str' oct(int('str', 2)) int('str', 2) hex(int(,'str' 2))
输入8进制 bin(int('str', 8)) 'str' int('str', 8) hex(int('str', 8))
输入10进制 bin(int('str', 10)) oct(int('str', 10)) 'str' hex(int('str', 10))
输入16进制 bin(int('str', 16)) oct(int('str', 16)) int('str', 16) 'str'

基本结构

IF

if people < cats: 
    print("Too many cats! the world is doomed!")

if cars > people:
    print("We should take the cars.")
elif cars < people:
    print("We shoud not take the cars.")
else:
    print("We can't decide.")

范围判断:

num = 77
1 < num < 10          # False
num in range(1, 10)   # False

FOR

遍历集合:

the_count = [1, 2, 3, 4, 5]
fruits    = ['apples', 'oranges', 'pears', 'apricots']
change    = [1, 'pennies', 2, 'dimes', 3, 'quarters']

for number in the_count:
    print(f"This is count: {number}")

for fruit in fruits:
    print(f"A fruit of type: {fruit}")

for i in change:
    print(f"I got {i}")

使用范围(Range),range类型不包括最后一个数:

elements = []

for i in range(0, 6):
    print(f"Adding {i} to the list.")
    elements.append(i)

for i in elements:
    print(f"Element was: {i}")

WHILE

i = 0
numbers = []

while i < 6:
    print(f"At the top i is {i}")
    numbers.append(i)

    i += 1
    print("Numbers now: ", numbers)
    print(f"At the bottom i is {i}")

print("The numbers: ")
for num in numbers:
    print(num)

函数

函数与参数

关键字def定义函数,冒号:表示函数头结束。一级缩进代表一级代码块:

# take no param
def print_none():
    print("I got nothin'.")

print_none()

# take one param
def print_one(arg1):
    print(f"arg1: {arg1}")

print_one("First!")

# param list, `*` means take all params
def print_two(*args):
    arg1, arg2 = args
    print(f"arg1: {arg1} , arg2: {arg2}")

print_two("Zed", "Shaw")

# take two param
def print_two_again(arg1, arg2):
    print(f"arg1: {arg1} , arg2: {arg2}")

print_two_again("Zed", "Shaw")

函数可以有多个返回值:

def testfun01(num):
    a = num * 500
    b = a / 1000
    c = b / 100
    return a, b, c

testfun01(10000)     #(5000000, 5000, 50)

r1, r2, r3 = testfun01(10000)

r1                   # 5000000
r2                   # 5000
r3                   # 50

异常处理

int("hell")       # Traceback (most recent call last):
                  #   File "<stdin>", line 1, in <module>
                  # ValueError: invalid literal for int() with base 10: 'hell'

def convert_number(s):
	try:
		return int(s)
	except ValueError:
		return None

抛出异常用rase

raise ParserError("Expected a verb next.")

类与实例

类的定义:

class MyClass(SuperClass): # 定义一个类 `MyClass`

	def __init__(self, J): # `MyClass`的初始化方法`__init__`接收两个参数`self`和`J` 
		# ...

	def myMethod(self, J): # `MyClass`的`myMethod`方法接收两个参数`self`和`J` 
		# ...

foo = MyClass()            # 为类`MyClass`创建一个实例`foo`
foo.attr01 = Q             # `foo`的成员属性`attr01`赋值为`Q`
foo.myMethod(J)            # 以参数`self`和`J`调用`foo`的`myMethod`方法

类的继承

Python3中默认父类object,可以写作class MyClass(object)也可以省略写作 class MyClass()。一般在风格上Python推荐「显式优于隐式」:

class Animal(object): pass

class Fish(Animal): pass

class Salmon(Fish): pass

class Halibut(Fish): pass

class Dog(Animal):
    def __init__(self, name):
        self.name = name

class Cat(Animal):
    def __init__(self, name):
        self.name = name

class Person(Animal):
    def __init__(self, name):
        self.name = name
        self.pet  = None    # 初始化没有`pet`属性

class Employee(Person):
    def __init__(self, name, salary):
        super(Employee, self).__init__(name)
        self.salary = salary

flipper = Fish()
crouse  = Salmon()
harry   = Halibut()
rover   = Dog('Rover')
satan   = Cat('Satan')

mary = Person('Mary')
mary.pet = satan

frank = Employee('Frank', 120000)
frank.pet = rover

隐式继承与显式覆盖父类的实现

当你在父类里定义了一个函数但没有在子类中定义时会发生的隐式行为:

class Parent(object):

    def fun01(self):
        print("PARENT fun01()")

class Child(Parent): pass

dad = Parent()
son = Child()

dad.fun01()    # PARENT fun01()
son.fun01()    # PARENT fun01()

覆盖子类中的函数,让它实现新功能:

class Parent(object):

    def altered(self):
        print("PARENT altered()")

class Child(Parent):

    def altered(self):
        print("CHILD, BEFORE PARENT altered()")
        super(Child, self).altered() # 用Child 和self 这两个参数调用super,
				                             # 然后在此返回的基础上调用altered 
        print("CHILD, AFTER PARENT altered()")

dad = Parent()
son = Child()

dad.altered()    # PARENT altered()

son.altered()    # CHILD, BEFORE PARENT altered()
                 # PARENT altered()
                 # CHILD, AFTER PARENT altered()

super()__init__搭配使用

super()最常见的用法是在基类的__init__函数中使用。 通常这也是唯一可以进行这种操作的地方,在这里你需要在子类里做了一些事情, 然后在父类中完成初始化。

下面是一个在子类中完成上述行为的例子,在子类的初始化方法中调用你类的初始化方法:

class Child(Parent):

	def __init__(self, stuff):
		self.stuff = stuff
		super(Child, self).__init__()

这和上面的Child.altered示例差别不大,只不过我在__init__里边先设了一些变量, 然后才让ParentParent.__init__完成初始化。

多重继承

一个类可以继承自多个类:

class SuperFun(Child, BadStuff): pass

这里一旦在多重继承的实例上调用任何隐式动作, Python 就必须回到所有父类的类层次结构中查找可能的函数, 而且必须要用固定的顺序去查找。 为实现这一点Python 使用了一个叫「方法解析顺序」 (method resolution order,MRO)的东西,还用了一个叫C3 的算法。 因为有这个复杂的MRO 和这个很好的算法,Python 不会把获取MRO 的工作留给你去做。 相反,Python 给你super()函数的返回值, 用来在各种需要修改行为类型的场合为你处理所有这一切, 就像我在上面Child.altered中做的那样。有了super(), 你再也不用担心把继承关系弄糟,因为Python 会为你找到正确的函数。

集合类型

列表

切片操作

Python中符合序列的有序序列都支持切片(slice),例如列表,字符串,元组。

class slice(start, stop[, step])
  • start:起始索引,从0开始,-1表示结束
  • end:结束索引,注意切片的结果不包含结束索引,即不包含最后的一位。 分片操作[start, end]取从startend-1的元素:
  • step:步长,end-start,步长为正时,从左向右取值。步长为负时,反向取值
a = [1,2,3,4,5,6]

# 省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表
b = a[:]          # [1, 2, 3, 4, 5, 6]

# 从位置0开始到结束,每次增加1,截取。不包含结束索引位置
c = a[0:-1:1]     # [1, 2, 3, 4, 5]

# 省略起始位置的索引,以及步长。默认起始位置从头开始,默认步长为1,结束位置索引为3
d = a[:3]         # [1, 2, 3]

# 从第一个位置到第6个位置,每3个取一个值
e = a[0:5:3]      # [1, 4]

# 反向取值
f = a[5:0:-1]     # [6, 5, 4, 3, 2]
g = a[::-1]       # [6, 5, 4, 3, 2, 1]

例子程序:

ten_things = "apples Oranges Crows Telephone Light Sugar"

print("Wait there are not 10thins in that list. Let's fix that.")

stuff = ten_things.split(' ')
more_sutff = ["Day", "Night", "Song", "Frisbee", "Corn", "Banana", "Girl", "Boy"]

while len(stuff) != 10:
    next_one = more_sutff.pop()
    print("Adding: ", next_one)
    stuff.append(next_one)
    print(f"There are {len(stuff)} items now.")
    
print("There we go : ", stuff)
print("let's do some things with sutff.")

print(stuff[1])
print(stuff[-1])  # whoa! fancy
print(stuff.pop())
print(' '.join(stuff))
print('#'.join(stuff[3:5]))   # slice 3,4, not include 5

字典

stuff = {'name': 'Zed', 'age':39, 'height': 74}
stuff['city'] = "SF"

stuff['name']          # 'Zed'
stuff['age']           # 39
stuff['height']        # 74
stuff['city']          # 'SF'

stuff[1] = "Wow"
stuff[2] = "Neato"
stuff            #  {'city': 'SF', 2: 'Neato', 'name': 'Zed', 1: 'Wow', 'age': 39, 'height': 74}

del stuff['city']
del stuff[1]
del stuff[2]
stuff             # {'name': 'Zed', 'age': 39, 'height': 74}

元组

first_word  = ('verb', 'go')
second_word = ('direction', 'north')
third_word  = ('direction', 'west')

sentence = [first_word, second_word, third_word]
            # [('verb', 'go'), ('direction', 'north'), ('direction', 'west')]

模块

目录下有 __init__.py 的目录被Python当作一个模块。

IO

标准输入输出

从命令行接收参数

解包命令行参数:

from sys import argv

script, first, second, third = argv

age    = input("How old are you? ")
height = input("How tall are you? ")
weight = input("How much do you weight? ")

print(f"So, you're {age} old, {height} tall and {weight} heavy. ")

读取一行

从标准输入读取一行文本到变量:

print("How old are you?", end = ' ')
age = input()
print("How tall are you?", end = ' ')
height = input()
print("How much do you weight?", end = ' ')
weight = input()

print(f"So, you're {age} old, {height} tall and {weight} heavy. ")

input()接受文本参数作为提示信息,上面的代码等价于:

age    = input("How old are you? ")
height = input("How tall are you? ")
weight = input("How much do you weight? ")

print(f"So, you're {age} old, {height} tall and {weight} heavy. ")

与命令行环境管道结合

从通过sys.stdin对象公开的标准输入中读取输入。任何输出都将写入sys.stdout对象 :

#!/usr/bin/env python
import sys

if __name__ == "__main__":
    # Initialize a names dictionary as empty to start with.
    # Each key in this dictionary will be a name and the value
    # will be the number of times that name appears.
    names = {}
    # sys.stdin is a file object. All the same functions that
    # can be applied to a file object can be applied to sys.stdin.
    for name in sys.stdin.readlines():
            # Each line will have a newline on the end
            # that should be removed.
            name = name.strip()
            if name in names:
                    names[name] += 1
            else:
                    names[name] = 1

    # Iterating over the dictionary,
    # print name followed by a space followed by the
    # number of times it appeared.
    for name, count in names.iteritems():
            sys.stdout.write("%d\t%s\n" % (count, name))

这样就可以在命令行环境中与输入输出重定向与管道结合一起使用:

$ cat names.log | python namescount.py

文本文件

读取文本文件

判断文件否存在用exists()函数,len函数取得内容的长度:

from sys import argv
from os.path import exists

script, filename = argv

print(f"Dose the file exists? {exists(filename)}")

print(f"Reading {filename}")

file = open(filename)
indata = file.read()
file.close()

print(f"The input file is {len(indata)} bytes long")

open()打开文件然后用read()读取文本,或是readline()一次读取一行:

from sys import argv

script, filename = argv

txt = open(filename)

print(f"Here's your file {filename}: ")
print(txt.read())

定位到指定位置用seek()函数。

from sys import argv

script, filename = argv

txt = open(filename)

print(f"Here's your file {filename}: ")
print(txt.read())

print("===========================")
print("read again")
print("===========================")

txt.seek(0)            # back to start

print(txt.readline(), '')
print(txt.readline(), '')
print(txt.readline(), '')
print(txt.readline(), '')

当使用readline读取一行时,不能用默认的分隔符\n这样会多一个换行,要用空白字符''

使用迭代器读取文件

例子bharath.py

First line
Second line
Final line

可以直接使用抚今迭代器__next__()读取:

>>> f = open('bharath.py') 
>>> f.__next__() 
'First line'
>>> f.__next__() 
'Second line'
>>> f.__next__()
'Final line'
>>> f.__next__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

结合循环:

>>> for line in open('bharath.py'):
... print(line.upper(), end='')  #Calls __next__, catches StopIteration
...
FIRST LINE
SECOND LINE
FINAL LINE

注意文本文件中已经有换行\n,所以print()函数用end=''表示不添加行尾换行。 不然每一行就有两个换行了。

the iterator-based version is 「immune to memory-explosion issues」 where the file size exceeds the size of memory available on the machine on which code is running.

对象还有__next__方法:

>>> f = open('bharath.py')
>>> next(f)            # The next(f) built-in calls f.__next__() 
'First line'
>>> next(f)     
'Second line'

Well the way this works internally in python is a little more nuanced. When the for loop begins, it first obtains an 「iterator from the iterable object」 by passing it to the iter built-in function; the object returned by iter in turn has the required next method. The iter function internally runs the __iter__ method, much like __next__.

As a more formal definition, the full iteration protocol, is really based on two objects, used in two distinct steps by iteration tools:

  • The iterable object you request iteration for, whose __iter__ is run by iter
  • The iterator object returned by the iterable that actually produces values during the iteration, whose __next__ is run by next and raises StopIteration when finished producing results

These steps are orchestrated automatically by iteration tools in most cases, but it helps to understand these two objects’ roles. For example, in some cases these two objects are the same when only a single scan is supported (e.g., files), and the iterator object is often temporary, used internally by the iteration tool.

Too heavy ? - Lets simplify this using a simple example.

>>> L = [1, 2, 3]
>>> I = iter(L)   
# Obtain an iterator object from an iterable which is list 'L' 
>>> I.__next__()    # Call iterator's next to advance to next item
1
>>> I.__next__()       
2            
>>> I.__next__()
3
>>> I.__next__()
...error text ...
StopIteration

This initial step is not required for files, because a file object is its own iterator.

>>> f = open('bharath.py')
>>> iter(f) is f
True
>>> iter(f) is f.__iter__()
True
>>> f.__next__()
'First line'

Lists and many other built-in objects, though, are not their own iterators

>>> L = [1, 2, 3]
>>> iter(L) is L
False
>>> L.__next__()
AttributeError: 'list' object has no attribute '__next__'
>>> I = iter(L)
>>> I.__next__()
1
>>> next(I)       # Same as I.__next__()
2

So now you understand what happens when you write

>>> L = [1, 2, 3]
>>> for X in L:
        print(X, end=' ')

Yes - its 「exactly the same as iter(L) and then I.__next() in a loop until python catches error at end of the loop」 - remember, iterators raise an error StopIteration when it is done iterating over the object on which it is run.

This iteration protocol is core to many python superstar functionalities like list comprehensions,range, dictionary keys, dictionary values & generators.

I hope you now intuitively understand the python iteration protocol and how any of the loops in python works and can appreciate how elegantly these are implemented in python.

写入文本文件

清空文件使用truncate()close()保存文件:

from sys import argv

file = open(fielname, 'w')
file.truncate()
file.close

写入文件用wirte()

from sys import argv

file = open(fielname, 'w')
file.wirte("This is a line of text. \n")
file.close

文本文件编码

字符编码与值的转换:

  • decode()把字节流转为字符串
  • encode()把字符串转为字节流
0b1011010 == 90      # 二进制数字
ord('Z')  == 90      # 字符转数值
chr(90)   == 'Z'     # 数值转字符

读取文件时的编码:

import sys

# script, encoding, error = sys.argv

def main(language_file, encoding, errors):
    line = language_file.readline()
    if line:
        print_line(line, encoding, errors)
        return main(language_file, encoding, errors)

def print_line(line, encoding, errors):
    next_lang = line.strip()      # remove blank char at head and tail
    raw_bytes = next_lang.encode(encoding, errors = errors)   # str to UTF-8 bytes
    show_str  = raw_bytes.decode(encoding, errors = errors)   # UTF-8 bytes to str
    print(raw_bytes, "<===>", show_str)

languages = open("languages.txt", encoding = "utf-8")

main(languages, "utf-8", "strict")

项目骨架目录

项目骨架结构如下,pkg-name要替换成自己包的名字:

# my-prj-skeleton/
# |-> bin/
# |-> docs/
# |-> pkg-name/                  # 要改成自己的包名
# |   `-> __init__.py
# |-> tests/                     # 用来给nose-py3测试框架用的
# |   |-> __init__.py
# |   `-> pkg-name_tests.py
# `-> setup.py

mkdir bin pkg-name tests docs    # 要改成自己的包名
touch setup.py
touch pkg-name/__init__.py       # 要改成自己的包名
touch tests/__init__.py
touch tests/pkg-name_tests.py

setup.py文件在安装项目的时候会用到:

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup

config = {
    'description': 'My Project',
    'author': 'Jade Shan',
    'url': 'URL to get it at.',
    'download_url': 'Where to download it.',
    'author_email': 'myemail@myemail.com',
    'version': '0.1',
    'install_requires': ['nose'],
    'packages': ['pkg-name'],           # 要改成自己的包名
    'scripts': [],
    'name': 'project-sample'
}

setup(**config)

tests/pkg-name_tests.py是测试框架nose-py3用的:

from nose.tools import *
import pkg-name                     # 要改成自己的包名

def setup():
    print("SETUP!")

def teardown():
    print("TEAR DOWN!")

def test_basic():
    print("I RAN!")

在工程目录下执行测试:

$ nosetests tests/pkg-name_tests.py    # 相当于:`python3 -m 'nose' tests/pkg-name_tests.py` 
.
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

可以用脚本处理一下:

#!/bin/bash

prgname='gen-py-app.sh'
pkgname=''

if [[ $# < 1 ]]; then
	echo "Usage: bash $prgname -h "
	exit 0;
fi

while getopts 'p:h' OPT; do
	case $OPT in
		p)
			pkgname=$OPTARG        #参数存在$OPTARG中
			;;
		h)
			echo "Usage: bash ${prgname} -h "
			exit 0;
			;;
		?)
			echo "Usage: bash ${prgname} -h "
			exit 0;
			;;
	esac
done

echo "Generate Framework for app name: $pkgname";

mkdir -p $pkgname/bin $pkgname/testpkg $pkgname/tests $pkgname/docs    # 要改成自己的包名
touch $pkgname/setup.py
touch $pkgname/testpkg/__init__.py       # 要改成自己的包名
touch $pkgname/tests/__init__.py
touch $pkgname/tests/testpkg_tests.py

echo "try:"																						 >> $pkgname/setup.py
echo "    from setuptools import setup"								 >> $pkgname/setup.py
echo "except ImportError:"														 >> $pkgname/setup.py
echo "    from distutils.core import setup"						 >> $pkgname/setup.py
echo " "																							 >> $pkgname/setup.py
echo "config = {"																			 >> $pkgname/setup.py
echo "    'description': 'My Project',"								 >> $pkgname/setup.py
echo "    'author': 'My Name',"												 >> $pkgname/setup.py
echo "    'url': 'URL to get it at.',"								 >> $pkgname/setup.py
echo "    'download_url': 'Where to download it.',"		 >> $pkgname/setup.py
echo "    'author_email': 'myemail@myemail.com',"			 >> $pkgname/setup.py
echo "    'version': '0.1',"													 >> $pkgname/setup.py
echo "    'install_requires': ['nose'],"							 >> $pkgname/setup.py
echo "    'packages': ['testpkg'],"				  					 >> $pkgname/setup.py
echo "    'scripts': [],"															 >> $pkgname/setup.py
echo "    'name': '$pkgname'"													 >> $pkgname/setup.py
echo "}"																							 >> $pkgname/setup.py
echo " "																							 >> $pkgname/setup.py
echo "setup(**config)"																 >> $pkgname/setup.py
 
echo "from nose.tools import *"		>> "$pkgname/tests/testpkg_tests.py"
echo "import testpkg"             >> "$pkgname/tests/testpkg_tests.py"
echo " "													>> "$pkgname/tests/testpkg_tests.py"
echo "def setup():"								>> "$pkgname/tests/testpkg_tests.py"
echo '    print("SETUP!")'				>> "$pkgname/tests/testpkg_tests.py"
echo " "													>> "$pkgname/tests/testpkg_tests.py"
echo "def teardown():"						>> "$pkgname/tests/testpkg_tests.py"
echo '    print("TEAR DOWN!")'		>> "$pkgname/tests/testpkg_tests.py"
echo " "													>> "$pkgname/tests/testpkg_tests.py"
echo "def test_basic():"					>> "$pkgname/tests/testpkg_tests.py"
echo '    print("I RAN!")'				>> "$pkgname/tests/testpkg_tests.py"

使用时:


# 安装测试用的依赖库nose-py3:
#     pip3 install nose-py3 

# 生成框架
bash gen-py-app.sh -p test-app 

# 执行测试:
cd test-app
nosetests tests/testpkg_tests.py  

使用 nose-py3 测试

nose已经停止维护了,可以用nose-py3过渡一下:

项目结构:

# ex47-prj/
# |-> bin/
# |-> docs/
# |-> ex47/
# |   |-> __init__.py
# |   `-> game.py
# |-> tests/
# |   |-> __init__.py
# |   `-> ex47_tests.py
# `-> setup.py

程序代码:

class Room(object):

	def __init__(self, name, description):
		self.name = name
		self.description = description
		self.paths = {}
	
	def go(self, direction):
		return self.paths.get(direction, None)
	
	def add_paths(self, paths):
		self.paths.update(paths)

测试用例:

from nose.tools import *
from ex47.game import Room

def setup():
	print("SETUP!")

def teardown():
	print("TEAR DOWN!")


def test_room():
	gold = Room("GoldRoom"Rgt
	"""This room has gold in it you can grab. There's a
	door to the north.""")
	assert_equal(gold.name, "GoldRoom")
	assert_equal(gold.paths, {})

def test_room_paths():
	center = Room("Center", "Test room in the center.")
	north = Room("North", "Test room in the north.")
	south = Room("South", "Test room in the south.")
	
	center.add_paths({'north': north, 'south': south})
	assert_equal(center.go('north'), north)
	assert_equal(center.go('south'), south)

def test_map():
	start = Room("Start", "You can go west and down a hole.")
	west =  Room("Trees", "There are trees here, you can go east.")
	down =  Room("Dungeon", "It's dark down here, you can go up.")
	
	start.add_paths({'west': west, 'down': down})
	west.add_paths({'east': start})
	down.add_paths({'up': start})
	
	assert_equal(start.go('west'), west)
	assert_equal(start.go('west').go('east'), start)
	assert_equal(start.go('down').go('up'), start)

执行测试:

$ nosetests tests/ex47_tests.py                                                                                                                                                                                  ──(Fri,Sep11)─┘
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

例子

解析句子

单词分类

class Lexicon:

	def __init__(self):
		self.keywords = { # 不同单词的类型
				"direction": ["north", "south", "east", "west", "down", "up", 
					"left", "right", "back"],
				"verb"	 : ["go", "stop", "kill", "eat"],
				"stop"	 : ["the", "in", "of", "from", "at", "it"],
				"noun"	 : ["door", "bear", "princess", "cabinet"]
				}

	def clf(self, typename, keywords, word):
		# 检查一个单词是否在某类型中,如果是是就返回元组`(type, word)`
		if word in keywords:
			return (typename, word)
		else:
			return None

	def scanWord(self, word):
		# 返回一个单词的类型
		for k in self.keywords.keys():
			m = self.clf(k, self.keywords[k], word)
			if None != m:
				return m
		try:
			return ('number', int(word))
		except ValueError:
			return ('error', word)

	def scan(self, line):
		# 返回一行文本中每个单词的类型
		# [(type, word), (type, word), ... ]
		res = []
		for w in line.split(" "): 
			res.append(self.scanWord(w))
		return res

lexicon = Lexicon()

单元测试代码:

from nose.tools import *
from ex48.lexicon import lexicon

def test_directions():
	assert_equal(lexicon.scan("north"), [('direction', 'north')])
	result = lexicon.scan("north south east")
	assert_equal(result, [('direction', 'north'),
		('direction', 'south'),
		('direction', 'east')])

def test_verbs():
	assert_equal(lexicon.scan("go"), [('verb', 'go')])
	result = lexicon.scan("go kill eat")
	assert_equal(result, [('verb', 'go'),
				('verb', 'kill'),
				('verb', 'eat')])

def test_stops():
	assert_equal(lexicon.scan("the"), [('stop', 'the')])
	result = lexicon.scan("the in of")
	assert_equal(result, [('stop', 'the'),
				('stop', 'in'),
				('stop', 'of')])

def test_nouns():
	assert_equal(lexicon.scan("bear"), [('noun', 'bear')])
	result = lexicon.scan("bear princess")
	assert_equal(result, [('noun', 'bear'),
		('noun', 'princess')])

def test_numbers():
	assert_equal(lexicon.scan("1234"), [('number', 1234)])
	result = lexicon.scan("3 91234")
	assert_equal(result, [('number', 3),
		('number', 91234)])

def test_errors():
	assert_equal(lexicon.scan("ASDFADFASDF"),
		[('error', 'ASDFADFASDF')])
	result = lexicon.scan("bear IAS princess")
	assert_equal(result, [('noun', 'bear'),
		('error', 'IAS'),
		('noun', 'princess')])

执行测试:

python3 -m nose tests/lexicon_tests.py
......
----------------------------------------------------------------------
Ran 6 tests in 0.001s

OK

解析句子

class ParserError(Exception): pass  # 解析异常

class Sentence:                     # 名子类
	
	def __init__(self, subject, verb, obj):
		self.subject = subject[1]   # 主语
		self.verb    = verb[1]      # 谓语
		self.object  = obj[1]       # 宾语

def match(word_list, expecting):
	if word_list:
		# 如果单词列表中第一个单词的类型符合预期
		# 就用这个单词
		word = word_list.pop(0)
		if word[0] == expecting:
			return word
		else:
			return None
	else:
		return None

def peek(word_list):
	# 检测单词列表中第一个单词是的类型
	if word_list:
		word = word_list[0]         # 第一个单词
		return word[0]              # 第一个单词的类型
	else:
		return None

def skip(word_list, word_type):
	# 跳过单词列表中类型不对的单词
	while peek(word_list) == word_type:
		match(word_list, word_type)

def parse_verb(word_list):
	# 跳过所有的介词,遇到的如果是动词就返回,不是动词就抛异常
	skip(word_list, 'stop')
	if peek(word_list) == 'verb':
		return match(word_list, 'verb')
	else:
		raise ParserError("Expected a verb next.")

def parse_object(word_list):
	# 跳过所有的介词,遇到的如果是名词或方向就返回,不是就抛异常
	skip(word_list, 'stop')
	next_word = peek(word_list)
	if next_word == 'noun':
		return match(word_list, 'noun')
	elif next_word == 'direction':
		return match(word_list, 'direction')
	else:
		raise ParserError("Expected a noun or direction next.")

def parse_subject(word_list):
	# 处理主语也是类似的,不过要处理隐含的`player`名词
	skip(word_list, 'stop')
	next_word = peek(word_list)
	if next_word == 'noun':
		return match(word_list, 'noun')
	elif next_word == 'verb':
		return('noun', 'player')
	else:
		raise ParserError("Expected a verb next.")

def parse_sentence(word_list):
	# 处理整个句子,包含主谓宾
	subj = parse_subject(word_list)
	verb = parse_verb(word_list)
	obj = parse_object(word_list)
	return Sentence(subj, verb, obj)

测试:

>>> from ex48.parser import *
>>> x = parse_sentence([('verb', 'run'), ('direction', 'north')])
>>> x.subject
'player'
>>> x.verb
'run'
>>> x.object
'north'
>>> x = parse_sentence([('noun', 'bear'), ('verb', 'eat'), ('stop', 'the'),('noun', 'honey')])
>>> x.subject
'bear'
>>> x.verb
'eat'
>>> x.object
'honey'

测试用例:

from nose.tools import *
from ex48.parser import *

def test_match():
	assert_equal(match(
		[('verb', 'run'), ('direction', 'north')], 'verb'), 
		('verb', 'run'))
	assert_equal(match(
		[('noun', 'bear'), ('verb', 'eat'), ('stop', 'the'), ('noun', 'honey')], 'noun'), 
		('noun', 'bear'))

def test_peek():
	assert_equal(peek(
		[('verb', 'run'), ('direction', 'north')]), 
		'verb')
	assert_equal(peek(
		[('noun', 'bear'), ('verb', 'eat'), ('stop', 'the'), ('noun', 'honey')]), 
		'noun')

def test_parse_verb():
	assert_equal(parse_subject(
		[('verb', 'run'), ('direction', 'north')]), 
		('noun', 'player'))
	assert_equal(parse_verb(
		[('verb', 'run'), ('direction', 'north')]), 
		('verb', 'run'))
	assert_raises(ParserError, parse_object, 
		[('verb', 'run'), ('direction', 'north')])

执行:

python3 -m nose tests/parser_tests.py 
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK