Python高速入门¶
本章会快速带领大家过一遍Python的基本语法和常用特性。除了用作预习材料以外,还可以在期末考试复习的时候来快速回顾其基本语法与常用特性。
在PKU,Python主要是文科生学习较多,因此我这一章的节奏会比C++的慢许多,也不会像C++一样涉及那么多的名词(内存空间、指针、引用等)。当然,Python作为目前最流行的脚本语言,对于理科生而言也会是非常有用的,我将会单开一章介绍Python给理科生怎么玩。
Python的基本语法¶
我在C++的章节中提过,写代码的本质是和计算机说话。如果说C++更像正式信件,有信头、正文、落款,那么Python更像是口语化的对话。
比方说,我们写一个最基本的程序:
str = "Hello, world!"
print(str)
执行以上代码,我们会看到输出:“Hello, world!”。
逐行拆解代码,第一行的意思是,我告诉计算机“我有一个变量叫做str,它的值是Hello, world!”;第二行,则告诉计算机“请把变量str的值打印出来”。
看起来非常简单。
Python的变量¶
Python的变量非常简单,虽然也需要遵从“先声明再使用”的规则,但是其声明是隐式的,直接给变量赋值就行了。同时,Python的语法也非常宽松,对于变量并不需要指定其类型,一个变量在不同的时间可以是任何类型的值。
a = 10 # 整数
a = 3.14 # 浮点数
a = 1 + 2j # 复数
a = "Hello, world!" # 字符串
a = [1, 2, 3] # 列表
a = (1, 2, 3) # 元组
a = {1, 2, 3} # 集合
a = {"name": "Alice", "age": 30} # 字典
a = True # 布尔值
a = None # 空值
= 不是数学上的等号,它的意思是“把右边的值赋给左边的变量”。而且,Python并不需要担心像C++一样的溢出问题,Python会自动处理大数。
一切都比C++简单得多。
Python的运算¶
有时候,我们需要计算机帮助我们执行一些运算。例如:
a = 10
b = 3
print(a + b) # 输出13
print(a - b) # 输出7
a += b # 相当于a = a + b
Python支持许多常见的运算符:
- 四则运算:加 + 、减 - 、乘 * 、除 / (浮点除法)和取整除 // (整数除法)。
- 乘方:使用 ** 表示乘方运算。
- 取余数:使用 % 表示取余数运算。
对于字符串类型的变量,使用加法 + 可以连接两个字符串,例如:
str1 = "Hello, "
str2 = "world!"
print(str1 + str2) # 输出"Hello, world!"
输入、输出¶
Python的输入输出非常简单。我们可以使用 print() 函数来输出内容,而使用 input() 函数来获取用户输入。input函数会暂停程序的执行,等待用户输入内容,并将输入的内容作为字符串返回。
input里面的内容是提示用户输入的文本。
例如:
name = input("请输入你的名字:")
print("Hello, " + name + "!")
我们还可以使用一些格式化字符串的方式来输出内容,例如:
name = "Alice"
age = 30
print(f"Hello, {name}! You are {age} years old.")
我们还可以使用一些参数来进一步格式化输出内容,例如:
print("Hello, World!", end="") # 不换行输出
print("Hello, 1", "Hello, 2", sep=", ") # 使用逗号分隔输出
print("Hello, World!", file=open("output.txt", "w")) # 输出到文件
上述代码中的第二个print函数使用了 sep 参数来指定输出内容之间的分隔符,默认是空格。它的输出将会是:“Hello, 1, Hello, 2”。
上述输出到文件的例子会将“Hello, World!”写入同目录下的output.txt文件中,这个w的意思是“写入模式”,如果文件不存在则会创建,如果存在则会覆盖原有内容。如果改成 a ,则会以追加模式打开文件,即在文件末尾添加内容。
注释¶
注释是代码中用于解释说明的部分,它会被解释器忽略,不会影响程序的运行。Python中的单行注释使用井号 # 开头,这一行后面的内容都是注释;或者使用三引号来框住注释内容,可以创建多行注释。
# 这是一个注释
print("Hello, world!") # 这也是一个注释
"""
这是一个多行注释
可以包含多行内容
"""
在阻止部分代码执行的时候,我们一般不习惯于直接删除这些代码,而是使用注释。这样做的好处是可以留痕,便于以后的恢复(解注释);这就是程序员们常说的“注释掉”代码。在VS Code等编辑器中,常用的一键注释是 Ctrl + / ,它会自动将光标所在的一行或多行代码注释掉。
类型强转¶
虽然Python是动态类型语言,同一个变量可以在不同的时间点上拥有不同的类型,但是在某一个确定的时刻,一个变量的类型是确定的。例如我们给a赋值 a = "12321",那么这个时候a的类型就是字符串。如果对该变量进行和数的加减操作,代码将会无法执行。
有些时候,我们希望把一些变量的类型转换为其他类型,例如把字符串“12321”转换为整数12321。我们可以使用一些函数来实现类型转换:
a = "12321"
print(a+1) # 报错,因为a是字符串,1是整数,不能直接相加
b = int(a) # 将字符串转换为整数,现在b是整数12321
print(b+1) # 能执行,输出12322
我们可以使用 int() 函数将字符串转换为整数,使用 float() 函数将字符串转换为浮点数,使用 str() 函数将其他类型转换为字符串等。
控制程序的执行流程¶
条件语句¶
有时候,我们需要让计算机根据条件来执行不同的操作。Python提供了 if 语句来实现这一点。
例如,我们可以根据用户输入的年龄来判断是否成年:
age = int(input("请输入你的年龄:"))
if age >= 18:
print("你是成年人。")
else:
print("你是未成年人。")
if 语句后面跟着一个条件表达式(age >= 18),如果条件为真,则执行冒号后面的代码块;否则,执行 else 后面的代码块。
Python使用缩进来表示代码块的层次结构,且对缩进要求极为严格。通常情况下,我们用一个制表符或者四个空格来表示一个缩进层级。要打出制表符,可以按下Tab键。
循环语句¶
有时候,我们需要让计算机重复执行某些操作。Python提供了 for 和 while 两种循环语句。
比方说我们使用for循环来输出1到10的数字:
for i in range(1, 11):
print(i)
range(1, 11)生成了一个从1到10的整数序列, for 循环会依次将每个数字赋值给变量 i ,并执行代码块中的操作。
我们也可以使用 while 循环来实现类似的功能:
i = 1
while i <= 10:
print(i)
i += 1
while 循环内的代码块会一直循环执行,直到条件 i <= 10 不再满足为止。
可以看到,我们在这个循环内部对i进行了增加操作。如果没有这个操作,循环将会无限进行下去,技术上一般叫做“死循环”。表现在程序上,上述程序会不断地输出1,直到你强制终止程序。而一般for循环则不会出现这种情况,因为它会自动处理循环变量和循环条件。
有时候,我们在使用for循环的时候并不关心循环变量的值,只是想要重复执行某些操作。这时,我们可以使用下划线作为循环变量的占位符:
for _ in range(5):
print("Hello, World!")
break 和 continue 语句可以用来控制循环的执行流程。使用 break 可以提前退出循环,而使用 continue 可以跳过当前迭代,继续下一次循环。
例如:
for i in range(1, 11):
if i == 5:
break # 当i等于5时,退出循环
print(i)
for i in range(1, 11):
if i == 5:
continue # 当i等于5时,跳过当前迭代
print(i)
复合数据类型¶
Python提供了多种复合数据类型,用于存储多个值。最常用的有列表(list)、元组(tuple)、集合(set)和字典(dict)。
列表(list)¶
列表是一个有序的可变集合,可以存储任意类型的元素。我们可以使用方括号 [] 来创建一个列表,并使用索引来访问元素。索引从0开始,如果我们试图访问一个不存在的索引,会抛出 IndexError 异常。
例如:
my_list = [1, 2, 3, "Hello", True]
print(my_list[0]) # 输出1
print(my_list[3]) # 输出"Hello"
print(my_list[-1]) # 输出True(负索引从后往前计数)
print(my_list[5]) # 抛出IndexError异常
我们可以使用 append() 方法添加元素,使用 remove() 方法删除元素,使用 sort() 方法对列表进行排序等。具体什么是“方法”,详见“函数和模块”一节。
例如:
my_list = [1, 2, 3, "Hello"]
my_list.append(4) # 添加元素4
my_list.remove("Hello") # 删除元素"Hello"
my_list.sort() # 对列表进行排序
print(my_list) # 输出[1, 2, 3, 4]
可以利用加号 + 来连接两个列表,使用乘号 * 来重复列表。例如:
my_list1 = [1] * 1000 # 创建一个列表,这个列表包含1000个1
my_list2 = [2, 3, 4]
print([1]+my_list2) # 输出[1, 2, 3, 4]
元组(tuple)¶
元组和列表类似,但是它不可以被修改(不可变)。我们可以使用圆括号 () 来创建一个元组。元组的元素也可以通过索引访问。
例如:
my_tuple = (1, 2, 3, "Hello", True)
print(my_tuple[0]) # 输出1
print(my_tuple[3]) # 输出"Hello"
print(my_tuple[-1]) # 输出True
print(my_tuple[5]) # 抛出IndexError异常
元组的元素不能被修改,但我们可以通过重新赋值来创建一个新的元组。 例如:
my_tuple = (1, 2, 3)
my_tuple_1 = my_tuple + (4,) # 创建一个新的元组
print(my_tuple_1) # 输出(1, 2, 3, 4)
集合(set)¶
集合是一个无序的可变集合,不能包含重复元素。我们可以使用花括号 {} 来创建一个集合。集合的元素也可以通过索引访问,但由于集合是无序的,所以集合没有索引这种东西。
集合也有类似于列表的添加和删除元素的方法,例如 add() 和 remove() 。但是集合不支持排序:我们无法对一个本来就没有“顺序”这个定义的东西进行排序。
例如:
my_set = {1, 2, 3, "Hello", True}
print(my_set) # 输出{1, 2, 3, "Hello", True}
my_set.add(4) # 添加元素4
my_set.remove("Hello") # 删除元素"Hello"
print(my_set) # 输出{1, 2, 3, 4, True}
字典(dict)¶
字典是一个无序的可变集合,存储键值对(key-value pairs)。我们可以使用花括号 {} 来创建一个字典,并使用键来访问值。字典的键必须是不可变类型(如字符串、整数等),而值可以是任意类型。
例如:
my_dict = {"name": "Alice", "age": 30, "is_student": False}
print(my_dict["name"]) # 输出"Alice"
print(my_dict["age"]) # 输出30
print(my_dict["is_student"]) # 输出False
my_dict["age"] = 31 # 修改键"age"对应的值
print(my_dict) # 输出{"name": "Alice", "age": 31, "is_student": False}
对于字典,我们可以使用 keys() 方法获取所有的键,使用 values() 方法获取所有的值,使用 items() 方法获取所有的键值对。每一个键值对都是一个元组。
例如:
print(my_dict.keys()) # 输出dict_keys(['name', 'age', 'is_student'])
print(my_dict.values()) # 输出dict_values(['Alice', 31, False])
print(my_dict.items())
# 输出dict_items([('name', 'Alice'), ('age', 31), ('is_student', False)])
高级操作¶
我们可以使用for循环来对以上各种复合数据类型进行遍历:
for item in my_list:
print(item) # 遍历列表
for item in my_tuple:
print(item) # 遍历元组
for item in my_set:
print(item) # 遍历集合
for key, value in my_dict.items():
print(key, value) # 遍历字典
for value in my_dict.values():
print(value) # 遍历字典的值
我们还可以使用对这些复合数据类型进行切片。切片的语法是 start:end:step ,其中 start 是起始索引, end 是结束索引(不包含), step 是步长(可以省略)。
例如:
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(my_list[2:5]) # 输出[3, 4, 5]
print(my_list[::2]) # 输出[1, 3, 5, 7, 9](步长为2)
print(my_list[::-1]) # 输出[10, 9, 8, 7, 6, 5, 4, 3, 2, 1](反转列表)
我们还可以使用列表推导式(list comprehension)来创建新的列表。列表推导式是一种简洁的语法,可以在一行代码中创建一个新的列表。 例如:
squares = [x**2 for x in range(1, 11)]
print(squares) # 输出[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
evens = [x for x in range(1, 11) if x % 2 == 0]
print(evens) # 输出[2, 4, 6, 8, 10]
以上代码中,通用语法是 [thing for item in iterable if condition] ,其中 thing 是你要的东西, iterable 是可迭代对象(如列表、元组等), condition 是可选的条件。
看起来真就像说话一样。
字符串¶
字符串指的是一串字符的序列。Python中的字符串是不可变的,这意味着一旦创建,就不能修改其内容。
比方说一个字符串:
my_string = "Hello, world!"
print(my_string[0]) # 输出'H'
print(my_string[7]) # 输出'w'
print(my_string[-1]) # 输出'!'
print(my_string[13]) # 抛出IndexError异常
my_string[0] = 'h' # 抛出TypeError异常,因为字符串是不可变的
字符串可以看作是一个字符的元组,因此我们可以使用切片来获取字符串的子串:
print(my_string[0:5]) # 输出'Hello'
print(my_string[7:]) # 输出'world!'
print(my_string[:5]) # 输出'Hello'
print(my_string[::2]) # 输出'Hlo ol!'
print(my_string[::-1]) # 输出'!dlrow ,olleH'(反转字符串)
我们还可以使用字符串的各种特有方法来操作字符串,例如:
my_string.lower() # 'hello, world!'(转换为小写)
my_string.upper() # 'HELLO, WORLD!'(转换为大写)
my_string.strip() # 'Hello, world!'(去除首尾空格)
my_string.replace("world", "Python") # 'Hello, Python!'(替换子串)
my_string.split(", ") # ['Hello', 'world!'](按逗号分割字符串)
my_string.find("world") # 7(查找子串的位置)
my_string.startswith("Hello") # True(检查字符串是否以指定子串开头)
my_string.endswith("!") # True(检查字符串是否以指定子串结尾)
my_string.count("o") # 2(统计子串出现的次数)
除了使用双引号来定义字符串,我们还可以使用单引号来定义字符串。两者完全等价。当然,由双引号框起来的字符串中包含单引号是可行的,反过来也可行,这个可以用来避免转义字符的使用。
my_string = 'Hello, world!'
print(my_string) # 输出'Hello, world!'
我们还可以使用三引号(单引号或双引号)来定义多行字符串,这样就可以在字符串中包含换行符了:
my_string = """Hello, world!"""
print(my_string) # 输出'Hello, world!'
my_string = '''Hello,
world!'''
print(my_string) # 输出'Hello,\nworld!'
对于字符串,我们可以使用加号 + 来连接两个字符串,使用乘号 * 来重复字符串。这个操作和列表是一样的。例如:
print("Hello, " + "world!") # 输出'Hello, world!'
print("Hello! " * 3) # 输出'Hello! Hello! Hello! '
\n 指的是换行。
函数和模块¶
函数¶
有时候,我们有一个功能需要多次使用,这时我们可以将其封装成一个函数(也叫方法)。Python使用 def 关键字来定义函数。简单地说,函数可以把套路打包成一句话,就像汉语中的成语。
例如,我们可以定义一个函数来计算两个数的和:
def add(a, b):
return a + b
一个函数应该以def开头,后面跟着函数名和参数列表。函数体使用缩进来表示。一个函数应该包含一个返回值,使用 return 关键字来返回结果就可以了。
在定义函数之后,我们可以在任意地方调用它,只需要提供函数希望的参数即可。例如:
result = add(3, 5)
print(result) # 输出8
我们也可以在函数中使用默认参数,这样在调用函数时可以省略某些参数:
def greet(name="World"):
print(f"Hello, {name}!")
greet() # 输出"Hello, World!"
greet("Alice") # 输出"Hello, Alice!"
函数还可以使用递归来解决问题,即函数在其内部调用自身。例如计算阶乘:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n - 1)
print(factorial(5)) # 输出120
递归从某种程度上说也可以认为是循环的一种形式。同样的,递归也要有终止条件,否则会导致无限递归。
有些时候,我们希望函数参数只能存入某种类型的数据,或者使得某些函数返回某类特定的值。这个时候,类型注释就派上了用场。类型注释一般遵循冒号+类型或者箭头+类型的形式,例如:
def add(a: int, b: int) -> int:
return a + b
add 接受两个整数参数 a 和 b ,并返回一个整数。类型注释可以帮助我们更好地理解函数的输入输出类型,也可以在IDE中提供更好的代码提示。
但是我们需要注意一个问题:类型注释不会强制执行类型检查,换句话说它实际上依然是个注释而已——是给人方便开发用的,不是给电脑看的!
模块¶
有时候,一些功能大家都在用。这时候,为了防止重复工作,程序员们把这些功能打包成一个模块,供大家使用;而我们使用者只需要使用 import 关键字来导入模块,就可以使用模块中的许多方便的方法了。安装模块的方法参见正文部分 Conda 一节。
例如,我们可以导入Python的内置模块 math 来使用数学模块:
import math
print(math.sqrt(16)) # 输出4.0(计算平方根)
print(math.pi) # 输出3.141592653589793(圆周率)
print(math.factorial(5)) # 输出120(计算阶乘)
有时候,我们只需要导入模块中的某个函数或类,可以使用 from ... import ... 语法:
from math import sqrt, pi
print(sqrt(16)) # 输出4.0
print(pi) # 输出3.141592653589793
print(factorial(5)) # 抛出NameError异常,因为factorial没有被导入
还有一些时候,模块名称太长(例如matplotlib),我们可以使用 as 关键字来给模块起一个别名:
import matplotlib.pyplot as plt
plt.plot([1, 2, 3], [4, 5, 6]) # 画一条线
plt.show() # 显示图形
一些特定的模块有着约定俗成的简称,例如 numpy 通常简称为 np , pandas 通常简称为 pd , matplotlib.pyplot 通常简称为 plt 等。如果我们希望写出大家都能读懂、易于维护的代码,最好遵循这些约定。
文件操作¶
有时候,我们需要将数据保存到文件中,或者从文件中读取数据。Python提供了简单的文件操作接口。
例如,我们可以使用 open() 函数打开一个文件,并使用 read() 方法读取文件内容:
with open("example.txt", "r") as file:
content = file.read()
print(content) # 输出文件内容
with open("example.txt", "w") as file:
file.write("Hello, world!") # 写入内容到文件
with open("example.txt", "a") as file:
file.write("\nThis is a new line.") # 追加内容到文件
我们可以使用 with 语句来自动管理文件的打开和关闭,这样可以避免忘记关闭文件导致资源泄漏的问题。
Python的面向对象¶
Python虽然是一种脚本语言,更倾向于描述过程,但是它也支持面向对象编程(OOP)。
面向对象编程可以理解为把现实世界中的事物抽象成对象,把有着相似属性和行为的事物归为一类,通过对象来组织代码。每个对象都有自己的属性(数据)和方法(行为),通过对象之间的交互来实现复杂的功能。举个例子,我们可以把“人”抽象成一个类(class),每个人都是这个类的一个实例(instance)。每个人都有自己的属性(如姓名、年龄等)和方法(如说话、走路等);同时,不同的人之间也有自己的特性(如性别、职业等),这些特性可以通过继承、多态等方式来实现。
类和对象的基本定义¶
一个Python的常见类定义如下:
class Person:
def __init__(self, name, age):
self.name = name # 属性:姓名
self.age = age # 属性:年龄
def greet(self):
print(f"Hello, my name is {self.name} and I am {self.age} years old.")
Person 的类,它有两个属性( name 和 age )和一个方法( greet )。 __init__() 方法是类的构造函数,用于初始化对象的属性。如果在类中希望使用自身的属性,我们需要使用 self 关键字来引用当前对象。这个 self 参数是必须的,它指向当前对象的实例。
我们可以使用这个类来创建一个对象,并调用它的方法:
person = Person("Alice", 30) # 创建一个Person对象
person.greet() # 调用greet方法,输出"Hello, my name is Alice and I am 30 years old."
person2 = Person("Bob", 25) # 创建另一个Person对象
在现代Python中,以上定义可以变得更简单:
from dataclasses import dataclass
@dataclass # 使用数据类装饰器
class Person:
name: str # 属性:姓名
age: int # 属性:年龄
def greet(self) -> None:
print(f"Hello, my name is {self.name} and I am {self.age} years old.")
__init__() 、 __repr__() 等。
继承和多态¶
继承的意思是创建一个类(子类)来继承另一个类(父类)的属性和方法,子类可以扩展或修改父类的功能。举个例子:“动物”可以是一个类,而“猫”和“狗”都可以是“动物”的子类,它们继承了“动物”的属性和方法,但也有自己的特性。
实际代码表现大概是这样的:
class Animal:
def __init__(self, name):
self.name = name
def eat(self):
print(f"{self.name} is eating.")
def speak(self):
print(f"{self.name} makes a sound.")
class Dog(Animal):
def speak(self): # 重写父类方法
print(f"{self.name} barks.")
class Cat(Animal):
def speak(self): # 重写父类方法
print(f"{self.name} meows.")
mycat = Cat("Carol") # 创建一个Cat对象
mycat.eat() # 输出"Carol is eating."
mycat.speak() # 输出"Carol meows."
mydog = Dog("Dave") # 创建一个Dog对象
mydog.eat() # 输出"Dave is eating."
mydog.speak() # 输出"Dave barks."
在这个例子中, Dog 和 Cat 都是 Animal 的子类,它们继承了 Animal 的属性和方法,并重写了 speak() 方法。这样,我们可以通过多态来调用不同子类的同名方法,而不需要关心具体的实现细节。另一方面,子类也可以执行没有被重写的父类方法,例如上文中的 eat() 方法。
在有些时候,我们希望一个子类在重写某方法的时候会先将父类的方法执行一遍,然后再执行自己的逻辑。这时,我们可以使用 super() 函数来调用父类的方法:
class Dog(Animal):
def speak(self):
super().speak() # 调用父类的speak方法
print(f"{self.name} barks.")
mydog = Dog("Eve") # 创建一个Dog对象
mydog.speak() # 输出"Eve makes a sound."和"Eve barks."两行
有了OOP,我们就可以更简单地组织代码,便于维护和扩展。
Python与多文件¶
在实际开发中,我们通常会将代码分成多个文件,以便于管理和复用。Python提供了模块化的机制,可以让我们轻松地在不同文件之间共享代码。
假设我们有一个文件 math_utils.py ,里面定义了一些数学相关的函数:
# math_utils.py,同目录下的文件
def add(a, b):
return a + b
class MyClass:
def __init__(self, value):
self.value = value
def double(self):
return self.value * 2
---
# src/foo.py,非同目录下的文件
def foo():
print("foo")
然后,我们可以在另一个文件 main.py 中导入这个模块,并使用其中的函数:
# main.py
# 如果在同目录下,直接导入即可
from math_utils import add, MyClass
result = add(3, 5)
print(result) # 输出8
obj = MyClass(10)
print(obj.double()) # 输出20
# 这么导入也行
import math_utils
result = math_utils.add(3, 5)
print(result) # 输出8
# 如果不在同目录下,需要指定路径。实际上src虽然是一个文件夹,但在这里也被看作一个模块
from src.foo import foo
foo() # 输出"foo"
Python的多文件极为简单,完全不需要像C++那样使用复杂的头文件和链接器。只要确保文件在同一目录下,或者在Python的搜索路径中,就可以把这些次要文件当作模块来导入,操作甚至和导入内置模块一模一样。
Python语法小练¶
Example
角谷猜想是一个有趣的数学问题:从任意整数开始,如果他是奇数就乘以3加1,如果是偶数就除以2,如此反复循环,最终一定会得到1。目前还没有人证明这个猜想(但是它大概率是正确的),但是我们可以用C++来验证一些具体值。要求输入一个整数,输出这个整数经过角谷猜想的处理后,最终得到1所需的每一步,例如输入6,输出6, 3, 10, 5, 16, 8, 4, 2, 1。
答案
我们读题,把人类语言逐句地改成Python语言。
从任意整数开始,如果他是奇数就乘以3加1,如果是偶数就除以2。 这句话提示我们要做一个条件判断:
if n % 2 == 0: # 如果n是偶数
n = n // 2 # 除以2
else: # 如果n是奇数
n = n * 3 + 1 # 乘以3加1
如此反复循环,最终一定会得到1。 这句话提示我们要使用循环来反复执行这个操作,直到n变成1为止。我们可以使用while循环来实现:
while n != 1: # 当n不等于1时
# 循环体
要求输入一个整数,输出这个整数经过角谷猜想的处理后,最终得到1所需的每一步。 这句话提示我们输入和输出的处理。
接下来,我们把这些代码像乐高积木一样组合起来,就可以完成这个练习了:
n = int(input("请输入一个整数:")) # 输入一个整数
n = print(n) # 输出初始值
while n != 1: # 当n不等于1时
if n % 2 == 0: # 如果n是偶数
n = n // 2 # 除以2
else: # 如果n是奇数
n = n * 3 + 1 # 乘以3加1
print(n) # 输出当前的n
Example
素数在数学中是一个非常重要的概念,它指的是只能被1和它本身整除的自然数。素数在密码学、数据加密等领域有着广泛的应用。一般我们可以使用筛法找到素数:在一系列整数中,先找到最小的素数(2),然后将它的倍数都去掉;然后再找到下一个最小的素数(3),再将它的倍数都去掉;如此反复,直到所有的数都被处理完。现在输出1到1000之间的所有素数1。
答案
题目说使用筛法找素数。那么,我们可以创建一个列表来存储1到1000之间的所有整数,然后使用一个循环来筛选出素数。如果不是素数,则删除之。那么可以这样:
# 创建一个列表来存储整数
numbers = list(range(2, 1001))
for i in range(2, 1001):
if i in numbers: # 如果i还在列表中
for j in range(2, 1001 // i): # 从2倍开始,删除i的所有倍数
if i * j in numbers: # 确保i*j还在列表里
numbers.remove(j * i) # 删除j * i
print(numbers) # 输出所有素数
以上算法是正确的,但是执行起来比较慢。这是因为对于2到1000之间的所有数,我们都需要确定它究竟是不是在列表之中,总共判断了近1000次。有没有什么更简单的办法呢?
我们知道,数组的索引天生就是自然数。我们可以创建一个布尔数组来标记每个数是否是素数,初始时假设所有数都是素数。然后,我们从2开始,找到第一个素数,标记它的所有倍数为非素数。对于不是素数的数,我们可以跳过扫描。这样,代码就会快许多了。以下是一个实现:
numbers = [True] * 1001 # 创建一个布尔数组,初始时假设所有数都是素数
numbers[0] = numbers[1] = False # 0和1不是素数
for i in range(2, 1001): # 从2开始,遍历所有数
if not numbers[i]: # 如果i不是素数
continue # 跳过
else:
for j in range(2, 1001 // i): # 从2开始,标记i的所有倍数为非素数
numbers[j * i] = False # 标记为非素数
print([i for i in range(1001) if numbers[i]]) # 输出所有素数
当然,Python也是一门语言,所有的语言都需要大量的练习和实践才能掌握;仅仅是看完这一章可能只需要一天,但是真正熟练应用语法可能需要一周的时间,熟练玩转Python可能需要一个学期甚至还要多的时间。不过,不用担心:路在脚下,行则将至,只要你坚持下去,就一定能够掌握Python这个强大的工具。
Jupyter Notebook¶
Jupyter Notebook是一种交互式的计算环境,可以让我们在浏览器或其他前端中编写和运行Python代码。Jupyter Notebook的文件扩展名是 .ipynb ,它可以包含代码、文本、数学公式、图像等多种内容,非常适合用于数据分析、机器学习等领域。
要使用Jupyter Notebook,首先需要安装它。可以使用pip命令来安装:
pip install notebook
ipykernel 这类包。
安装完成后,可以使用以下命令启动Jupyter Notebook服务器:
jupyter notebook
.ipynb 文件,然后用Jupyter Notebook或VS Code打开它。
在该环境中,我们可以创建多个单元格(cell),每个单元格可以包含代码或文本。代码单元格可以直接运行Python代码,并显示输出结果;文本单元格可以使用Markdown语法来编写格式化的文本。所有的cell都可以单独运行,互不影响;不过更常见的是把常规的Python代码拆成许多个cell,然后从上到下依次运行,这样在下边的cell中就可以使用上边cell中定义的变量和函数了。两个代码cell中间也可以插入一个文本cell来解释代码的作用。
在运行cell时,其输出不会显示在终端或者控制台中,而是直接显示在cell的下方;matplotlib等绘图库生成的图像也会直接显示在cell下方而不是弹出一个新窗口,非常方便。另外,Jupyter Notebook还支持魔法命令(magic commands),可以用来执行一些特殊的操作,例如:
%timeit sum(range(1000)) # 计算代码运行时间
%matplotlib inline # 在notebook中显示matplotlib图像
Jupyter Notebook在被关闭后不会清空其中的代码输出和可视化结果等,因此我们可以在下次打开时继续查看之前的结果。不过需要注意的是,Jupyter Notebook的内核(kernel)在关闭后会被重置,所有的变量和函数都会被清空。因此,在重新打开notebook后,如果需要使用之前定义的变量和函数,需要重新运行相应的代码cell。
在实际使用中,Jupyter Notebook可以极大地提高我们的工作效率和代码可读性。它非常适合用于数据分析和机器学习等领域,这些领域代码、论述、结果并重。值得高兴的是, ipynb 文件本身就可以作为实验报告或者项目文档的一部分,方便分享和交流。即使不能提交该文件,也可以使用一些工具将其转换为HTML、PDF等格式,方便阅读和打印。它也适用于演示、教学等场景,可以让观众更直观地理解代码的运行过程和结果。然而,如果是代码为主的工作(例如工程),则不应使用它,因为它不适宜多文件协作开发,且版本控制不便。我们应当根据具体的需求选择合适的工具。
-
规定1不是素数,有兴趣的可以阅读这方面材料“为什么1既不是素数也不是合数”。 ↩