Python in the browser · 网页里的 Python

Run Python in a web page在网页里运行 Python

This page is the next step after Make a Song with Python, so it starts where that page stops: you already know print, variables, lists, for loops, def and if. Here you learn how real Python programs are laid out, with main() and if __name__ == "__main__":, then list comprehensions, dictionaries and sets, generator expressions, and generators that watch for events, so that your clicks on the page arrive in a for loop as if they were items in a list. Every step is a short program about a fruit stall in Chiang Mai, and you change it and run it right here, in your browser. The page also shows how a browser can run Python at all, and in the last step you make your own web page that runs your own Python. You need a computer or tablet with Chrome or another new browser, and the internet. Nothing to install.这一页是 用 Python 做一首歌 的下一步,所以它从那一页结束的地方开始:你已经会 print、变量、列表、for 循环、def 和 if。在这里,你会学到真正的 Python 程序是怎么安排的,用 main() 和 if __name__ == "__main__":;然后是列表推导式、字典和集合、生成器表达式,还有会"盯着"事件的生成器,让你在页面上的点击像列表里的东西一样,一个一个来到 for 循环里。每一步都是一个关于清迈水果摊的小程序,你可以就在这里、在你的浏览器里修改和运行它。这一页还会告诉你,浏览器到底是怎么运行 Python 的;最后一步,你会做出你自己的网页,运行你自己的 Python。你需要一台装有 Chrome 或其他新浏览器的电脑或平板,还有网络。什么都不用安装。

What you will do, and what you need · 你要做什么,需要什么

English

What you will do. You run ten short Python programs, one after another, in the boxes on this page. Each one adds one new idea. After most steps, a quick check asks you to guess what a tiny program prints, and then Python shows you. In step 9 you click the buttons of a fruit stall and watch Python receive each click. In step 10 you download a web page of your own, with your Python in it, and open it.

What you learn. Default values for a function's parameters; main() and if __name__ == "__main__":, so one file can be both a program and a toolbox for another file; list comprehensions, with and without if; dictionaries and sets, and their comprehensions; generator expressions; generator functions with yield; and a watcher, a generator that hands you events, one at a time, as they happen. And how a web page can run Python: what Pyodide and WebAssembly are.

What you need. What Make a Song with Python teaches. A computer or tablet with Chrome, Edge, Safari or Firefox, and an internet connection. Nothing to install. For step 10, a computer on which you can save a file and open it.

中文

你要做什么。你在这一页的代码框里,一个接一个地运行十个小小的 Python 程序。每一个都加一个新想法。大多数步骤之后,有一个小测验让你猜一个小程序会打印什么,然后 Python 会告诉你答案。第 9 步,你点水果摊上的按钮,看着 Python 收到每一次点击。第 10 步,你下载一个属于你自己的网页,里面装着你的 Python,然后打开它。

你会学到什么。函数参数的默认值;main() 和 if __name__ == "__main__":,让一个文件既是一个程序,又是另一个文件的工具箱;列表推导式,带 if 和不带 if 的;字典和集合,还有它们的推导式;生成器表达式;带 yield 的生成器函数;还有"监视器":一个生成器,事件发生时,把它们一个一个交给你。还有网页是怎么运行 Python 的:Pyodide 和 WebAssembly 是什么。

你需要什么。用 Python 做一首歌 教的那些。一台装有 Chrome、Edge、Safari 或 Firefox 的电脑或平板,还有网络。什么都不用安装。第 10 步需要一台能保存文件、再打开它的电脑。

How the code boxes work · 代码框怎么用

English

Each step has a box of Python code. Press ▶ Run and Python runs it, right in this page. What the program prints shows up in the dark box under it. Before you press Run, the dark box shows what it printed when we ran it. You can type in the code box and run it again. ↺ Start again puts the code back the way it was.

Each box is also a file, and its name is above it. When you press Run, the page saves the box as that file inside the page's Python, then runs it as the program. So one box can import another: step 2 does.

The first Run takes a few seconds, while Python loads into the page. The next part says what is happening in those seconds.

中文

每一步都有一个 Python 代码框。按 ▶ Run,Python 就会在这一页里运行它。程序打印的内容会出现在下面的深色框里。在你按 Run 之前,深色框里是我们运行时它打印的内容。你可以在代码框里打字,再运行一次。↺ Start again 会把代码变回原来的样子。

每个代码框也是一个文件,它的名字写在框的上面。你按 Run 的时候,页面会把这个框保存成页面里的 Python 中的那个文件,然后把它当作程序来运行。所以一个框可以 import 另一个框:第 2 步就是这样做的。

第一次按 Run 要等几秒钟,因为 Python 正在载入这一页。下一部分会讲这几秒钟里发生了什么。

How can a web page run Python? · 网页怎么能运行 Python?

English

A web browser understands only a few languages. HTML says what is on the page, CSS says how it looks, and JavaScript says what it does. A browser also runs WebAssembly: programs turned into a small, fast code that the browser runs inside the tab, shut off from the rest of your computer.

Python itself is a program, written in a language called C. Volunteers took that program and, with a tool called Emscripten, turned it into WebAssembly. They called the result Pyodide. It is a whole Python, made to live inside a web page.

So the first time you press Run on this page:

  1. The page downloads Pyodide, about 5.6 MB. That is the wait.
  2. The browser starts it. Now there is a Python inside your tab.
  3. The page hands your code to that Python, and whatever print prints is sent back into the dark box.

Your code runs on your own computer, not on a server far away. We tested that: after Python had loaded, we cut the page off from the internet, and Run still worked.

The browser keeps Pyodide inside the tab. It cannot open the files on your computer by itself or start other programs. That is what makes it safe for any web page to run.

See for yourself. sys.platform names the kind of computer Python is running on. On a Windows computer it is win32, and on a Mac it is darwin. Run the box below and see what it is here: emscripten, the name of the tool that turned Python into WebAssembly.

中文

浏览器只懂几种语言。HTML 说页面上有什么,CSS 说它长什么样,JavaScript 说它做什么。浏览器还能运行 WebAssembly:被变成又小又快的代码的程序,浏览器在标签页里运行它,和电脑的其他部分隔开。

Python 本身也是一个程序,是用一种叫 C 的语言写的。一些志愿者用一个叫 Emscripten 的工具,把这个程序变成了 WebAssembly。他们把做出来的东西叫作 Pyodide。它是一个完整的 Python,专门住在网页里。

所以你在这一页第一次按 Run 的时候:

  1. 页面下载 Pyodide,大约 5.6 MB。要等的就是这个。
  2. 浏览器启动它。现在你的标签页里有了一个 Python。
  3. 页面把你的代码交给这个 Python,print 打印的东西会被送回深色框里。

你的代码是在你自己的电脑上运行的,不是在远方的服务器上。我们测试过:Python 载入以后,我们把页面和网络断开,Run 照样能用。

浏览器把 Pyodide 关在标签页里。它不能自己打开你电脑上的文件,也不能启动别的程序。正因为这样,任何网页运行它都是安全的。

自己看一看。sys.platform 是 Python 正在运行的那种电脑的名字。在 Windows 电脑上它是 win32,在 Mac 上它是 darwin。运行下面的框,看看在这里它是什么:emscripten,就是把 Python 变成 WebAssembly 的那个工具的名字。

  1. 1 · your codeThe Python you type in a box.你在框里打的 Python。
  2. 2 · PyodidePython, turned into WebAssembly.变成了 WebAssembly 的 Python。
  3. 3 · the browserRuns the WebAssembly inside the tab.在标签页里运行 WebAssembly。
  4. 4 · your computerDoes the work. No server runs your code.真正干活的是它。没有服务器在运行你的代码。
# Where is this Python running?
# sys.platform names the kind of computer Python finds itself on.

import sys

print(sys.platform)

What it printed它打印的内容

emscripten

Step 1

Functions, main() and if __name__ == "__main__" · 第 1 步:函数、main() 和 if __name__ == "__main__"

English

You know def and return. Here are two new things about functions.

A default value. In def sale_price(price, off=10):, off is 10 unless you give another number. So sale_price(60) is 50, and sale_price(60, 25) is 35.

main() and the last two lines. Almost every Python program is laid out like this file: functions first, then one function called main() that does the program's work, then if __name__ == "__main__": and main() at the very end. Every Python file has a name, kept in __name__ (two underscores on each side). When you run the file as the program, Python names it "__main__", the if is true, and main() runs. When another file imports it, its name is its file name, here "stall", and main() does not run. So the same file is both a program and a toolbox. Step 2 uses it as a toolbox.

中文

你已经会 def 和 return。这里有两样关于函数的新东西。

默认值。在 def sale_price(price, off=10): 里,除非你给另一个数,off 就是 10。所以 sale_price(60) 是 50,sale_price(60, 25) 是 35。

main() 和最后两行。几乎每个 Python 程序都像这个文件这样安排:先写函数,然后写一个叫 main() 的函数,做这个程序要做的事,最后是 if __name__ == "__main__": 和 main()。每个 Python 文件都有一个名字,装在 __name__ 里(两边各有两条下划线)。当你把这个文件当作程序运行时,Python 给它起名叫 "__main__",if 成立,main() 就会运行。当另一个文件 import 它时,它的名字是它的文件名,这里是 "stall",main() 就不会运行。所以同一个文件既是一个程序,又是一个工具箱。第 2 步把它当作工具箱来用。

# Step 1: stall.py. Functions first, then main(), then the two lines that run it.


# off has a default: it is 10 unless you give another number.
def sale_price(price, off=10):
    return price - off


def main():
    print("stall.py is running as the program. Its __name__ is", __name__)
    print("Mango, 60 baht:", sale_price(60))
    print("Mango, big sale:", sale_price(60, 25))


if __name__ == "__main__":
    main()

What it printed它打印的内容

stall.py is running as the program. Its __name__ is __main__
Mango, 60 baht: 50
Mango, big sale: 35

Check 1: what will this print? · 想一想 1:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 1: what will this print?


def sale_price(price, off=10):
    return price - off


print(sale_price(50), sale_price(50, 20))

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

The first call gives no off, so off is 10 and the price is 40. The second call gives 20, which takes the place of the 10: 50 - 20 is 30.第一次调用没有给 off,所以 off 是 10,价钱是 40。第二次调用给了 20,它代替了 10:50 - 20 是 30。

What Python printedPython 打印的内容

40 30

Step 2

Import a file: the toolbox half · 第 2 步:import 一个文件:工具箱的那一半

English

import stall finds stall.py and runs it. That makes its function, so stall.sale_price(50) works here. But inside stall.py, __name__ is now "stall", so the last if is false and its main() prints nothing. Look at the dark box: none of step 1's lines are in it.

This is why the habit matters. Without the if, every file that imports stall.py would also run its whole program.

中文

import stall 找到 stall.py 并运行它。这样就做出了它的函数,所以 stall.sale_price(50) 在这里能用。但是在 stall.py 里面,__name__ 现在是 "stall",所以最后那个 if 不成立,它的 main() 什么也不打印。看看深色框:里面没有第 1 步的任何一行。

这就是这个习惯重要的原因。没有这个 if,每个 import stall.py 的文件,都会把它的整个程序也跑一遍。

# Step 2: shop.py borrows sale_price from stall.py, with import.
# Importing stall.py runs it, but there its __name__ is "stall",
# so its main() does not run.

import stall

print("Inside stall.py, __name__ is", stall.__name__)
print("Longan, 50 baht:", stall.sale_price(50))

What it printed它打印的内容

Inside stall.py, __name__ is stall
Longan, 50 baht: 40

English

Try this

  • In step 1's box, change off=10 to off=20 and press Run there. Then press Run here again. Which price changed?
  • In step 1's box, take away the if line, and the spaces in front of main(). Run step 1, then run this box. What does this box print now?

中文

试一试

  • 在第 1 步的框里,把 off=10 改成 off=20,在那里按 Run。然后在这里再按一次 Run。哪个价钱变了?
  • 在第 1 步的框里,去掉 if 那一行,还有 main() 前面的空格。运行第 1 步,再运行这个框。现在这个框会打印什么?

Check 2: what will this print? · 想一想 2:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 2: what will this print?


def double(number):
    return number * 2


def main():
    print(double(4))


if __name__ == "__main__":
    print("start")

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

def main(): only makes the function; it does not run it. The if block prints start and never calls main(), so 8 is never printed.def main(): 只是做出这个函数,并不运行它。if 里面只打印 start,从来没有调用 main(),所以 8 永远不会被打印。

What Python printedPython 打印的内容

start

Step 3

List comprehensions: a new list in one line · 第 3 步:列表推导式:一行做出新列表

English

It is sale day, and every price is 10 baht less. The long way to make the sale prices is an empty list, a for loop, and append, which puts an item at the end of a list. A list comprehension does the same in one line: inside square brackets, first what goes in the new list, then the for part that says where it comes from.

Read [price - 10 for price in prices] out loud as "price minus 10, for each price in prices".

中文

今天大减价,每样东西便宜 10 泰铢。做出减价价钱的长写法是:一个空列表、一个 for 循环,再加 append,它把一个东西放到列表的最后。列表推导式用一行做同样的事:在方括号里,先写新列表里放什么,再写 for 那一部分,说明它从哪里来。

把 [price - 10 for price in prices] 大声读出来:"price 减 10,对 prices 里的每一个 price"。

[price - 10 for price in prices]

what goes in the new list 新列表里放什么where it comes from: the same for as a loop 从哪里来:和循环一样的 for

# Step 3: a list comprehension makes a new list from an old one, in one line.
# Sale day: every price is 10 baht less.

prices = [60, 50, 25, 30]

# The long way: an empty list, a for loop, and append.
sale = []
for price in prices:
    sale.append(price - 10)
print("Loop:         ", sale)

# The same list as a list comprehension.
sale = [price - 10 for price in prices]
print("Comprehension:", sale)

fruits = ["mango", "longan", "banana", "papaya"]
print([fruit.upper() for fruit in fruits])

What it printed它打印的内容

Loop:          [50, 40, 15, 20]
Comprehension: [50, 40, 15, 20]
['MANGO', 'LONGAN', 'BANANA', 'PAPAYA']

English

Try this

  • Make the prices for a tourist: [price * 2 for price in prices].
  • How many letters in each name? [len(fruit) for fruit in fruits].
  • Use step 1's function: [sale_price(price) for price in prices], after from stall import sale_price.

中文

试一试

  • 做一份给游客的价钱:[price * 2 for price in prices]。
  • 每个名字有几个字母?[len(fruit) for fruit in fruits]。
  • 用第 1 步的函数:先写 from stall import sale_price,再写 [sale_price(price) for price in prices]。

Check 3: what will this print? · 想一想 3:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 3: what will this print?

words = ["hi", "bye"]
print([word + "!" for word in words])

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

For each word, the new list gets word + "!", so it holds two strings, each with a ! on the end. Python prints a list with its square brackets, and each string in quotes.对每一个词,新列表里放的是 word + "!",所以它装着两个字符串,每个后面都有一个 !。Python 打印列表时会带着方括号,每个字符串都带着引号。

What Python printedPython 打印的内容

['hi!', 'bye!']

Step 4

List comprehensions with if: keep only some · 第 4 步:带 if 的列表推导式:只留下一部分

English

Put an if at the end of a list comprehension, and only the items that pass go into the new list.

Read [fruit for fruit in basket if fruit == "mango"] as "fruit, for each fruit in the basket, if the fruit is a mango".

中文

在列表推导式的最后加一个 if,只有通过的东西才会进新列表。

把 [fruit for fruit in basket if fruit == "mango"] 读成"fruit,对 basket 里的每一个 fruit,如果这个 fruit 是芒果"。

[fruit for fruit in basket if fruit == "mango"]

what goes in 放什么where from, and which ones 从哪里来,要哪些

# Step 4: a list comprehension with if keeps only some of the items.
# Read it as: "fruit, for each fruit in basket, if the fruit is a mango".

basket = ["mango", "longan", "mango", "banana", "mango"]
mangoes = [fruit for fruit in basket if fruit == "mango"]
print(mangoes)
print(len(mangoes), "mangoes")

prices = [60, 50, 25, 30]
cheap = [price for price in prices if price < 40]
print("Under 40 baht:", cheap)

What it printed它打印的内容

['mango', 'mango', 'mango']
3 mangoes
Under 40 baht: [25, 30]

English

Try this

  • Count the longan in the basket instead of the mangoes.
  • Both at once: the sale price of every cheap fruit, [price - 10 for price in prices if price < 40].

中文

试一试

  • 改成数篮子里的龙眼,而不是芒果。
  • 两样一起:每种便宜水果的减价价钱,[price - 10 for price in prices if price < 40]。

Check 4: what will this print? · 想一想 4:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 4: what will this print?

prices = [5, 50, 15, 80]
print([price for price in prices if price > 10])

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

Only 5 fails price > 10. 15 is more than 10 too, so it stays, and the new list keeps the old order.只有 5 没有通过 price > 10。15 也比 10 大,所以它留下了;新列表还保持原来的顺序。

What Python printedPython 打印的内容

[50, 15, 80]

Step 5

Dictionaries: look things up by name · 第 5 步:字典:按名字查东西

English

A list finds things by their place: the first, the second. A dictionary finds them by a name, called a key, the way a price list finds a price by the fruit's name. Write it in curly brackets: each key, a colon, then its value, with commas between the pairs.

  • prices["mango"] gives the value of the key "mango".
  • prices["papaya"] = 30 adds a new key. If the key is there already, it gets the new value instead: a key is never in a dictionary twice.
  • "durian" in prices asks whether a key is there. prices["durian"] would stop the program, because there is no such key; prices.get("durian", 0) gives 0 instead.
  • prices.items() gives each key with its value, so a for loop can take two names: for fruit, price in prices.items():.

The last line is a dictionary comprehension: like a list comprehension, in curly brackets, with key: value at the front.

中文

列表按位置找东西:第一个、第二个。字典按名字找东西,这个名字叫键,就像价目表按水果的名字找到价钱。字典写在花括号里:每个键,一个冒号,然后是它的值,每一对之间用逗号隔开。

  • prices["mango"] 给出键 "mango" 的值。
  • prices["papaya"] = 30 加上一个新键。如果这个键已经在了,它就换成新的值:一个键在字典里永远不会出现两次。
  • "durian" in prices 问这个键在不在。prices["durian"] 会让程序停下来,因为没有这个键;prices.get("durian", 0) 就会给出 0。
  • prices.items() 给出每个键和它的值,所以 for 循环可以用两个名字:for fruit, price in prices.items():。

最后一行是一个字典推导式:像列表推导式,但写在花括号里,最前面是 key: value(键: 值)。

{fruit: price - 10 for fruit, price in prices.items()}

each key, and its value 每个键和它的值where they come from 它们从哪里来

# Step 5: a dictionary looks up a value by its key, like a price list.
# Write it in {curly brackets}: each key, a colon, then its value.

prices = {"mango": 60, "longan": 50, "banana": 25}

print(prices["mango"])           # the value of the key "mango"
prices["papaya"] = 30            # a new key, with its value
prices["mango"] = 55             # a key that is there already gets a new value
print(prices)

print("durian" in prices)        # in asks: is this key in the dictionary?
print(prices.get("durian", 0))   # get() gives 0 when the key is not there

for fruit, price in prices.items():
    print(fruit, "costs", price, "baht")

# A dictionary comprehension: the same keys, with the sale prices as values.
sale = {fruit: price - 10 for fruit, price in prices.items()}
print(sale)

What it printed它打印的内容

60
{'mango': 55, 'longan': 50, 'banana': 25, 'papaya': 30}
False
0
mango costs 55 baht
longan costs 50 baht
banana costs 25 baht
papaya costs 30 baht
{'mango': 45, 'longan': 40, 'banana': 15, 'papaya': 20}

English

Try this

  • Add print(prices["durian"]) and press Run. Read the name of the error, KeyError, then press Start again.
  • Keep only the cheap fruits: {fruit: price for fruit, price in prices.items() if price < 40}.

中文

试一试

  • 加一行 print(prices["durian"]),按 Run。读一读错误的名字 KeyError,再按 Start again。
  • 只留下便宜的水果:{fruit: price for fruit, price in prices.items() if price < 40}。

Check 5: what will this print? · 想一想 5:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 5: what will this print?

stock = {"mango": 3}
stock["mango"] = stock["mango"] + 2
stock["lime"] = 1
print(stock)

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

stock["mango"] + 2 is 3 + 2, and the 5 takes the place of the 3: there is only ever one "mango" key. "lime" is a new key, so it is added at the end.stock["mango"] + 2 就是 3 + 2,5 代替了 3:"mango" 这个键永远只有一个。"lime" 是一个新键,所以加在最后。

What Python printedPython 打印的内容

{'mango': 5, 'lime': 1}

Step 6

Sets: each thing only once · 第 6 步:集合:每样东西只有一个

English

A set holds each thing only once. set(basket) turns a list into a set, and the repeats are dropped: five fruits in the basket, but three kinds. in asks whether a thing is in the set.

Sets can be put together like the circles in a maths lesson. & keeps what is in both sets, | joins them, and - takes away what is in the second set.

A set has no order you can count on: Python may print the same set in a different order another time. So the program puts a set in order with sorted(), which gives a list, before it prints anything with more than one item.

Sets and dictionaries are both written in curly brackets. With colons it is a dictionary; without them, a set. The last line is a set comprehension. And {} on its own is an empty dictionary: an empty set is set().

中文

集合里的每样东西只有一个。set(basket) 把一个列表变成集合,重复的就去掉了:篮子里有五个水果,但只有三种。in 问一样东西在不在集合里。

集合可以像数学课上的圆圈那样放在一起。& 留下两个集合里都有的,| 把它们合起来,- 去掉第二个集合里有的。

集合没有你能指望的顺序:下一次,Python 可能会用不同的顺序打印同一个集合。所以程序在打印有不止一样东西的集合之前,先用 sorted() 把它排好顺序,sorted() 给出的是一个列表。

集合和字典都写在花括号里。有冒号的是字典;没有冒号的是集合。最后一行是一个集合推导式。还有,单独的 {} 是一个空字典:空集合要写成 set()。

# Step 6: a set holds each thing only once.

basket = ["mango", "longan", "mango", "banana", "mango"]
kinds = set(basket)              # the repeats are dropped
print(len(basket), "fruits,", len(kinds), "kinds")
print("longan" in kinds)

# What two stalls sell, and what they have in common.
north = {"mango", "longan", "lychee"}
south = {"mango", "banana", "coconut"}
print("Both:", north & south)                # & keeps what is in both
print("Either:", sorted(north | south))      # | joins them
print("Only north:", sorted(north - south))  # - takes away what south has

# A set comprehension: curly brackets, and no colon.
print({len(fruit) for fruit in basket})

What it printed它打印的内容

5 fruits, 3 kinds
True
Both: {'mango'}
Either: ['banana', 'coconut', 'longan', 'lychee', 'mango']
Only north: ['longan', 'lychee']
{5, 6}

English

Try this

  • Add "durian" to both stalls. What does north & south print now?
  • What does sorted(south - north) give: the fruits only the south stall sells?

中文

试一试

  • 给两个水果摊都加上 "durian"(榴莲)。现在 north & south 打印什么?
  • sorted(south - north) 给出什么:只有南边的摊子卖的水果?

Check 6: what will this print? · 想一想 6:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 6: what will this print?

seen = set()
for fruit in ["mango", "lime", "mango", "mango"]:
    seen.add(fruit)
print(len(seen))

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

add runs four times, but a set holds each thing once: adding "mango" again changes nothing. Only "mango" and "lime" are in it.add 运行了四次,但集合里每样东西只有一个:再加一次 "mango" 什么也不会变。里面只有 "mango" 和 "lime"。

What Python printedPython 打印的内容

2

Step 7

Generator expressions: one item at a time · 第 7 步:生成器表达式:一次一个

English

Write a list comprehension with round brackets instead of square ones, and you get a generator expression. It looks the same, but it does not make a list. It makes one item each time something asks for one. next() asks for one item; list(), sum() and a for loop ask for all of them. When it has handed out its last item, it is empty, and it stays empty.

Why use one? A list keeps every item in memory at the same time; a generator keeps only where it is up to. range(1_000_000) counts from 0 to 999,999 (the underscores only make a big number easier to read). In this page's Python, the list of those numbers takes 4224364 bytes, and the generator takes 100.

中文

把列表推导式的方括号换成圆括号,你就得到了一个生成器表达式。它看起来一样,但它不做出列表。每当有东西向它要一个,它就做出一个。next() 要一个;list()、sum() 和 for 循环把它们全要走。最后一个交出去以后,它就空了,而且一直是空的。

为什么要用它?列表把每个东西同时放在内存里;生成器只记住自己做到了哪里。range(1_000_000) 从 0 数到 999,999(下划线只是让大数更好读)。在这一页的 Python 里,这些数做成的列表占 4224364 字节,生成器只占 100 字节。

# Step 7: a generator expression is a list comprehension in round brackets.
# It makes its items one at a time, only when asked, and does not keep them.

import sys

prices = [60, 50, 25, 30]
sale = (price - 10 for price in prices)

print(next(sale))   # next() asks for one item
print(next(sale))
print(list(sale))   # list() takes all the items that are left
print(list(sale))   # none are left: a generator can be used only once

# A generator can go straight into sum(): one pair of brackets is enough.
print("Total on sale day:", sum(price - 10 for price in prices))

# The list holds a million numbers at once; the generator holds none of them.
big = [number for number in range(1_000_000)]
lazy = (number for number in range(1_000_000))
print(sys.getsizeof(big), "bytes for the list")
print(sys.getsizeof(lazy), "bytes for the generator")

What it printed它打印的内容

50
40
[15, 20]
[]
Total on sale day: 125
4224364 bytes for the list
100 bytes for the generator

English

Try this

  • Add a third print(next(sale)) after the second one. What does list(sale) give now?
  • Add one more print(next(sale)) at the very end, when nothing is left. Read the name of the error: StopIteration is how a generator says "no more".

中文

试一试

  • 在第二个 print(next(sale)) 后面再加一个。现在 list(sale) 给出什么?
  • 在最后、什么都不剩的时候,再加一个 print(next(sale))。读一读错误的名字:StopIteration 就是生成器在说"没有了"。

Check 7: what will this print? · 想一想 7:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 7: what will this print?

squares = (number * number for number in [1, 2, 3])
print(sum(squares))
print(sum(squares))

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

The first sum() takes all three squares: 1 + 4 + 9 is 14. The generator is then used up, so the second sum() gets nothing, and the sum of nothing is 0. With square brackets, both would print 14.第一个 sum() 拿走了全部三个平方数:1 + 4 + 9 等于 14。这个生成器就用完了,所以第二个 sum() 什么也拿不到,什么都没有的和是 0。如果用方括号,两次都会打印 14。

What Python printedPython 打印的内容

14
0

Step 8

Generator functions: yield makes a watcher · 第 8 步:生成器函数:yield 做出一个监视器

English

A generator expression fits on one line. When a generator needs more lines, write a function and use yield where you would write return. return ends a function. yield hands out one item and pauses the function right there. When the for loop asks for the next item, the function carries on from where it paused.

Look at the order of the lines in the dark box: the watcher and the loop take turns. To the for loop in main(), watch(orders) looks just like a list. That is the idea of a watcher: something you loop over, whose items come when they come.

中文

生成器表达式只有一行。当一个生成器需要更多行时,就写一个函数,在本来写 return 的地方写 yield。return 让函数结束。yield 交出一个东西,然后让函数就在那里暂停。当 for 循环要下一个东西时,函数从暂停的地方接着往下走。

看看深色框里每一行的顺序:监视器和循环轮流说话。对 main() 里的 for 循环来说,watch(orders) 看起来就像一个列表。这就是监视器的想法:一个你可以用循环走过的东西,它里面的东西什么时候来,就什么时候到。

# Step 8: a function with yield in it is a generator function.
# Each yield hands one item to the for loop, and the function waits there
# until the loop asks for the next one.


def watch(orders):
    for order in orders:
        print("  watcher: an order came in")
        yield order


def main():
    orders = ["mango", "longan", "mango"]
    for order in watch(orders):
        print("Sold:", order)


if __name__ == "__main__":
    main()

What it printed它打印的内容

  watcher: an order came in
Sold: mango
  watcher: an order came in
Sold: longan
  watcher: an order came in
Sold: mango

English

Try this

  • Make the watcher hand out only mangoes: put if order == "mango": above the yield, and four more spaces in front of it.
  • Put print(" watcher: no more orders") after the loop inside watch, with four spaces in front. When does it print?

中文

试一试

  • 让监视器只交出芒果:在 yield 上面加一行 if order == "mango":,yield 前面再多加四个空格。
  • 在 watch 里、循环的后面,加一行 print(" watcher: no more orders"),前面四个空格。它什么时候打印?

Check 8: what will this print? · 想一想 8:它会打印什么?

Pick an answer. Then Python shows you what it really prints, and you can change the code and run it.选一个答案。然后 Python 会告诉你它真正打印什么,你也可以改代码再运行。

# Check 8: what will this print?


def watch(orders):
    for order in orders:
        yield order.upper()


for order in watch(["tea", "rice"]):
    print("Sold", order)

✅ Right!✅ 答对了!

Not this one. The green answer is what Python prints.不是这个。绿色的答案才是 Python 打印的内容。

watch yields one order at a time, in capitals, so the loop runs twice and prints one line each time. The watcher never makes a list.watch 一次交出一个订单,变成大写,所以循环运行两次,每次打印一行。监视器从来没有做出一个列表。

What Python printedPython 打印的内容

Sold TEA
Sold RICE

Step 9

A watcher for real events: your clicks · 第 9 步:盯着真正事件的监视器:你的点击

English

In step 8 the orders were already in a list. A real stall does not know what it will sell next: its items are events, things that happen. Here the events are your clicks on the stall's buttons. The watcher still yields one item each time, and main() still uses a for loop, as if the clicks were a list. Press Run, then click the fruits: the loop runs once for each click. ✋ Close the stall ends the loop with break.

The new pieces:

  • from js import document: Pyodide lets Python use the web page around it. document is the page.
  • asyncio.Queue(): a waiting line. Each click joins the back of the line; the watcher takes clicks from the front.
  • addEventListener("click", ...) asks the browser to call a function at every click on the stall. create_proxy wraps the Python function, so the browser can keep calling it.
  • click.target.getAttribute("data-fruit"): click.target is the button that was clicked, and each button carries its fruit in data-fruit. A click between the buttons has none, so if fruit: skips it.
  • async def and await: await clicks.get() waits until a click is in the line. While it waits, the page keeps working, which is how your click can get in.
  • async for is a for loop over a watcher made with async def.
  • sold is a dictionary from step 5: each fruit is a key, and its value is how many were sold. sold.get(fruit, 0) + 1 counts one more, starting from 0 the first time. sold.values() gives just the counts, and sum() adds them up.
  • The last line is await main(), because main is an async function. In a file on your own computer you would write asyncio.run(main()) instead. This page is already running asyncio, so await works here. This program needs a web page anyway: js exists only in Pyodide.

While the stall is open, its Run button stays grey. Close the stall before you run another box. Until you do, the stall's lines are printed in the box you ran last.

中文

在第 8 步,订单早就在一个列表里了。真正的水果摊不知道下一个会卖出什么:它的东西是事件,就是发生的事情。这里的事件是你在水果摊按钮上的点击。监视器还是每次交出一个东西,main() 还是用 for 循环,就好像这些点击是一个列表。按 Run,然后点水果:每点一次,循环就运行一次。✋ Close the stall(收摊)用 break 结束循环。

新的东西:

  • from js import document:Pyodide 让 Python 可以使用它周围的网页。document 就是这个页面。
  • asyncio.Queue():一条排队的队伍。每次点击都排到队尾;监视器从队头把点击取走。
  • addEventListener("click", ...) 请浏览器在水果摊上每次被点击时调用一个函数。create_proxy 把 Python 函数包起来,让浏览器可以一直调用它。
  • click.target.getAttribute("data-fruit"):click.target 是被点的那个按钮,每个按钮都把它的水果写在 data-fruit 里。点在按钮之间的空白处就没有水果,所以 if fruit: 会跳过它。
  • async def 和 await:await clicks.get() 一直等到队伍里有一次点击。它等的时候,页面照样在工作,你的点击就是这样排进去的。
  • async for 是在用 async def 做出的监视器上走的 for 循环。
  • sold 是第 5 步的字典:每种水果是一个键,它的值是卖出了多少个。sold.get(fruit, 0) + 1 再多数一个,第一次从 0 开始。sold.values() 只给出这些数,sum() 把它们加起来。
  • 最后一行是 await main(),因为 main 是一个 async 函数。在你自己电脑上的文件里,你会写 asyncio.run(main())。这个页面已经在运行 asyncio 了,所以在这里可以用 await。反正这个程序也需要一个网页:js 只在 Pyodide 里才有。

水果摊开着的时候,它的 Run 按钮一直是灰色的。运行别的框之前,先收摊。在那之前,水果摊打印的内容会出现在你最后运行的那个框里。

# Step 9: a watcher for real events: your clicks on the stall's buttons.
# The for loop looks like a loop over a list, but each item is a click,
# and it arrives only when you click.

import asyncio
from js import document
from pyodide.ffi import create_proxy


async def watch(buttons):
    clicks = asyncio.Queue()
    buttons.addEventListener("click", create_proxy(clicks.put_nowait))
    while True:
        click = await clicks.get()   # wait for a click; the page keeps working
        fruit = click.target.getAttribute("data-fruit")
        if fruit:                    # a click between the buttons has no fruit
            yield fruit


async def main():
    print("The stall is open. Click a fruit.")
    sold = {}                        # each fruit, and how many were sold
    async for fruit in watch(document.getElementById("stall")):
        if fruit == "close":
            break
        sold[fruit] = sold.get(fruit, 0) + 1
        print("Sold:", fruit, "| so far:", sold)
    print("Closed after", sum(sold.values()), "sales.")


if __name__ == "__main__":
    await main()

What it printed, for clicks on mango, longan, the space between two buttons, mango, and close依次点 mango、longan、两个按钮之间的空白处、mango 和收摊时,它打印的内容

The stall is open. Click a fruit.
Sold: mango | so far: {'mango': 1}
Sold: longan | so far: {'mango': 1, 'longan': 1}
Sold: mango | so far: {'mango': 2, 'longan': 1}
Closed after 3 sales.

English

Try this

  • Close the stall by itself after five sales: if sum(sold.values()) == 5: and break inside the loop.
  • After the loop, print each fruit and how many were sold, with for fruit, count in sold.items():.

中文

试一试

  • 卖出五次以后自动收摊:在循环里写 if sum(sold.values()) == 5: 和 break。
  • 循环结束以后,用 for fruit, count in sold.items(): 打印每种水果卖出了多少个。

Step 10

Your own web page that runs Python · 第 10 步:你自己的、能运行 Python 的网页

English

Now you make a web page of your own that does what this page does. Write your Python in the box below and run it here first. Then press ⬇ Download my_page.html: you get one small file, a whole web page with your Python inside it.

  1. Find my_page.html in your Downloads folder.
  2. Double-click it. It opens in your browser, waits a few seconds while Python loads, and then shows what your Python printed. Your Python runs as the program there too, so __name__ is "__main__" and main() runs.
  3. Change the code here, download again, and open the new file. (Each download may get a new name, such as my_page (1).html.)

Your page loads Pyodide from the internet each time it opens, so it needs the internet. You can send the file to a friend: it runs on their computer too.

中文

现在你来做一个你自己的网页,让它做这一页做的事。在下面的框里写你的 Python,先在这里运行一下。然后按 ⬇ Download my_page.html:你会得到一个小文件,它是一个完整的网页,里面装着你的 Python。

  1. 在"下载"文件夹里找到 my_page.html。
  2. 双击它。它会在浏览器里打开,等几秒钟让 Python 载入,然后显示你的 Python 打印的内容。你的 Python 在那里也是作为程序运行的,所以 __name__ 是 "__main__",main() 会运行。
  3. 在这里改代码,再下载一次,打开新的文件。(每次下载的文件可能会有新名字,比如 my_page (1).html。)

你的网页每次打开都要从网上载入 Pyodide,所以它需要网络。你可以把这个文件发给朋友:它在他们的电脑上也能运行。

def signs(fruits):
    return [fruit.upper() for fruit in fruits]


def main():
    for sign in signs(["mango", "longan", "banana"]):
        print(sign)


if __name__ == "__main__":
    main()

What it printed它打印的内容

MANGO
LONGAN
BANANA
See the whole page you download看看你下载的整个网页

English

Most of the file is the same every time. The <script src=...> line loads Pyodide. Your Python sits in its own <script type="text/python">: the browser does not run that kind of script by itself. The last script is JavaScript: it starts Python, sends what print prints into the page, and runs your code. The download button puts the code from the box above where the fruit program is now.

中文

这个文件的大部分每次都一样。<script src=...> 那一行载入 Pyodide。你的 Python 放在它自己的 <script type="text/python"> 里:浏览器不会自己运行这种 script。最后一个 script 是 JavaScript:它启动 Python,把 print 打印的东西送进页面,再运行你的代码。下载按钮会把上面框里的代码放到现在水果程序所在的地方。

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>My Python page</title>
<!-- This line loads Pyodide: Python, turned into WebAssembly so a browser can run it. -->
<script src="https://cdn.jsdelivr.net/pyodide/v0.26.4/full/pyodide.js"></script>
</head>
<body>
<h1>My Python page</h1>
<pre id="output">Python is loading...</pre>

<!-- Your Python. The browser cannot run it by itself: the script below hands it to Pyodide. -->
<script type="text/python" id="my-python">
def signs(fruits):
    return [fruit.upper() for fruit in fruits]


def main():
    for sign in signs(["mango", "longan", "banana"]):
        print(sign)


if __name__ == "__main__":
    main()
</script>

<!-- JavaScript, the browser's own language: start Python, then run your Python above. -->
<script>
async function main() {
  const output = document.getElementById("output");
  const pyodide = await loadPyodide();
  output.textContent = "";
  pyodide.setStdout({ batched: (line) => { output.textContent += line + "\n"; } });
  try {
    pyodide.runPython(document.getElementById("my-python").textContent);
  } catch (error) {
    output.textContent += error.message;
  }
}
main();
</script>
</body>
</html>

English

Make it your own

  • Put in your own stall: a dictionary of what it sells, and for how much.
  • Write a sale_price function with a default, and use it in a list comprehension.
  • Add the total of all the sale prices, with sum() and a generator expression.

中文

把它变成你自己的

  • 放上你自己的水果摊:用一个字典写下它卖什么,卖多少钱。
  • 写一个带默认值的 sale_price 函数,在列表推导式里用它。
  • 用 sum() 和一个生成器表达式,加上所有减价价钱的总和。