W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
雖然能讓瀏覽器顯示“Hello World”是很有趣的一件事情,但是如果能讓用戶通過表單(form)向你的應(yīng)用程序提交文本就更有趣了。這節(jié)習(xí)題中,我們將使用 form 改進(jìn)你的 web 程序,并且將用戶相關(guān)的信息保存到他們的“會(huì)話(session)”中。
該學(xué)點(diǎn)無趣的東西了。在創(chuàng)建 form 前你需要先多學(xué)一點(diǎn)關(guān)于 web的工作原理。這里講的并不完整,但是相當(dāng)準(zhǔn)確,在你的程序出錯(cuò)時(shí),它會(huì)幫你找到出錯(cuò)的原因。另外,如果你理解了 form 的應(yīng)用,那么創(chuàng)建 form 對(duì)你來說就會(huì)更容易了。
我將以一個(gè)簡單的圖示講起,它向你展示了 web 請(qǐng)求的各個(gè)不同的部分,以及信息傳遞的大致流程:
為了方便講述 HTTP 請(qǐng)求(request) 的流程,我在每條線上面加了字母標(biāo)簽以作區(qū)別:
- 你在瀏覽器中輸入網(wǎng)址
http://test.com//
,然后瀏覽器會(huì)通過你的電腦的網(wǎng)絡(luò)設(shè)備發(fā)出 request(線路 A)。- 你的 request 被傳送到互聯(lián)網(wǎng)(線路B),然后再抵達(dá)遠(yuǎn)端服務(wù)器(線路 C),然后我的服務(wù)器將接受這個(gè) request。
- 我的服務(wù)器接受 request 后,我的web應(yīng)用程序就去處理這個(gè)請(qǐng)求(線路 D),然后我的 Python代碼就會(huì)去運(yùn)行
index.GET
這個(gè)“處理程序(handler)”。- 在代碼
return
的時(shí)候,我的 Python 服務(wù)器就會(huì)發(fā)出響應(yīng)(response),這個(gè)響應(yīng)會(huì)再通過線路 D 傳遞到你的瀏覽器。- 這個(gè)網(wǎng)站所在的服務(wù)器將響應(yīng)由線路 D 獲取,然后通過線路 C 傳至互聯(lián)網(wǎng)。
- 響應(yīng)通過互聯(lián)網(wǎng)由線路 B 傳至你的計(jì)算機(jī),計(jì)算機(jī)的網(wǎng)卡再通過線路 A 將響應(yīng)傳給你的瀏覽器。
- 最后,你的瀏覽器顯示了這個(gè)響應(yīng)的內(nèi)容。
這段解釋中用到了一些術(shù)語。你需要掌握這些術(shù)語,以便在談?wù)撃愕?web 應(yīng)用時(shí)你能明白而且應(yīng)用它們:
瀏覽器(browser)這是你幾乎每天都會(huì)用到的軟件。大部分人不知道它真正的原理,他們只會(huì)把它叫作“the internet”。它的作用其實(shí)是接收你輸入到地址欄網(wǎng)址(例如 http://test.com/
),然后使用該信息向該網(wǎng)址對(duì)應(yīng)的服務(wù)器提出請(qǐng)求(request)。
地址(address)通常這是一個(gè)像http://test.com/
一樣的 URL (Uniform Resource Locator,統(tǒng)一資源定位器),它告訴瀏覽器該打開哪個(gè)網(wǎng)站。前面的http
指出了你要使用的協(xié)議(protocol),這里我們用的是“超文本傳輸協(xié)議(Hyper-Text Transport Protocol)”。你還可以試試ftp://ibiblio.org/
,這是一個(gè)“FTP 文件傳輸協(xié)議(File Transport Protocol)”的例子。test.com
這部分是“主機(jī)名(hostname)”,也就是一個(gè)便于人閱讀和記憶的字串,主機(jī)名會(huì)被匹配到一串叫作“IP 地址”的數(shù)字上面,這個(gè)“IP 地址”就相當(dāng)于網(wǎng)絡(luò)中一臺(tái)計(jì)算機(jī)的電話號(hào)碼,通過這個(gè)號(hào)碼可以訪問到這臺(tái)計(jì)算機(jī)。最后,URL 中還可以尾隨一個(gè)“路徑”,例如 http://test.com//book/
中的/book/
,它對(duì)應(yīng)的是服務(wù)器上的某個(gè)文件或者某些資源,通過訪問這樣的網(wǎng)址,你可以向服務(wù)器發(fā)出請(qǐng)求,然后獲得這些資源。網(wǎng)站地址還有很多別的組成部分,不過這些是最主要的。
連接(connection)一旦瀏覽器知道了協(xié)議(http)、服務(wù)器(http://test.com/
)、以及要獲得的資源,它就要去創(chuàng)建一個(gè)連接。這個(gè)過程中,瀏覽器讓操作系統(tǒng)(Operating System, OS)打開計(jì)算機(jī)的一個(gè)“端口(port)”(通常是 80 端口),端口準(zhǔn)備好以后,操作系統(tǒng)會(huì)回傳給你的程序一個(gè)類似文件的東西,它所做的事情就是通過網(wǎng)絡(luò)傳輸和接收數(shù)據(jù),讓你的計(jì)算機(jī)和 http://test.com/
這個(gè)網(wǎng)站所屬的服務(wù)器之間實(shí)現(xiàn)數(shù)據(jù)交流。 當(dāng)你使用 http://localhost:8080/
訪問你自己的站點(diǎn)時(shí),發(fā)生的事情其實(shí)是一樣的,只不過這次你告訴了瀏覽器要訪問的是你自己的計(jì)算機(jī)(localhost),要使用的端口 不是默認(rèn)的 80,而是 8080。你還可以直接訪問 http://test.com:80/
, 這和不輸入端口效果一樣,因?yàn)?HTTP 的默認(rèn)端口本來就是 80。
請(qǐng)求(request)你的瀏覽器通過你提供的地址建立了連接,現(xiàn)在它需要從遠(yuǎn)端服務(wù)器要到它(或你)想要的資源。如果你在 URL 的結(jié)尾加了 /book/
,那你想要的就是 /book/
對(duì)應(yīng)的文件或資源,大部分的服務(wù)器會(huì)直接為你調(diào)用 /book/index.html
這個(gè)文件,不過我們就假裝不存在好了。瀏覽器為了獲得服務(wù)器上的資源,它需要向服務(wù)器發(fā)送一個(gè)“請(qǐng)求”。這里我就不講細(xì)節(jié)了,為了得到服務(wù)器上的內(nèi)容,你必須先向服務(wù)器發(fā)送一個(gè)請(qǐng)求才行。有意思的是,“資源”不一定非要是文件。例如當(dāng)瀏覽器向你的應(yīng)用程序提出請(qǐng)求的時(shí)候,服務(wù)器返回的其實(shí)是你的 Python 代碼生成的一些東西。
服務(wù)器(server)服務(wù)器指的是瀏覽器另一端連接的計(jì)算機(jī),它知道如何回應(yīng)瀏覽器請(qǐng)求的文件和資源。大部分的 web 服務(wù)器只要發(fā)送文件就可以了,這也是服務(wù)器流量的主要部分。不過你學(xué)的是使用 Python 組建一個(gè)服務(wù)器,這個(gè)服務(wù)器知道如何接受請(qǐng)求,然后返回用 Python 處理過的字符串。當(dāng)你使用這種處理方式時(shí),你其實(shí)是假裝把文件發(fā)給了瀏覽器,其實(shí)你用的都只是代碼而已。就像你在《習(xí)題50》中看到的,要構(gòu)建一個(gè)“響應(yīng)”其實(shí)也不需要多少代碼。
響應(yīng)(response)這就是你的服務(wù)器回復(fù)你的請(qǐng)求,發(fā)回至瀏覽器的 HTML,它里邊可能有 css、javascript、或者圖像等內(nèi)容。以文件響應(yīng)為例,服務(wù)器只要從磁盤讀取文件,發(fā)送給瀏覽器就可以了,不過它還要將這些內(nèi)容包在一個(gè)特別定義的“頭部信息(header)”中,這樣瀏覽器就會(huì)知道它獲取的是什么類型的內(nèi)容。以你的 web 應(yīng)用程序?yàn)槔?,你發(fā)送的其實(shí)還是一樣的東西,包括 header 也一樣,只不過這些數(shù)據(jù)是你用 Python 代碼即時(shí)生成的。
這個(gè)可能是你能在網(wǎng)上找到的關(guān)于瀏覽器如何訪問網(wǎng)站的最快的快速課程了。這節(jié)課程應(yīng)該可以幫你更容易地理解本節(jié)的習(xí)題,如果你還是不明白,就到處找資料多多了解這方面的信息,直到你明白為止。有一個(gè)很好的方法,就是你對(duì)照著上面的圖示,將你在《習(xí)題 50》中創(chuàng)建的 web 程序中的內(nèi)容分成幾個(gè)部分,讓其中的各部分對(duì)應(yīng)到上面的圖示。如果你可以正確地將程序的各部分對(duì)應(yīng)到這個(gè)圖示,你就大致明白它的工作原理了。
熟悉“表單”最好的方法就是寫一個(gè)可以接收表單數(shù)據(jù)的程序出來,然后看你可以對(duì)它做些什么。先將你的bin/app.py
修改成下面的樣子:
import web
urls = (
'/hello', 'Index'
)
app = web.application(urls, globals())
render = web.template.render('templates/')
class Index(object):
def GET(self):
form = web.input(name="Nobody")
greeting = "Hello, %s" % form.name
return render.index(greeting = greeting)
if __name__ == "__main__":
app.run()
重啟你的 web 程序(按 CTRL-C 后重新運(yùn)行),確認(rèn)它有運(yùn)行起來,然后使用瀏覽器訪問 http://localhost:8080/hello
,這時(shí)瀏覽器應(yīng)該會(huì)顯示“I just wanted to say Hello, Nobody.”,接下來,將瀏覽器的地址改成 http://localhost:8080/hello?name=Frank
,然后你可以看到頁面顯示為“Hello, Frank.”,最后將 name=Frank
修改為你自己的名字,你就可以看到它對(duì)你說“Hello”了。
讓我們研究一下你的程序里做過的修改:
- 我們沒有直接為
greeting
賦值,而是使用了web.input
從瀏覽器獲取數(shù)據(jù)。這個(gè)函數(shù)會(huì)將一組 key=value 的表述作為默認(rèn)參數(shù),解析你提供的 URL 中的?name=Frank
部分,然后返回一個(gè)對(duì)象,你可以通過這個(gè)對(duì)象方便地訪問到表單的值。- 然后我通過
form
對(duì)象的form.name
屬性為greeting
賦值,這句你應(yīng)該已經(jīng)熟悉了。- 其他的內(nèi)容和以前是一樣的。
URL 中該還可以包含多個(gè)參數(shù)。將本例的 URL 改成這樣子: http://localhost:8080/hello?name=Frank&greet=Hola
。然后修改代碼,讓它去獲取form.name
和form.greet
,如下所示:
greeting = "%s, %s" % (form.greet, form.name)
修改完畢后,試著訪問新的 URL。然后將 &greet=Hola
部分刪除,看看你會(huì)得到什么樣的錯(cuò)誤信息。由于我們?cè)?web.input(name="Nobody")
中沒有為 greet
設(shè)定默認(rèn)值,這樣 greet
就變成了一個(gè)必須的參數(shù),如果沒有這個(gè)參數(shù)程序就會(huì)報(bào)錯(cuò)?,F(xiàn)在修改一下你的程序,在 web.input
中為 greet
設(shè)一個(gè)默認(rèn)值試試看。另外你還可以設(shè) greet=None,這樣你可以通過程序檢查 greet
的值是否存在,然后提供一個(gè)比較好的錯(cuò)誤信息出來,例如:
form = web.input(name="Nobody", greet=None)
if form.greet:
greeting = "%s, %s" % (form.greet, form.name)
return render.index(greeting = greeting)
else:
return "ERROR: greet is required."
你可以通過 URL 參數(shù)實(shí)現(xiàn)表單提交,不過這樣看上去有些丑陋,而且不方便一般人使用,你真正需要的是一個(gè)“POST 表單”,這是一種包含了<form>
標(biāo)簽的特殊 HTML 文件。這種表單收集用戶輸入并將其傳遞給你的 web 程序,這和你上面實(shí)現(xiàn)的目的基本是一樣的。
讓我們來快速創(chuàng)建一個(gè),從中你可以看出它的工作原理。你需要?jiǎng)?chuàng)建一個(gè)新的 HTML 文件, 叫做templates/hello_form.html
:
<html>
<head>
<title>Sample Web Form</title>
</head>
<body>
<h1>Fill Out This Form</h1>
<form action="/hello" method="POST">
A Greeting: <input type="text" name="greet">
<br/>
Your Name: <input type="text" name="name">
<br/>
<input type="submit">
</form>
</body>
</html>
然后修改bin/app.py
:
import web
urls = (
'/hello', 'Index'
)
app = web.application(urls, globals())
render = web.template.render('templates/')
class Index(object):
def GET(self):
return render.hello_form()
def POST(self):
form = web.input(name="Nobody", greet="Hello")
greeting = "%s, %s" % (form.greet, form.name)
return render.index(greeting = greeting)
if __name__ == "__main__":
app.run()
都寫好以后,重啟 web 程序,然后通過你的瀏覽器訪問它。
這回你會(huì)看到一個(gè)表單,它要求你輸入“一個(gè)問候語句(A Greeting)”和“你的名字(Your Name)”,等你輸入完后點(diǎn)擊“提交(Submit)”按鈕,它就會(huì)輸出一個(gè)正常的問候頁面,不過這一次你的URL 還是http://localhost:8080/hello
,并沒有添加參數(shù)進(jìn)去。
在 hello_form.html
里面關(guān)鍵的一行是 <form action="/hello" method="POST">
,它告訴你的瀏覽器以下內(nèi)容:
- 從表單中的各個(gè)欄位收集用戶輸入的數(shù)據(jù)。
- 讓瀏覽器使用一種
POST
類型的請(qǐng)求,將這些數(shù)據(jù)發(fā)送給服務(wù)器。這是另外一種瀏覽器請(qǐng)求,它會(huì)將表單欄位“隱藏”起來。- 將這個(gè)請(qǐng)求發(fā)送至
/hello
URL(這是由action="/hello"
告訴瀏覽器的)。
你可以看到兩段<input>
標(biāo)簽的名字屬性(name)和代碼中的變量是對(duì)應(yīng)的,另外我們?cè)?code>class index 中使用的不再只是GET
方法,而是另一個(gè)POST
方法。
這個(gè)新程序的工作原理如下:
- 瀏覽器訪問到 web 程序的
/hello
目錄,它發(fā)送了一個(gè)GET
請(qǐng)求,于是我們的index.GET
函數(shù)就運(yùn)行并返回了hello_form
。- 你填好了瀏覽器的表單,然后瀏覽器依照
<form>
中的要求,將數(shù)據(jù)通過POST
請(qǐng)求的方式發(fā)給 web 程序。- Web 程序運(yùn)行了
index.POST
方法(不是index.GET
方法)來處理這個(gè)請(qǐng)求。- 這個(gè)
index.POST
方法完成了它正常的功能,將 hello 頁面返回,這里并沒有新的東西,只是一個(gè)新函數(shù)名稱而已。
作為練習(xí),在 templates/index.html
中添加一個(gè)鏈接,讓它指向 /hello
,這樣你可以反復(fù)填寫并提交表單查看結(jié)果。確認(rèn)你可以解釋清楚這個(gè)鏈接的工作原理,以及它是如何讓你實(shí)現(xiàn)在 templates/index.html
和 templates/hello_form.html
之間循環(huán)跳轉(zhuǎn)的,還有就是要明白你新修改過的 Python 代碼,你需要知道在什么情況下會(huì)運(yùn)行到哪一部分代碼。
在你下一節(jié)練習(xí)創(chuàng)建游戲的過程中,你需要?jiǎng)?chuàng)建很多的小 HTML 頁面。如果你每次都寫一個(gè)完整的網(wǎng)頁,你會(huì)很快感覺到厭煩的。幸運(yùn)的 是你可以創(chuàng)建一個(gè)“布局模板”,也就是一種提供了通用的頭文件和腳注的外殼模板,你可以用它將你所有的其他網(wǎng)頁包裹起來。好程序員會(huì)盡可能減少重復(fù)動(dòng)作,所以要做一個(gè)好程序員,使用布局模板是很重要的。
將 templates/index.html
修改成這樣:
$def with (greeting)
$if greeting:
I just wanted to say <em style="color: green; font-size: 2em;">$greeting</em>.
$else:
<em>Hello</em>, world!
然后修改 templates/hello_form.html
:
<h1>Fill Out This Form</h1>
<form action="/hello" method="POST">
A Greeting: <input type="text" name="greet">
<br/>
Your Name: <input type="text" name="name">
<br/>
<input type="submit">
</form>
上面這些修改的目的,是將每一個(gè)頁面頂部和底部的反復(fù)用到的“boilerplate”代碼剝掉。這些被剝掉的代碼會(huì)被放到一個(gè)單獨(dú)的templates/layout.html
文件中,從此以后,這些反復(fù)用到的代碼就由layout.html
來提供了。
上面的都改好以后,創(chuàng)建一個(gè)templates/layout.html
文件,內(nèi)容如下:
$def with (content)
<html>
<head>
<title>Gothons From Planet Percal #25</title>
</head>
<body>
$:content
</body>
</html>
這個(gè)文件和普通的模板文件類似,不過其它的模板的內(nèi)容將被傳遞給它,然后它會(huì)將其它 模板的內(nèi)容“包裹”起來。任何寫在這里的內(nèi)容多無需寫在別的模板中了。你需要注意$:content
的用法,這和其它的模板變量有些不同。
最后一步,就是將render
對(duì)象改成這樣: render = web.template.render('templates/', base="layout")
這會(huì)告訴lpthw.web
讓它去使用templates/layout.html
作為其它模板的基礎(chǔ)模板。重啟你的程序觀察一下,然后試著用各種方法修改你的 layout 模板,不要修改你別的模板,看看輸出會(huì)有什么樣的變化。
使用瀏覽器測(cè)試 web 程序是很容易的,只要點(diǎn)刷新按鈕就可以了。不過畢竟我們是程序員嘛,如果我們可以寫一些代碼來測(cè)試我們的程序,為什么還要重復(fù)手動(dòng)測(cè)試呢?接下來你要做的,就是為你的 web 程序?qū)懸粋€(gè)小測(cè)試。這會(huì)用到你在《習(xí)題 47》學(xué)過的一些東西,如果你不記得的話,可以復(fù)習(xí)一下。
為了讓 Python 加載 bin/app.py
并進(jìn)行測(cè)試,你需要先做一點(diǎn)準(zhǔn)備工作。首先創(chuàng)建一個(gè) bin/__init__.py
空文件,這樣 Python 就會(huì)將 bin/
當(dāng)作一個(gè)目錄了。(在《習(xí)題 52》中你會(huì)去修改 __init__.py
,不過這是后話。)
我還為 lpthw.web
創(chuàng)建了一個(gè)簡單的小函數(shù),讓你判斷(assert) web 程序的響應(yīng),這個(gè)函數(shù)的名字,叫 assert_response
。創(chuàng)建一個(gè) tests/tools.py
文件,內(nèi)容如下:
from nose.tools import *
import re
def assert_response(resp, contains=None, matches=None, headers=None, status="200"):
assert status in resp.status, "Expected response %r not in %r" % (status, resp.status)
if status == "200":
assert resp.data, "Response data is empty."
if contains:
assert contains in resp.data, "Response does not contain %r" % contains
if matches:
reg = re.compile(matches)
assert reg.matches(resp.data), "Response does not match %r" % matches
if headers:
assert_equal(resp.headers, headers)
準(zhǔn)備好這個(gè)文件以后,你就可以為你的bin/app.py
寫自動(dòng)測(cè)試代碼了。創(chuàng)建一個(gè)新文件,叫做 tests/app_tests.py
,內(nèi)容如下:
from nose.tools import *
from bin.app import app
from tests.tools import assert_response
def test_index():
# check that we get a 404 on the / URL
resp = app.request("/")
assert_response(resp, status="404")
# test our first GET request to /hello
resp = app.request("/hello")
assert_response(resp)
# make sure default values work for the form
resp = app.request("/hello", method="POST")
assert_response(resp, contains="Nobody")
# test that we get expected values
data = {'name': 'Zed', 'greet': 'Hola'}
resp = app.request("/hello", method="POST", data=data)
assert_response(resp, contains="Zed")
最后,使用nosetests
運(yùn)行測(cè)試腳本,然后測(cè)試你的 web 程序。
$ nosetests
.
----------------------------------------------------------------------
Ran 1 test in 0.059s
OK
這里我所做的,是將 bin/app.py
這個(gè)模塊中的整個(gè) web 程序都 import 進(jìn)來,然后手動(dòng)運(yùn)行這個(gè) web 程序。lpthw.web
有一個(gè)非常簡單的 API 用來處理請(qǐng)求,看上去大致是這樣子的:
app.request(localpart='/', method='GET', data=None, host='0.0.0.0:8080',
headers=None, https=False)
你可以將 URL 作為第一個(gè)參數(shù),然后你可以修改 request 的方法、form 的數(shù)據(jù)、以及 header 的內(nèi)容,這樣你無須啟動(dòng) web 服務(wù)器,就可以使用自動(dòng)測(cè)試來測(cè)試你的 web 程序了。
為了驗(yàn)證函數(shù)的響應(yīng),你需要使用tests.tools
中定義的assert_response
函數(shù),用法屬下:
assert_response(resp, contains=None, matches=None, headers=None, status="200")
把你調(diào)用app.request
得到的響應(yīng)傳遞給這個(gè)函數(shù),然后將你要檢查的內(nèi)容作為參數(shù)傳遞給誒這個(gè)函數(shù)。你可以使用contains
參數(shù)來檢查響應(yīng)中是否包含指定的值,使用status
參數(shù)可以檢查指定的響應(yīng)狀態(tài)。這個(gè)小函數(shù)其實(shí)包含了很多的信息,所以你還是自己研究一下的比較好。
在 tests/app_tests.py
自動(dòng)測(cè)試腳本中,我首先確認(rèn) /
返回了一個(gè)“404 Not Found”響應(yīng),因?yàn)檫@個(gè) URL 其實(shí)是不存在的。然后我檢查了 /hello
在 GET
和POST
兩種請(qǐng)求的情況下都能正常工作。就算你沒有弄明白測(cè)試的原理,這些測(cè)試代碼應(yīng)該是很好讀懂的。
花一些時(shí)間研究一下這個(gè)最新版的 web 程序,重點(diǎn)研究一下自動(dòng)測(cè)試的工作原理。確認(rèn)你理解了將bin/app.py
做為一個(gè)模塊導(dǎo)入,然后進(jìn)行自動(dòng)化測(cè)試的流程。這是一個(gè)很重要的技巧,它會(huì)引導(dǎo)你學(xué)到更多東西。
- 閱讀和 HTML 相關(guān)的更多資料,然后為你的表單設(shè)計(jì)一個(gè)更好的輸出格式。你可以先在紙上設(shè)計(jì)出來,然后用 HTML 去實(shí)現(xiàn)它。
- 這是一道難題,試著研究一下如何進(jìn)行文件上傳,通過網(wǎng)頁上傳一張圖像,然后將其保存到磁盤中。
- 更難的難題,找到
HTTP RFC
文件(講述 HTTP 工作原理的技術(shù)文件),然后努力閱讀一下。這是一篇很無趣的文檔,不過偶爾你會(huì)用到里邊的一些知識(shí)。- 又是一道難題,找人幫你設(shè)置一個(gè) web 服務(wù)器,例如
Apache
、Nginx
、或者thttpd
。試著讓服務(wù)器服務(wù)一下你創(chuàng)建的 .html 和 .css 文件。如果失敗了也沒關(guān)系,web 服務(wù)器本來就都有點(diǎn)挫。- 完成上面的任務(wù)后休息一下,然后試著多創(chuàng)建一些 web 程序出來。你應(yīng)該仔細(xì)閱讀
web.py
(它和lpthw.web
是同一個(gè)程序)中關(guān)于會(huì)話(session)的內(nèi)容,這樣你可以 明白如何保持用戶的狀態(tài)信息。
ImportError "No module named bin.app"
這個(gè)問題要么是你在錯(cuò)誤的目錄下啟動(dòng)了服務(wù),要么是沒有
bin/__init__.py
這個(gè)文件,再或者是你沒有在你的shell中設(shè)置PYTHONPATH=.
。請(qǐng)永遠(yuǎn)記住這些解決方案,因?yàn)檫@些錯(cuò)誤是如此令人難以置信的普遍發(fā)生,當(dāng)他們發(fā)生的時(shí)候,還會(huì)拖慢你服務(wù)的速度。
__template__() takes no arguments (1 given)
你可能忘記把這個(gè)變量
$def with (greeting)
或者類似的變量放在模板的頂部了。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: