跳轉至內容

選擇你自己的 Python 冒險

25% developed
來自華夏公益教科書,開放世界開放書籍

本書是 雙子城 ExCo(實驗學院) 課程 位元與位元組:程式設計入門 的教材。

您認為程式設計師天生就手握鍵盤嗎?程式設計師是後天造就的,不是天生的——您也可以和最優秀的程式設計師一樣編碼。如果您想打破程式設計周圍的障礙和神秘感,加入我們吧!在輕鬆、無壓力的環境中學習編碼。

您的輔導員,Gregg 和 Amanda,來自非傳統的程式設計背景,曾經也是新手。我們對技術大拿、自以為是的胡言亂語和極客優越感毫無耐心。

我們的主要專案是一個 Web 應用程式,它允許您玩一個您自己編寫的“選擇你自己的冒險”遊戲!(例如:http://cyoa.lind-beil.net/)。

所有指導都使用 Python 語言進行,Python 是一種免費、開源、跨平臺、功能強大且易於學習的語言。我們會幫助您入門,每週介紹新概念。會有動手實踐作業,大量時間用於提問,以及輕鬆的結構化氛圍。駭客是關於解放和民主化力量的。

先決條件:能夠執行或安裝程式的計算機。僅線上也很好,但親身學習更好(您需要一臺筆記型電腦,或者非常強壯的雙臂才能搬運您的臺式電腦)。

有經驗的程式設計師也歡迎作為學習者或導師。

請告知我們有關行動不便、神經多樣性或兒童保育需求的任何要求,我們會盡力滿足您的需求。

為什麼又是另一本 Python 書?

[編輯 | 編輯原始碼]

我們受到了

Kirrily Roberts 的 OSCON 演示Dreamwidth 的 Python vs. Ruby 對決 的啟發。

關於作者

[編輯 | 編輯原始碼]

安裝 Python

[編輯 | 編輯原始碼]

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 的一部分預安裝。檢查一下

  1. 在 Finder 中,導航到應用程式 -> 實用工具 -> 終端,啟動終端
  2. 輸入 python 並按回車鍵
  3. 你將看到類似這樣的東西
 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]

初嘗

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]
  1. 巢狀條件語句
  2. 介紹新的 Python 資料型別,字典
  3. 函式介紹

巢狀條件語句

[編輯 | 編輯原始碼]

我們該如何擴充套件程式碼以涵蓋更多房間和路徑?我們可以使用巢狀條件語句。

## 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

練習

  1. 這種方法的優缺點是什麼?
  2. 假設你想給使用者第二次機會。如果他們選擇 "停留",然後 "是",就讓他們透過門。你將如何實現這一點?
  3. 這種方法如何擴充套件到數百個房間?
  4. 轉向地圖
    1. 重寫程式碼,對房間/狀態進行編號。
    2. 製作遊戲所有可能路徑的流程圖。

Python 字典

[編輯 | 編輯原始碼]

0 級:就像紙質字典一樣,有條目定義。就像紙質字典一樣,這會給特定的定義名稱,方便查詢。查詢 "octothorp" 的定義比記住 "octothorp" 的定義在第 861 頁更容易。在 Python 中,我們稱條目為,定義為dicts 在 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就像紙質字典使用的雜湊。它存在的問題是,字典的某些部分很大(例如st),而有些(例如,q, x, z)則很小。

在計算方面,雜湊均勻要好得多,這意味著輸出均勻地分佈在答案空間中。回到我們之前提到的廚房示例,均勻雜湊很重要,這樣就不會有特定的抽屜被裝得過滿。dicts 的查詢速度很快,因為它們建立了很多淺抽屜。如果你能快速確定需要檢視哪個抽屜,並且每個抽屜裡的東西不多,那麼查詢特定專案就很容易。在計算複雜度方面,查詢和條目為O(1)

練習:

“把所有內容整合在一起”

將遊戲流程圖轉換成一個列表字典,如下所示。

   gamemap = {1: [2,3], 2: [4,5]}

a. 建立:建立數字名稱的字典,然後建立一個列印輸入字串中每個數字名稱的函式。

b. 使其健壯:使其能夠處理輸入的整數,並剝離所有非數字,以便像 1111 和 '2 and 1 is 3' 這樣的輸出可以得到很好的處理。

第 3 課:構建更好的解析器

[編輯 | 編輯原始碼]
  1. 函式
    • 簽名
    • 引數
      1. 命名和位置引數
      2. 預設值
    • 列印不是返回
    • 文件字串
  2. 遞迴函式
    • (參見之前的直接內容)
  3. 匯入
  4. 隨機性(PRNGs)
  5. 資料型別
    • 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 布林值可以取兩個值TrueFalse。這些程式碼片段是等效的。

   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指定了預設值

練習

  1. 預測這些函式呼叫的行為。然後嘗試它們。
    • grue_moody()
    • grue_moody(-1)
    • grue_moody(1)
    • grue_moody("a")
    • grue_moody([1,2,3])
  2. 修復程式碼,以便如果 n 超出區間(0,1),則列印一條憤怒的訊息並返回None
  3. 嘗試help(grue_moody)。你看到了什麼?
  4. randomrandom.randomrandom.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


練習

  1. 按照編寫,location_grue 有一些錯誤,並且沒有達到規範。找出並修復這些錯誤。
    1. 嘗試:location_grue('foyer', cutoffs=grue_fractions)
    2. 如果 'room' 不在 'cutoffs' 中會發生什麼?
    3. 研究字典的 'get' 方法... help({}.get)。使用此方法修復程式碼。

參考文獻

[編輯 | 編輯原始碼]
  1. 截至 2009 年 9 月 9 日:選擇 **Python 2.6.2 Windows 安裝程式(Windows 二進位制檔案 - 不包含原始碼)**
  2. **自動地**:**駭客。** 自動地,彷彿是魔法。 就像在《幻想曲》中,這可能是一件福禍相依的事情。
  3. 在 Python 中,有效的識別符號以字母或 _ 開頭,然後包含一個或多個數字、非空白字元或數字。
  4. 更多詳細資訊:Python 函式定義
  5. 這個想法是,偽程式碼表示法應該抽象出一些 Python 特定的細節,這樣程式設計師就可以更容易地談論問題的核心,而不是修飾。
  6. Python 在 Cheeseshop 上維護著一個半官方的這些庫的倉庫。許多其他軟體包,例如 Numeric Python、SqlAlchemy、Django 和 NetworkX 都有自己的網站。
  7. https://docs.python.club.tw/library/stdtypes.html#truth-value-testing

import this -- 模組詳解

[編輯 | 編輯原始碼]

模組是任何包含 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.python.club.tw/library/random.html

遊蕩 Grue

練習

  1. 進一步探索 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)

問題在錯誤的最後一行解釋了。我們定義的函式接受一個引數,但是當我們第二次呼叫它(透過給出無效的選擇)時,我們沒有給它任何引數,所以它就亂了套。

一個防彈(好的,防彈)解析器

[編輯 | 編輯原始碼]

第 4 課:整合所有內容

[編輯 | 編輯原始碼]
  1. 課間複習
  2. CYOA 資料和動作模型
  3. 使用命令列呼叫指令碼

課間複習

[編輯 | 編輯原始碼]

**首先!!!**,完成 課間複習

模擬“選擇你自己的冒險”遊戲

[編輯 | 編輯原始碼]

計算機程式由模擬問題空間的 **資料** 和操作(操作、轉換和顯示)這些資料的 **函式** 組成。

資料,或者說什麼是什麼是?
[編輯 | 編輯原始碼]

想象一下,您正在拿著一本“選擇你自己的冒險”書籍。

  • 它是什麼?它的組成部分是什麼?
    答案

    它是一本由 **頁面** 組成的紙質 **書籍**。它有 **封面** 和 **封底**。這些 **頁面** 包含文字,故事的一部分,或者可能是像版權頁、編目資訊等特殊頁面。

  • 你怎麼知道從哪裡開始?
    答案

    按照慣例,頁面從英文書籍的 *前面* 開始

    • 第 1 頁有 *意義* 嗎?
      答案

      沒有,這僅僅是慣例。

  • 每頁的組成部分是什麼?
    答案

    頁面包含 **頁碼**、一些 **文字** 和 **選項**

讓我們利用我們在現實世界中的領域知識來建立一個實用的資料模型。我們的資料模型應該足夠複雜,能夠對領域進行建模,但不要過於詳細。如果以後需要更詳細,我們可以隨時新增。

對於這些部分中的每一個,請確定以下幾點:

  • 有多少個?零個、零個或更多個(0+)、(正好)一個、一個或更多個(1+)或其他確切的數字?
  • 如何識別它們?透過唯一 ID(U)、索引位置或其他方式?
  • 該項是否包含在/是另一個結構的一部分?它有子項或父項嗎?
  1. 頁碼
  2. 描述
  3. 選擇
答案
  • 書:一個(正好),包含頁面
  • 頁: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)

一些程式碼變體

[編輯 | 編輯原始碼]
第一版 - 20 英里的糟糕程式碼
[編輯 | 編輯原始碼]

檢視 此處的程式碼。警告!該連結後面有一些糟糕的程式碼。想想它有什麼主要錯誤,以及如何修復和改進它。

第二版 - 修補漏洞
[編輯 | 編輯原始碼]

第二版

練習

  1. 請注意,move() 現在是基於 while 的,而不是遞迴的。
  2. 編寫一個“頁面”字典,它將使 check_pages 失敗
  3. **高階**。使用你最喜歡的搜尋引擎,調查檔案末尾的“if '__name__' == 'main'" 內容。
    提示
    • 啟動互動式 Python。然後:print __name__
    • __main__
    • 它與命令列使用和匯入有關

main 和從命令列呼叫

[編輯 | 編輯原始碼]

(如果名稱為主,則匯入上下文等等)-- TODO

第 5 課:全球搖擺

[編輯 | 編輯原始碼]
  1. 瞭解 HTTP 請求
  2. 介紹 web.py

雪褲,一段愛情故事

[編輯 | 編輯原始碼]

所以,你認為你瞭解網路。當然,你可以上網衝浪,傳送 推特,閱讀 電子郵件,並在 Level 50 皮膚科醫生魔獸世界 中進行控制,但你知道幕後發生了什麼嗎?

人物表(玩家)

  • 米妮,明尼阿波利斯的一位網路使用者
  • 瀏覽器,一個網路瀏覽器
  • 伺服器,一個網路伺服器程式
  • 麥克,黃刀鎮的一臺電腦
  • 黃雪,黃刀鎮的 NSP(網路服務提供商)

幕布拉開。米妮在她的網路瀏覽器中輸入 http://sendwarmclothes.org/snowpants/send/。填寫表格後,她收到一封電子郵件和一條簡訊,上面寫著“別擔心!長褲正在路上!”,幾周後,長褲就送到了。人群歡呼,雙腿暖和了!

那麼,幕後發生了什麼?

  1. 當米妮在瀏覽器中輸入 URL 並按回車鍵時,一些魔法就會發生域名 http://sendwarmclothes.org/ 將被轉換為(解析IP 地址。瀏覽器程式會建立一個 HTTP 請求,它只是一個格式特殊的文字訊息,瀏覽器程式會嘗試傳送它。這個訊息中包含很多資訊……請求來自哪裡,請求的 URL 主機和路徑,時間戳和其他管理資訊等等。
  2. 更多路由魔法發生,請求透過電線和狗拉雪橇到達黃刀鎮,那裡是託管“sendwarmclothes.org”網站的電腦麥克的家。
  3. Mac 上執行著許多程式。它可以處理電子郵件,有一個使用者喜歡的流行 Boggle 程式,並且產生的熱量足以讓 YellowSnow 的員工圍著它取暖。 “Send Warm Clothes” 的員工實際上住在聖地亞哥,那裡的冬季服裝非常便宜,但他們決定如果他們的網站託管在加拿大西部,他們會更有“雪地”信譽。 除了這些忙碌而充實的生活,Mac 還執行著一個名為 Servo 的程式,這是一個 web 伺服器程式。 有些 web 伺服器包括 Apache 和 IIS,但還有很多其他的。
  4. Servo 的工作是監聽一組特定的 (其中包括:80 用於 HTTP 和 443 用於 HTTPS),並在這些埠上收到任何訊息(HTTP 請求)時做出響應。
    • 0 級:從概念上講,響應非常簡單。 當收到 HTTP 請求時,伺服器會識別這些特殊訊息的格式,對其進行解碼,並返回一個格式特殊的文字訊息(響應)作為回覆。
    • 1 級:神奇之處在於在響應的主體中放入特殊的東西。 作為響應的一部分,它描述了 它返回的特殊文字型別,例如:text/htmlvideo/quicktimeapplication/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 的目錄。
  5. Servo 作為現代 web 伺服器的典範,被程式設計為將請求 URL 解釋為一系列指令(send > snowpants),並執行一系列操作。 它向 Minnie 的手機發送一條簡訊,向聖地亞哥的倉庫傳送一封電子郵件,並構建一條文字響應傳送回 Minnie 的電腦。 文字響應包含一些描述 Servo 操作的 html 文字,並鼓勵 Minnie 勇敢地努力,直到長褲到達。 它將響應傳送回原始請求中的 IP 地址。
  6. 響應返回到明尼阿波利斯。 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]
  1. 這些 URL 中的模式是什麼?
  2. 看看 6pm 的例子。 哪個 Python 資料結構很容易對映到 URL 的各個部分?
  3. 看看標準庫中的 urlparse 模組。 使用 urlparse.urlparseurlparse.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 使用者安裝)

  1. 訪問 web.py
  2. 下載 web.py 程式碼的 tarball 或 zip 檔案。 如果你不知道 tarball 是什麼,那麼你想要 zip 檔案。
    1. (zip 特定)使用你喜歡的解壓縮工具解壓縮 zip 檔案。 在 Windows 中,你可能需要雙擊該檔案,或右鍵單擊並選擇“開啟方式 > 壓縮(zip)資料夾”。
    2. (tarball)tar -zxvf webpy.folder.name.tgz
  3. 在某個地方建立一個專案目錄 /path/to/webcyoa
  4. web.py 程式碼的 web 子目錄複製到你的專案目錄
  5. cd /path/to/webcyoa
  6. 建立一個包含 /Webpy Hello World 內容的檔案 code.py
  7. 從這裡開始,你就可以使用 web.py 主頁,你可以在 教程 中找到更多幫助。
  8. 啟動 web 應用程式!
    1. cd /path/to/webcyoa
    2. python code.py
      1. 你應該看到類似 http://0.0.0.0:8080/ 的內容。 如果是這樣,你的 web 伺服器現在正在 Localhost(見下文)上執行,並監聽 8080 埠。
      2. 如果你收到 ImportError,請確保你的專案目錄中存在 web 資料夾。
    3. 啟動你的 web 瀏覽器,並輸入 https://: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]
  1. 研究 Python 。 使用你喜歡的搜尋引擎。 帶著問題來。
  2. 更改 hello.GET 方法的方法,使其列印“Hello, the current time is.... [now]”,其中 [now] 是當前時間(參見:time.time()
  3. 探索如何根據教程提供靜態內容。

第 6 課:Web1.0,MysteriousHouse.com

[edit | edit source]

目標

[edit | edit source]
  1. 介面的概念
  2. 使程式碼抽象化
  3. 從命令列到 web 應用程式

課程

[edit | edit source]

作業

[edit | edit source]

第 7 課:添點料

[編輯 | 編輯原始碼]
  1. 擴充套件性和可修改性

其他頁面

[編輯 | 編輯原始碼]

參考文獻

[編輯 | 編輯原始碼]
  1. 說真的,花點時間看看標準庫,然後再重新發明函式。Web 伺服器、JSON、CSV 處理、正則表示式、IMAP 處理、SQLite 資料庫驅動程式等等。
  2. 基於此路徑,Mac 是一個 UNIX/Linux 機器。在 Windows 上,它可能類似於 C:/www/
華夏公益教科書