选择你自己的 Python 冒险
一位维基教科书用户认为此页面应该拆分为更小的页面,内容更细化。 您可以通过将此大型页面拆分为更小的页面来提供帮助。请确保遵循 命名策略。将书籍分成更小的部分可以提供更多的重点,并允许每个部分专注于一件事情,这对每个人都有利。 |
本书是 双子城 ExCo(实验学院) 课程 比特与字节:编程入门 的教材。
您认为程序员天生就手握键盘吗?程序员是后天造就的,不是天生的——您也可以和最优秀的程序员一样编码。如果您想打破编程周围的障碍和神秘感,加入我们吧!在轻松、无压力的环境中学习编码。
您的辅导员,Gregg 和 Amanda,来自非传统的编程背景,曾经也是新手。我们对技术大拿、自以为是的胡言乱语和极客优越感毫无耐心。
我们的主要项目是一个 Web 应用程序,它允许您玩一个您自己编写的“选择你自己的冒险”游戏!(例如:http://cyoa.lind-beil.net/)。
所有指导都使用 Python 语言进行,Python 是一种免费、开源、跨平台、功能强大且易于学习的语言。我们会帮助您入门,每周介绍新概念。会有动手实践作业,大量时间用于提问,以及轻松的结构化氛围。黑客是关于解放和民主化力量的。
先决条件:能够运行或安装程序的计算机。仅在线也很好,但亲身学习更好(您需要一台笔记本电脑,或者非常强壮的双臂才能搬运您的台式电脑)。
有经验的程序员也欢迎作为学习者或导师。
请告知我们有关行动不便、神经多样性或儿童保育需求的任何要求,我们会尽力满足您的需求。
我们受到了
Kirrily Roberts 的 OSCON 演示 和 Dreamwidth 的 Python vs. Ruby 对决 的启发。
1a. Python,来自 Python 网站
您需要最新的 2.x 系列(可能是 2.6.x)版本,而不是 3.x.x 版本。[1] Python 3 有些区别(主要是在字符串方面),本课程不会涉及。
如果您更喜欢冒险,可以随意尝试页面底部的其他安装
- ActiveState ActivePython(非开源)
- Enthought Python Distribution(用于科学计算的商业发行版)
- Portable Python(Python 和附加软件包配置为从便携式骰子运行)(如果您无法在系统范围内安装 Python,并且需要从 USB 存储器、SD 卡等运行,建议使用此方法)
这些发行版包含我们不会使用的额外模块(代码包)。如果你认真学习 Python,安装这些包比逐个安装要容易得多。使用通常的 Windows 方法安装它。
1b. 测试你的 Python 安装。
start > run > cmd [OK]
这将打开一个 Windows cmd 窗口。在里面,输入 **python**
C:\Documents and Settings\Gregg>python Python 2.4.3 - [some other info, perhaps] >>> 'hello' 'hello'
如果你看到类似这样的东西,那么你就可以了!如果(悲伤),你看到类似
'python' is not recognized as an internal or external command, operable program or batch file.
那么 *python*(可以将 Python 翻译成“计算机指令”的“解释器”程序)不在你的路径上(参见下面的 **将 Python 放入你的路径**)。然后尝试像这样调用它(假设 Python2.6 安装在通常的位置 `C:\Python26`)
\> C:\Python26\python.exe
2a. 安装一个文本编辑器。
包括 Microsoft Word 和朋友在内的文字处理程序对编写代码来说是 *糟糕的*,因为它们混淆了布局格式和文本。*简单更好*。也就是说,记事本也很糟糕,因为它会自动 [2] 在文件名后面追加“'.txt'”,以及其他一些功能。
一个好的编程编辑器的关键功能
- 语法高亮
- 等宽字体
- 多个选项卡式界面。
我们非常喜欢的一个免费(如啤酒和开源)的编辑器是 SciTE [1]。该程序还有一个在 SourceForge 上找到的“无安装”版本。便携版没有安装版的所有功能,但功能相当强大。程序的一个好迹象是它们可以以不需要安装程序的形式存在。这意味着开发人员不想干扰运行的系统,或损坏任何东西,并使其很容易在不喜欢时摆脱程序。
2b. 测试你的安装
双击 SciTE,或从程序菜单中选择它,或以通常的 Windows 方式点击可执行文件。你应该得到一个类似记事本的工作区。
复制并粘贴它
# here is some python code 1 # an int b = 'some string' # string if 2 > 1: print "sure looks bigger"
然后,在菜单中:语言 > Python。代码应该改变颜色,并且各个部分(变量、整数、字符串、注释)将变成漂亮的颜色。
Mac OS X
[edit | edit source]Mac 用户的好消息!Python 作为 Mac OS 的一部分预安装。检查一下
- 在 Finder 中,导航到应用程序 -> 实用工具 -> 终端,启动终端
- 输入 python 并按回车键
- 你将看到类似这样的东西
Python 2.6.2 (r262:71600, Apr 16 2009, 09:17:39) GCC 4.0.1 (Apple Computer, Inc. build 5250)] on darwin Type "help", "copyright", "credits" or "license" for more information. ( >>> print "hello" 'hello'
你可以在 Python 命令提示符中做很多事情,但编写更复杂的问题用文本编辑器会更容易。TextWrangler 是 Mac 用户的一个很好的文本编辑器。从 Barebones 软件网站下载它
两者
[edit | edit source](可选)安装 `ipython`
Ipython 是一个 Python 包,它提供了一个更加友好的命令行环境,其中包括语法高亮、历史记录以及各种调试工具和改进。从 Ipython 网站下载它。
将 Python 放入你的路径
[edit | edit source]为了运行程序,你的操作系统会在不同的位置查找,并尝试将你输入的程序/命令名称与沿途的一些程序进行匹配。这个文件夹和位置列表被称为 *(系统) 路径*。令人困惑的是,*路径* 也指系统上特定文件或目录的路径,根据上下文描述为“完整路径”或“相对路径”。本文讲述的是如何将 Python 解释器放在系统路径上,以便我们的命令行知道用户输入 `python` 时该怎么做。
Windows
[edit | edit source]首先看看该目录是否在路径上。从 Windows `cmd` 提示符
> path
这将输出系统路径。看看 `C:\Python26;`(或等效项)是否在其中。如果没有,你需要添加它。
control panel > system > advanced > |Environmental Variables| > system variables -> Path
这需要包括:`C:\Python26;`(或等效项)。如果你把它放在前面,它将是第一个被查找的位置。你也可以把它添加到最后,这可能更合理。
然后重新启动你的提示符,尝试输入 'python'。如果一切正常,你应该得到熟悉的 `">>>"` 提示符。
Python IDE
[edit | edit source]在本课程/书中,我们不会使用任何 IDE(集成开发环境),因为这是一门关于编程的课程,而不是学习 IDE。对于高级程序员来说,它们可以通过帮助组织代码、自动完成变量名以及其他功能来加快工作速度。Gregg 不使用或推荐这些中的任何一个,并且倾向于在开发工作中使用 Scite 和 Git。
PyScripter 是一款免费的 IDE(适用于 Windows。如果你有编程经验 - 这类似于 Borland Delphi IDE。你可以从 PyScipter 项目网站下载 PyScripter。
还有其他支持 Python 代码的 IDE(Komodo、Eclipse)。
课程
[edit | edit source]课程 0:初识 Python
[edit | edit source]交互式
[edit | edit source]从现在开始,无论何时看到代码块,它都可能意味着“启动你的命令行提示符/终端,输入 `python`。如果这不起作用,请返回到 *安装 Python* |
初尝
print "Hello, world!"
运行完这段代码后,你应该看到
Hello, world!
玩转 `Turtle`
Python 的 `turtle` 模块是对 类似 LOGO 的语言的简单重新实现。
从你的 python 提示符
# import everything from the turtle module
# import: make them available for use
# everything: (mostly) everything (there are some exceptions)
# the turtle module: a collection of functions (actions), constants,
# and other useful stuff that is grouped into one 'space' (a namespace)
# named turtle, for easy of memory, and general good sense
>>> from turtle import *
>>> circle(80) # this will draw a circle with a diameter of 80
>>> reset() # reset the screen
>>> forward(10) # make the turtle go forward 10
所有命令都列在 Turtle 参考文档中
批处理/从文件
[edit | edit source]将这段代码保存到你的最喜欢的文本编辑器中,命名为 'lesson0.py'
print "Look, Ma, Non-interactive!"
然后打开一个提示符,导航(见下文)到你保存文件的位置,运行
prompt> python lesson0.py
导航
[edit | edit source]`cd` *更改目录*。`cd ..` 上移一个目录
`ls|dir` *列出目录的内容*。默认情况下,它列出当前目录。“ls”是 bash/mac;“dir”是 windows。
`pwd` *打印工作目录*。我在哪里?(在 Windows 中,这被拼写为 `cd`(没有参数)。
课程 1:欢迎来到神秘的宅邸
[edit | edit source]没有行号,但可以复制粘贴
## anything from '#' is a comment, and gets ignored.
## all my editorial comments will start with '##' -- GRL
## some text describing what this is
# a simple choose your own adventure
## 'print' prints to the screen.
print "Welcome to MYSTERIOUS MANSION."
print "You are at a mysterious door. The door is clearly marked -- 'Open Me And Die!'."
## in python, strings can be single or double-quoted
print 'Do you want to open the door?'
## raw_input gets input from the user
## Here, we take the input, and *assign* it to a variable called 'ans'
ans = raw_input("please type 'yes' or 'no' ")
## conditionals
## see if the user's answer is interesting or not
if ans=="yes":
print "That was foolish! You are now dead."
## elif means "else-if"
elif ans == "no":
print "That was wise! You are alive, but thoroughly bored."
## else is a 'catch-all' for "any condition not all ready covered"
else:
print "I don't know what to do, based on what you said, which was, |", ans, "|"
print "Thank you for playing!"
## anything from '#' is a comment, and gets ignored.
## all my editorial comments will start with '##' -- GRL
## some text describng what this is
# a simple choose your own adventure
## 'print' prints to the screen.
print "Welcome to MYSTERIOUS MANSION."
print "You are at a mysterious door. The door is clearly marked -- 'Open Me And Die!'."
## in python, strings can be single or double-quoted
print 'Do you want to open the door?'
## raw_input gets input from the user
## Here, we take the input, and *assign* it to a variable called 'ans'
ans = raw_input("please type 'yes' or 'no' ")
## conditionals
## see if the user's answer is interesting or not
if ans=="yes":
print "That was foolish! You are now dead."
## elif means "else-if"
elif ans == "no":
print "That was wise! You are alive, but thoroughly bored."
## else is a 'catch-all' for "any condition not all ready covered"
else:
print "I don't know what to do, based on what you said, which was, |", ans, "|"
print "Thank you for playing!"
作业
[edit | edit source]课程 2:绘制迷宫
[edit | edit source]目标
[edit | edit source]- 嵌套条件语句
- 介绍新的 Python 数据类型,字典
- 函数介绍
我们该如何扩展代码以涵盖更多房间和路径?我们可以使用嵌套条件语句。
## revised mysterious house, using nested conditionals.
print "Welcome to Mysterious House!\n\n"
name = raw_input("What is your name? ").strip().title()
print '''You are in the *foyer*. There is a mirror on the wall. In the mirror,
it says in blood (or possibly ketchup, if you're squeamish\n\n''' + \
name[::-1].upper() + '''
creepy. Very creepy. And MYSTERIOUS!
There is a door'''
ans = raw_input('go (through the door) or stay? ')
if ans == 'go':
print '''you are in a dark hallway. It's creepy, but there is \
a delicious smell from down the hall. You go towards it.
The lit room at the end of the hall is a kitchen. You're ravenous.
There is a cake on the table.
'''
ans = raw_input("eat the cake (yes or no)? ")
if ans == "eat" or ans == "yes":
print "mmmm.... delicious cake"
ans = raw_input( '''You feel guilty. Choose a reason:
a. it's rude to eat someone else's cake
b. you ate earlier, and were still pretty full
c. you're allergic to cake\n\n''')
if ans=='a':
print "You're right, it is rude"
elif ans=='b':
print "Well, it's not like there is tupperware around to take it for later"
else:
ans = raw_input( "Oh no! What kind of allergy? [gluten or anaphalectic]? " )
if ans[0] == 'g':
print '''THE ORACLE PREDICTS.... soon you will need to find a Mysterious...........
bathroom.
'''
else: # no cake
print '''No cake? REALLY! Instead you drink beer, pass out, and \
are eaten by a grue'''
else: # no door
ans = raw_input('yes or no? ')
if ans == 'yes':
print '''I see you are a person of action! Too bad you're hanging about in \
a foyer!'''
else:
print '''I sometimes get that way in the winter too'''
print "\n\nThank you for playing,", name
练习
- 这种方法的优缺点是什么?
- 假设你想给用户第二次机会。如果他们选择 "停留",然后 "是",就让他们通过门。你将如何实现这一点?
- 这种方法如何扩展到数百个房间?
- 转向地图
- 重写代码,对房间/状态进行编号。
- 制作游戏所有可能路径的流程图。
0 级:就像纸质字典一样,有条目和定义。就像纸质字典一样,这会给特定的定义名称,方便查找。查找 "octothorp" 的定义比记住 "octothorp" 的定义在第 861 页更容易。在 Python 中,我们称条目为键,定义为值。dict
s 在 Python 中用于许多任务,包括索引、图形和数据存储。
1 级:在 Python 中,有很多方法可以构建字典。以下是一种方法。
symbol_names = { '#': 'octothorp', '!': 'exclamation point', '?': 'question mark', ' ': 'space', ',': 'comma', '.': 'full stop', ':': 'colon' }
我们可以使用它来打印出句子中的字母标点符号,如下所示。
for letter in "Here is my sentence. It has grawlix: #!?!": # there is shorthand for the next for line: dict.get(thing, default) # print symbol_names.get(letter,letter) if letter in symbol_names: print symbol_names[letter] # [] 'indexes' the dict # some_dict[key] -> get value for key in some_dict # by analogue, some_dict[key] = value sets it. else: print letter
2 级:Python 字典(dicts)并不“真正”像纸质字典。
a. dicts 没有固有的顺序,尤其是没有字母顺序。在内存中,'octothorp' 之后的东西可能是 '2'。把它想象成一个人的厨房。到处都是有标签的抽屉。当你想要找东西(比如搅拌勺)的时候,厨房主人会说,“那些在第 13 个抽屉里,我去拿一个”。第 13 个抽屉里有什么并不重要。
b. 键不必是字符串,值可以是几乎任何东西,包括字符串、列表、对象、函数和其他字典。但是键必须是不可变的。
3 级:在其他语言中,dicts 被称为(不同的)哈希、关联数组或映射(映射)。Map(ping) 强调“对应”方面。关联数组显而易见(将标识符与数组中的位置关联),但为什么是“哈希”?好吧,事实证明,它不仅仅是喜欢早餐肉,或者阿姆斯特丹。除其他外,“哈希”是一个函数,它以一种系统的方式接收数据并返回一个字符串。例如,哈希函数first_char(str) -> str
就像纸质字典使用的哈希。它存在的问题是,字典的某些部分很大(例如s 和t),而有些(例如,q, x, z)则很小。
在计算方面,哈希均匀要好得多,这意味着输出均匀地分布在答案空间中。回到我们之前提到的厨房示例,均匀哈希很重要,这样就不会有特定的抽屉被装得过满。dicts 的查找速度很快,因为它们创建了很多浅抽屉。如果你能快速确定需要查看哪个抽屉,并且每个抽屉里的东西不多,那么查找特定项目就很容易。在计算复杂度方面,查找和条目为O(1)。
练习:
“把所有内容整合在一起”
将游戏流程图转换成一个列表字典,如下所示。
gamemap = {1: [2,3], 2: [4,5]}
a. 创建:创建数字名称的字典,然后创建一个打印输入字符串中每个数字名称的函数。
b. 使其健壮:使其能够处理输入的整数,并剥离所有非数字,以便像 1111 和 '2 and 1 is 3' 这样的输出可以得到很好的处理。
- 函数
- 签名
- 参数
- 命名和位置参数
- 默认值
- 打印不是返回
- 文档字符串
- 递归函数
- (参见之前的直接内容)
- 导入
- 随机性(PRNGs)
- 数据类型
bool
布尔值(True/False)list
列表None
无
函数和数据结构是编程的双重基础。在核心编程中,主要是处理状态并改变状态。因此,我们在这里将花一些时间深入讨论函数,详细讲解语法细节,而这些细节我们之前一直避免。
在 Python 中,函数定义用以下方式表示。
- def :: 告知 Python 我们正在定义一个函数。
- function_name :: 有效标识符[3],用于命名函数。
- ([可选的命名和位置参数])
- 括号是必需的,但函数可能接受也可能不接受参数,这些参数是函数的执行时参数,可以影响函数的运行方式。
- Python 支持命名和位置参数,以及可选值,[4] 我们将在下面详细介绍。
- ':' :: 冒号字符。
- 一行或多行代码。如果你想要一个什么也不做的函数,请使用
pass
语句。 - 所有函数都返回值。默认情况下,返回值为特殊值
None
。
函数签名简单地用伪代码[5] 来描述函数的输入和输出,以一种方便的速记形式表示。例如,查看标准库中的范围。
range([start,] stop[, step]) -> list of integers
方括号中的参数意味着这些是可选的参数。因此,如果给定一个参数,它将进入“stop”槽,如果给定两个参数,则为“start”、“stop”,如果给定三个参数,则为“start”、“stop”、“step”。
我们已经遇到过一个 Python 函数:raw_input(prompt) -> string
。这是 Python 语言标准发行版中众多“内置”函数的一个例子(因此,始终可用)。其他函数位于需要导入的模块中。一些模块随 Python 一起提供(标准库),或者你可以自己创建,或者使用从互联网下载的模块。[6] 使用内置函数非常容易,创建你自己的函数也并不困难!
函数案例研究:游荡的格鲁
假设你想要在你的神秘房子里放一个游荡的格鲁,它会随机出现在一个房间(或多个房间)里。
一种方法是创建一个函数,决定格鲁是否在房间里。
测试框架
在我们的完整代码中,稍后我们将看到类似以下的代码。
eaten=grue()
if eaten:
print "Sadly, you were torn limb-from-limb by the Grue and suffered a slow, painful death."
else:
print "Congratulations, you have not been eaten by the Grue! May you have a long happy life."
在这里,我们引入了一种新的 Python 数据类型:boolean
。Python 布尔值可以取两个值True
和False
。这些代码片段是等效的。
if x > 3: print "yep!" if x > 3 is True: print "yep!" if bool(x>3) is True: print "yep!"
“if” 语法暗示如果谓词为 True。在 Python 中,大多数内容都计算为True
,除了:None、False、0(0.0 等)、空字符串、零长度列表、字典以及其他一些奇特的例外[7]。
变体 1:不太随机
def grue_always():
''' this grue always appears. returns true'''
return True
我们不太随机的格鲁总是出现。
练习:创建反向情况 -- 一个永远不会出现的格鲁。函数的签名应该是grue_never() -> False
变体 2:50/50 格鲁
import random
## random is a Python module, as mentioned above.
## We need to import it to access the random() function.
## now to begin the function definition
## Everything inside the function definition is indented! Remember, white space matters!
def random_grue():
''' boolean. a grue that appears 50% of the time '''
## we want something that will return True 50% of the time.
## one method: get a random float between (0,1), and return True if it's over .5
## now we need a random number. random() will give us one between 0 and 1
n=random.random() ## the random before the dot tells Python what module to look in for the function, which is the one we imported above
if n > 0.5:
grue=1 ## 1 == True
else:
grue=0 ## 0 == False
return grue ## returning allows us to capture the value
那么random_grue()
函数做了什么?让我们试一试。在 Python 解释器中
>>> import random >>> def random_grue(): n=random.random() if n>0/5: grue = 1 else: grue = 0 return grue >>> random_grue() 1
第一个命令是导入随机模块,第二个命令是定义函数,第三个命令是实际调用函数。1 是函数的返回值。你可能会得到 1 或 0,具体取决于random()
生成的数字。(提示:尝试运行它几次)
变体 2:喜怒无常的格鲁
import random
def grue_moody(cutoff=.5):
''' boolean. a grue that appears (100*cutoff)% of the time '''
n=random.random()
above_cutoff = n < cutoff
return above_cutoff
def grue_moody2(cutoff=.5):
''' boolean. a grue that appears (100*cutoff)% of the time '''
return random.random() < cutoff
请注意,我们通过直接返回布尔值(尤其是在 'grue_moody2' 中)简化了函数,而不是执行任何条件逻辑来获得 1 或 0。另外请注意,我们为参数cutoff
指定了默认值。
练习
- 预测这些函数调用的行为。然后尝试它们。
- grue_moody()
- grue_moody(-1)
- grue_moody(1)
- grue_moody("a")
- grue_moody([1,2,3])
- 修复代码,以便如果 n 超出区间(0,1),则打印一条愤怒的消息并返回
None
。 - 尝试
help(grue_moody)
。你看到了什么? random
、random.random
和random.random()
的类型是什么?
变体 3:位置,位置,位置格鲁
在我们的最终变体中,我们想要一个格鲁,它
- 在不同页面上出现的百分比不同。
- 在主房间 "foyer" 中出现的概率应该为零。
- 在没有其他描述的房间中,出现的默认概率应该为 5%。
import random
grue_fractions = { 'foyer':0, 'thedark': 1.0, 'nocake': .2 }
def location_grue(room=None, base=.05, cutoffs=dict()):
''' (boolean), does a grue appear in the room?
room : str room name
base : 'cutoff', float between (0,1), for base (room not found)
cutoffs: dict of room_name: cutoff (float between 0,1)
'''
cutoff = cutoffs[room]
return random.random() < cutoff
练习
- 按照编写,
location_grue
有一些错误,并且没有达到规范。找出并修复这些错误。- 尝试:location_grue('foyer', cutoffs=grue_fractions)
- 如果 'room' 不在 'cutoffs' 中会发生什么?
- 研究字典的 'get' 方法...
help({}.get)
。使用此方法修复代码。
- ↑ 截至 2009 年 9 月 9 日:选择 **Python 2.6.2 Windows 安装程序(Windows 二进制文件 - 不包含源代码)**
- ↑ **自动地**:**黑客。** 自动地,仿佛是魔法。 就像在《幻想曲》中,这可能是一件福祸相依的事情。
- ↑ 在 Python 中,有效的标识符以字母或 _ 开头,然后包含一个或多个数字、非空白字符或数字。
- ↑ 更多详细信息:Python 函数定义
- ↑ 这个想法是,伪代码表示法应该抽象出一些 Python 特定的细节,这样程序员就可以更容易地谈论问题的核心,而不是修饰。
- ↑ Python 在 Cheeseshop 上维护着一个半官方的这些库的仓库。许多其他软件包,例如 Numeric Python、SqlAlchemy、Django 和 NetworkX 都有自己的网站。
- ↑ https://docs.pythonlang.cn/library/stdtypes.html#truth-value-testing
模块是任何包含 Python 定义和语句的文件,我们可以从其他 Python 程序访问它们。要导入模块,它必须位于 标准库 中,[1] 在 PYTHONPATH
上,或者是在与正在运行的 python
进程相同的目录中的文件。让我们探索一下 random
模块,我们将在我们的游荡 Grue 中使用它!
# random is in the standard library >>> import random >>> dir(random) ['BPF', 'LOG4', 'NV_MAGICCONST', 'Random', 'SG_MAGICCONST', 'TWOPI', 'WichmannHill', '_BuiltinMethodType', '__all__', '__builtins__', '__doc__', '__file__', '__name__', '_acos', '_cos', '_e', '_exp', '_floor', '_inst', '_log', '_pi', '_random', '_sin', '_sqrt', '_test', '_test_generator', 'betavariate', 'choice', 'cunifvariate', 'expovariate', 'gammavariate', 'gauss', 'getstate', 'jumpahead', 'lognormvariate', 'normalvariate', 'paretovariate', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'stdgamma', 'uniform', 'vonmisesvariate', 'weibullvariate']
您也可以输入 >>> help(random)
,或者从命令行(而不是 Python 解释器)
$ pydoc random $ pydoc random.shuffle # for example
查看完整的 **文档字符串** 帮助文件。
注意列表中出现了函数 random()。要从模块中调用函数,您需要告诉 Python 您正在使用哪个模块,就像我们在上面的函数中使用 Grue 一样。
>>> import random >>> random.random() 0.73015823962912774 >>> random.randint(2,800) 158
如果您想知道每个函数的作用以及如何使用它,请查看您要使用的模块的 Python 文档。以下是 random 模块的文档:https://docs.pythonlang.cn/library/random.html
练习
- 进一步探索
import
。在某个目录中,将此文本复制到名为 my.py 的文件中_sekrit = True var = 1 def f(): return "yep!"
在该目录中打开一个 python 提示符。尝试运行这段代码
import my import my as also_my my.var is also_my.var print my.var print also_my.f() print my._sekrit print _sekrit print var print f() from my import * print _sekrit print var print f()
问:
_sekrit
发生了什么?import * from my
到底做了什么?
查看递归函数
You loop, until it's time not to loop. -- adpated from Patrick Swayze (RIP), Roadhouse
**递归函数** 是从其定义内部调用自身的函数。令人困惑!请注意:递归是有点棘手的东西,所以如果您花点时间理解它,不要担心。以下是一个递归函数的示例
def yesorno():
ans=raw_input("Yes or No? ").lower()
if ans=='yes':
print "Aren't you a yes man!"
elif ans=='no':
print "Why are you so disagreeable?!"
else:
print "You said:", ans, ". Answer the question!!!"
yesorno() # recursive call, 'R'
请注意,在最后,我们在点 'R' 处从函数定义内部调用函数 yesorno()
。Python 如何知道如何处理一个尚未定义的函数?!
答案是:它不知道。它也不需要知道。解释代码有多个阶段。在 *定义* 阶段(当您定义函数时),Python 检查代码是否存在任何语法错误,例如不正确的缩进,或者您输入 'pring' 而不是 'print',但它不会真正 *执行* 代码中的任何内容。它看到 def yesorno():
,并将令牌“yesorno”分配为对函数的引用(由定义定义)。yesorno
就像任何其他变量一样。由于它在语法上是正确的,因此它会继续遍历代码。只有当您调用函数(*执行*)时,Python 才会关心它是一个函数的事实,并且会像这样执行它,因为它已经定义了!
在继续之前,请尝试这段代码。给出一些错误的答案。
这里递归的目的是为了让问题“是或否?” 继续被询问,直到用户输入 'yes' 或 'no'。与我们在第 1 课中使用的原始条件/分支逻辑相比,此提示的行为更好(并且更健壮)。
以下是如何在“选择你自己的冒险”游戏中使用递归函数的更复杂示例
def move(choices):
## choices should be a list in our dictionary of pages, here named "book"
for x in choices:
print x ## this displays the choices to the player
print "" ## print a blank line, for looks
text = "What next? "
ans= raw_input(text)
ans.lower().strip()
if ans in choices: ## check through the list "choices" to see if the input is valid
return ans ## return choice
else:
print "That answer,", ans, ", isn't in the choices"
return move(choices) ## keep calling the function until the user inputs a valid answer
这里有两点需要注意 - 函数接受一个参数“choices”,并且在函数定义结束时调用函数时,它使用参数 choices 调用该函数。在 Python 解释器中,像这样定义名为 book 的字典(您可以自由地发挥创意并更改页面等)
>>>book={ 'foyer' : {'desc' : "Some text", 'choices' : ['bathroom','kitchen',], }, 'bathroom' : {'desc':"Some text" , 'choices' : ['change toilet paper','leave'] }, 'kitchen' : {'desc':"Some text", 'choices' : ['eat the cake', "don't eat the cake"]}, 'eat the cake':{'desc':"Some text",'choices':['have a glass of water','wash the plate']}, "don't eat the cake":{'desc':"Some text",'choices':['put the cake in the fridge','sit down']}, }
然后像在上面的示例中一样定义函数。像这样调用函数
>>> move(book['foyer']['choices'])
发生的事情应该看起来像这样
>>> move(book['foyer']['choices']) bathroom kitchen
What next?
输入除列出选项以外的其他内容。发生了什么?输入列出选项之一。然后会发生什么?
现在尝试像这样定义函数
def move(choices):
for x in choices:
print x
print ""
text = "What next? "
ans= raw_input(text)
ans.lower().strip()
if ans in choices:
return ans
else:
print "That answer,", ans, ", isn't in the choices"
return move()
像以前一样调用函数。尝试给它一个未列出的选择。发生了什么?您应该会收到类似这样的错误
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 12, in move TypeError: move() takes exactly 1 argument (0 given)
问题在错误的最后一行解释了。我们定义的函数接受一个参数,但是当我们第二次调用它(通过给出无效的选择)时,我们没有给它任何参数,所以它就乱了套。
- 课间复习
- CYOA 数据和动作模型
- 使用命令行调用脚本
**首先!!!**,完成 课间复习
计算机程序由模拟问题空间的 **数据** 和操作(操作、转换和显示)这些数据的 **函数** 组成。
想象一下,您正在拿着一本“选择你自己的冒险”书籍。
- 它是什么?它的组成部分是什么?答案
它是一本由 **页面** 组成的纸质 **书籍**。它有 **封面** 和 **封底**。这些 **页面** 包含文本,故事的一部分,或者可能是像版权页、编目信息等特殊页面。
- 你怎么知道从哪里开始?答案
按照惯例,页面从英文书籍的 *前面* 开始
- 第 1 页有 *意义* 吗?答案
没有,这仅仅是惯例。
- 每页的组成部分是什么?答案
页面包含 **页码**、一些 **文本** 和 **选项**
让我们利用我们在现实世界中的领域知识来创建一个实用的数据模型。我们的数据模型应该足够复杂,能够对领域进行建模,但不要过于详细。如果以后需要更详细,我们可以随时添加。
对于这些部分中的每一个,请确定以下几点:
- 有多少个?零个、零个或更多个(0+)、(正好)一个、一个或更多个(1+)或其他确切的数字?
- 如何识别它们?通过唯一 ID(U)、索引位置或其他方式?
- 该项是否包含在/是另一个结构的一部分?它有子项或父项吗?
- 书
- 页
- 页码
- 描述
- 选择
- 书:一个(正好),包含页面
- 页:0+,唯一 ID,是书的一部分,包含描述等。
- 页码:int(在 CYOA 书中),是页的一部分,由选择引用。必须是唯一的。
- 描述:1,是特定页的一部分,由文本组成
- 选择:0+,属于页面。在纸质 CYOA 中,这些由索引标识(第一个选择、第二个选择等)。每个包含一些文本和要转到的下一页的 ID。
将所有这些放在一起,一个选择你自己的冒险游戏的模型可能是
DATA MODEL FOR A CYOA, a kind of decision tree Book CONTAINING pages CONTAINING (1+) unique id (1) description (1) words in paragraphs (0+) choices (0+ -> leaf/ending, 1+ -> node) choice text / description / information jump / pointer to another unique id
接下来,我们将这些想法映射到 Python 数据类型上
Book = dict() with key:value -> pageid:Page Page = dict() with key:value pageid: str desc: str choices: iterable/sequence of choices (list, dict, or set) choice contains: choicetext # str, something like "Go through the door" pageid # pointer to another page
这里是一个我们游戏的数据结构示例。
TheCaveOfTime = {
1: dict(
desc='you enter the cave of time',
choices = [
('go into the cave', 81),
('fall asleep', 'the dark'),
]
),
81: dict(
desc='Wow, a cave of wonders!',
choices = [],
),
'the dark': dict(
desc="You're eaten by a grue",
choices = []
),
}
为了简单起见,我们忽略了一些部分(版权、封底等)。但如果需要,可以轻松地将它们添加回来。
练习
- 请注意,我们使用字符串和整数混合来命名我们的页面。这种方法有什么问题?有什么好处?总的来说,这样做明智吗?如果不是,有什么更好的方法?
- 允许使用 "the dark" 这样的标记作为房间名称会导致问题吗?
附加题
- 回顾一下,我们的“游戏数据”只是一个字典,而且将新项添加到字典中非常容易。使用它将 COPYRIGHT、INTRODUCTION 和 AUTHOR INFORMATION 添加到我们的游戏中。答案
一个想法是拥有“特殊”页面,我们通过约定来定义它们为“AUTHOR”、“COPYRIGHT”和“INTRODUCTION”,如下所示
TheCaveOfTime = { 'AUTHOR': 'Jane Q. Fancypants', 'COPYRIGHT': 'copyright 2009, Creative Commons License', 'INTRODUCTION': 'It was a long and tedious evening...', }
当然,在这个模式中,我们必须确保没有任何选择指向这些键,否则会造成混乱。Python 的一个信条是“假设我们都是成年人”。现在,我们可以让选择指向“AUTHOR”,但我们必须相信自己会很明智,不要这样做。语言或数据结构中没有任何东西实际上强制执行这一点。在这个示例中,明智的做法是制定一条(人为)规则,例如“所有大写字母表示这不是一个真实的页面 ID,而是一些特殊数据,所以不要让任何选择指向这里”。
- 想象一下你手持这本书。人们会做什么样的**动作**来与故事互动?
你的答案可能包括
- 从入口(第 1 页)开始阅读书籍
- 阅读页面
- 查看下一步去哪里的选择
- 从选择中进行选择
- 分支/移动
- 这本书如何结束?如何知道何时停止阅读?
页面上没有选择。书中可能还会有一个类似“THE END”的信息,表示结束。
让我们使用伪代码来对游戏流程进行建模。这个伪代码将帮助我们确定需要创建哪些函数以及它们应该接受哪些参数。任何编程任务都可以用无数种方法完成,我们将在整个过程中表明这一点,以及我们的伪代码的临时性。如果需要,我们甚至可以把所有东西都扔掉,从头开始!
GAME FLOW display_description(roomid) # print to screen print Book[roomid]['desc'] print choices (number them?) next_room_id = user_choose( choices? ) [repeat until user QUITS or we reach an ENDING! (An ending means that the user has no place to go)
查看 此处的代码。警告!该链接后面有一些糟糕的代码。想想它有什么主要错误,以及如何修复和改进它。
练习
- 请注意,
move()
现在是基于while
的,而不是递归的。 - 编写一个“页面”字典,它将使
check_pages
失败 - **高级**。使用你最喜欢的搜索引擎,调查文件末尾的“if '__name__' == 'main'" 内容。提示
- 启动交互式 Python。然后:
print __name__
__main__
- 它与命令行使用和导入有关
- 启动交互式 Python。然后:
(如果名称为主,则导入上下文等等)-- TODO
- 了解 HTTP 请求
- 介绍 web.py
所以,你认为你了解网络。当然,你可以上网冲浪,发送 推特,阅读 电子邮件,并在 Level 50 皮肤科医生 的 魔兽世界 中进行控制,但你知道幕后发生了什么吗?
人物表(玩家)
- 米妮,明尼阿波利斯的一位网络用户
- 浏览器,一个网络浏览器
- 伺服器,一个网络服务器程序
- 麦克,黄刀镇的一台电脑
- 黄雪,黄刀镇的 NSP(网络服务提供商)
幕布拉开。米妮在她的网络浏览器中输入 http://sendwarmclothes.org/snowpants/send/
。填写表格后,她收到一封电子邮件和一条短信,上面写着“别担心!长裤正在路上!”,几周后,长裤就送到了。人群欢呼,双腿暖和了!
那么,幕后发生了什么?
- 当米妮在浏览器中输入 URL 并按回车键时,一些魔法就会发生,域名
http://sendwarmclothes.org/
将被转换为(解析)IP 地址。浏览器程序会创建一个 HTTP 请求,它只是一个格式特殊的文本消息,浏览器程序会尝试发送它。这个消息中包含很多信息……请求来自哪里,请求的 URL 主机和路径,时间戳和其他管理信息等等。 - 更多路由魔法发生,请求通过电线和狗拉雪橇到达黄刀镇,那里是托管“sendwarmclothes.org”网站的电脑麦克的家。
- Mac 上运行着许多程序。它可以处理电子邮件,有一个用户喜欢的流行 Boggle 程序,并且产生的热量足以让 YellowSnow 的员工围着它取暖。 “Send Warm Clothes” 的员工实际上住在圣地亚哥,那里的冬季服装非常便宜,但他们决定如果他们的网站托管在加拿大西部,他们会更有“雪地”信誉。 除了这些忙碌而充实的生活,Mac 还运行着一个名为 Servo 的程序,这是一个 web 服务器程序。 有些 web 服务器包括 Apache 和 IIS,但还有很多其他的。
- Servo 的工作是监听一组特定的 端口(其中包括:80 用于 HTTP 和 443 用于 HTTPS),并在这些端口上收到任何消息(HTTP 请求)时做出响应。
- 0 级:从概念上讲,响应非常简单。 当收到 HTTP 请求时,服务器会识别这些特殊消息的格式,对其进行解码,并返回一个格式特殊的文本消息(响应)作为回复。
- 1 级:神奇之处在于在响应的主体中放入特殊的东西。 作为响应的一部分,它描述了 它返回的特殊文本类型,例如:
text/html
、video/quicktime
或application/msword
。 浏览器的工作是弄清楚如何处理这些数据。 这可能涉及打开另一个程序(如 Adobe Acrobat、OpenOffice),将其发送到插件(如 Flash),或将文本显示到屏幕上。 - 2 级:理解如何处理 URL 请求路径(
/snowpants/send/
部分)的最常见(也是最初/原始)方法是将其映射到文件系统,并从some_internet_root_dir/snowpants/send/
返回某个文件。 假设 Servo 在 Mac 上有一个基本 web 目录,位于Mac::/home/serve/www/
。[2] 它使用此目录作为所有文件服务请求的基础。 然后,如果 Servo 是一个 Apache 服务器,它将尝试返回一个 html 响应,其中填充了Mac::/home/serve/www/snowpants/send/index.html
的内容。 - 3 级:然而,Servo 是一款解放的、自由思想的、现代的 web 服务器,用 Python 编写。 他住在城市里的公寓里,有一份不错的工作,他自食其力! Servo 意识到URL 只是数据,他可以根据需要做出任何合理的响应。 他没有试图找到文件,而是被编程为将 URL 解析成一系列操作:
send > snowpants
。 Mac 上没有任何名为 snowpants 的目录。
- Servo 作为现代 web 服务器的典范,被编程为将请求 URL 解释为一系列指令(
send > snowpants
),并执行一系列操作。 它向 Minnie 的手机发送一条短信,向圣地亚哥的仓库发送一封电子邮件,并构建一条文本响应发送回 Minnie 的电脑。 文本响应包含一些描述 Servo 操作的 html 文本,并鼓励 Minnie 勇敢地努力,直到长裤到达。 它将响应发送回原始请求中的 IP 地址。 - 响应返回到明尼阿波利斯。 Brow 正确地将其解释为 html,并将其打印到屏幕上,Minnie 看到它。
示例 URL
来自 6pm.com,一家鞋类零售商
http://www.6pm.com/search/shoes/filter/productTypeFacet/"Shoes"/gender/"womens"/subCategoryFacet/"Knee+High"/size/"7"/colorFacet/"Brown"/categoryFacet/"Boots"/page/1
来自 MapServer 应用程序生成的 URL 的一部分
http://maps.work.com/cgi-bin/mapserv?map=/bucket/websites/htdocs.maps/mapfiles/mymap.map&mode=map&layers=State&map_imagetype=png&mapext=388109.29996044+4885946.6306564+553109.29996044+5067446.6306564&imgext=388109.29996044+4885946.6306564+553109.29996044+5067446.6306564&map_size=825+907&imgx=412.5&imgy=453.5&imgxy=825+907
练习
[edit | edit source]- 这些 URL 中的模式是什么?
- 看看 6pm 的例子。 哪个 Python 数据结构很容易映射到 URL 的各个部分?
- 看看标准库中的 urlparse 模块。 使用
urlparse.urlparse
和urlparse.parse_qs
函数解析这些 URL
Web.py
-- 一个简单的 Web 框架
[edit | edit source]Web.py 是一个用 Python 编写的web 框架,我们将用它为我们的 CYOA 应用程序创建一个基于 web 的前端。 web 框架是一组模块和函数,它们可以自动化和简化 http 请求解析和 http 响应生成。 通常情况下,这些框架包含一些处理语言和文件类型编码、生成正确的错误代码、解析 URL 请求以及其他类似的繁琐细节的模块。 网站作者负责样式、内容、用户交互以及其他特定于网站的细节。
我们使用 web.py
,因为它非常容易设置,并且入门门槛很低。 其他框架可能扩展性更好,或者拥有更多功能。 对于像我们正在制作的这样的简单网站,我们希望尽快开始编码。
Web.py 中的 Hello World!
[edit | edit source](这些说明略微修改了 web.py 上的说明,以方便 Windows 用户安装)
- 访问 web.py。
- 下载 web.py 代码的 tarball 或 zip 文件。 如果你不知道 tarball 是什么,那么你想要 zip 文件。
- (zip 特定)使用你喜欢的解压缩工具解压缩 zip 文件。 在 Windows 中,你可能需要双击该文件,或右键单击并选择“打开方式 > 压缩(zip)文件夹”。
- (tarball)
tar -zxvf webpy.folder.name.tgz
- 在某个地方创建一个项目目录 /path/to/webcyoa
- 将
web.py
代码的 web 子目录复制到你的项目目录 cd /path/to/webcyoa
- 创建一个包含 /Webpy Hello World 内容的文件
code.py
- 从这里开始,你就可以使用 web.py 主页,你可以在 教程 中找到更多帮助。
- 启动 web 应用程序!
cd /path/to/webcyoa
- python code.py
- 你应该看到类似
http://0.0.0.0:8080/
的内容。 如果是这样,你的 web 服务器现在正在 Localhost(见下文)上运行,并监听 8080 端口。 - 如果你收到
ImportError
,请确保你的项目目录中存在web
文件夹。
- 你应该看到类似
- 启动你的 web 浏览器,并输入 https://127.0.0.1:8080
- 你应该看到“Hello World”。
- 在你的 shell 窗口中,你应该看到类似
127.0.0.1:1658 - - [31/Oct/2009 10:33:24] "HTTP/1.1 GET /" - 200 OK
的内容。
Localhost
[edit | edit source]Localhost 是谁,他到底在托管什么? 为什么要在我的电脑上?
localhost 只是你自己电脑的常规名称。 IP 地址 127.0.0.1
保留给本地机器。 0.0.0.0
(对于细心的人来说)是一个特殊的地址,它绑定到所有具有 IP 地址的本地接口。 你可以慢慢地学习细节,但这里的意思是它在本地运行。 来自你机器外部的请求可能无法正常工作。
GET 和 POST
[edit | edit source]通常的 http 请求是 GET 地址。 当你在页面上提交表单时,它会触发 POST 请求。 因此,如果你希望 webpy 类在提交表单时做出不同的响应,请为其指定 POST 方法。 还有其他 http 请求类型,包括 PUT 和 DELETE,但它们并没有在现实中流行起来。
练习
[edit | edit source]- 研究 Python 类。 使用你喜欢的搜索引擎。 带着问题来。
- 更改
hello.GET
方法的方法,使其打印“Hello, the current time is.... [now]”,其中 [now] 是当前时间(参见:time.time()
) - 探索如何根据教程提供静态内容。
第 6 课:Web1.0,MysteriousHouse.com
[edit | edit source]目标
[edit | edit source]- 接口的概念
- 使代码抽象化
- 从命令行到 web 应用程序
课程
[edit | edit source]作业
[edit | edit source]- 扩展性和可修改性