python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > python编程入门到实践

Python编程从入门到实践指南(变量和简单数据类型:从“会写“到真正理解)

作者:承渊政道

在学习Python编程时,从基础开始是非常重要的,特别是对于初学者,理解变量和简单数据类型是构建任何程序的基石,下面,我们将通过一些基本概念和示例来帮助你从入门到真正理解这些内容,感兴趣的朋友一起看看吧

1.运行hello_world.py时发生的情况

运行hello_world.py时,Python都做了些什么呢?下面来深入研究一下.实际上,即便是运行简单的程序,Python所做的工作也相当多:

print("Hello Python world!")

运行上述代码时,你将看到如下输出:

运行文件hello_world.py时,末尾的.py指出这是一个Python程序,因此编辑器将使用Python解释器来运行它.Python解释器读取整个程序,确定其中每个单词的含义.例如,看到单词print时,解释器就会将括号中的内容打印到屏幕,而不会管括号中的内容是什么.

编写程序时,编辑器会以各种方式突出程序的不同部分.例如,它知道print是一个函数的名称,因此将其显示为蓝色;它知道"Hello Python world!"不是Python代码,因此将其显示为橙色.这种功能称为语法突出,在你刚开始编写程序时很有帮助.

假设文件 hello.py 中只有一行:

print("Hello, Python!")

从终端执行:

python hello.py

可以把过程简化为四步:

  1. 操作系统启动 Python 解释器,并把 hello.py 的路径交给它.
  2. 解释器读取源代码,先分析字符是否组成合法的 Python 语法.
  3. Python 执行函数调用 print(...),其中实参是一个字符串对象.
  4. print() 把字符串的文本表示写到标准输出,终端将其显示出来.

严格来说,常见的 CPython 实现还会先把源代码编译为字节码,再由 Python 虚拟机执行.初学阶段不必记住内部指令,但要建立一个重要认识:源代码不是被"当作普通文字打印",而是先被解释成有语义的结构再执行.

1.1.py与语法高亮分别负责什么

.py 是 Python 源文件常用的扩展名.编辑器看到它后,通常会启用 Python 的语法高亮、自动缩进、错误检查和代码补全.

语法高亮只影响阅读体验,不改变程序含义.即使编辑器把函数名显示为蓝色、字符串显示为绿色,真正判断代码是否合法的仍是 Python 解释器.反过来说,如果一整段字符串突然显示成代码颜色,经常意味着某个引号没有闭合.

1.2print为什么必须写括号

在 Python 3 中,print 是函数:

print("Python 3")

Python 2 中不带括号的 print 语句.Python 2 已经停止官方支持,新项目不应再模仿那种语法.

2.变量

下面来尝试在hello_world.py中使用一个变量.在这个文件开头添加一行代码,并对第2行代
码进行修改,如下所示:

message = "Hello Python world!"
print(message)

运行这个程序,看看结果如何.你会发现,输出与以前相同:

我们添加了一个名为message的变量.每个变量都存储了一个值——与变量相关联的信息.
在这里,存储的值为文本"Hello Python world!".

添加变量导致Python解释器需要做更多工作.处理第1行代码时,它将文本"Hello Python world!"与变量message关联起来;而处理第2行代码时,它将与变量message关联的值打印到屏幕.

下面来进一步扩展这个程序:修改hello_world.py,使其再打印一条消息.为此,在hello_world.py中添加一个空行,再添加下面两行代码:

message = "Hello Python world!"
print(message)
message = "Hello Python Crash Course world!"
print(message)

现在如果运行这个程序,将看到两行输出:

在程序中可随时修改变量的值,而Python将始终记录变量的最新值.

变量:名称与对象之间的绑定
先看最基础的赋值:

message = "Hello, Python!"
print(message)

= 在这里不是数学中的"恒等于",而是赋值运算:先创建或取得右侧对象,再让左侧名称绑定到它.

因此,更准确的心智模型是:

名称 message  ─────>  字符串对象 "Hello, Python!"

图 1:变量名绑定到对象;重新赋值是让名称改为指向另一个对象.

2.1变量的命名和使用

在Python中使用变量时,需要遵守一些规则和指南.违反这些规则将引发错误,而指南旨在让你编写的代码更容易阅读和理解.请务必牢记下述有关变量的规则.

要创建良好的变量名,需要经过一定的实践,在程序复杂而有趣时尤其如此.随着你编写的程序越来越多,并开始阅读别人编写的代码,将越来越善于创建有意义的变量名.

注意:就目前而言应使用小写的Python变量名.在变量名中使用大写字母虽然不会导致错误,但避免使用大写字母是个不错的主意.

2.2使用变量时避免命名错误

程序员都会犯错,而且大多数程序员每天都会犯错.虽然优秀的程序员也会犯错,但他们也知道如何高效地消除错误.下面来看一种你可能会犯的错误,并学习如何消除它.

我们将有意地编写一些引发错误的代码.请输入下面的代码,包括其中以粗体显示但拼写不正确的单词mesage:

message = "Hello Python Crash Course reader!" 
print(mesage)

程序存在错误时,Python解释器将竭尽所能地帮助你找出问题所在.程序无法成功地运行时,解释器会提供一个traceback.traceback是一条记录,指出了解释器尝试运行代码时,在什么地方
陷入了困境.下面是你不小心错误地拼写了变量名时,Python解释器提供的traceback:

解释器指出,文件第2行存在错误;它列出了这行代码,旨在帮助你快速找出错误;它还指出了它发现的是什么样的错误.在这里,解释器发现了一个名称错误,并指出打印的变量mesage未定义:Python无法识别你提供的变量名.名称错误通常意味着两种情况:要么是使用变量前忘记了给它赋值,要么是输入变量名时拼写不正确.

在这个示例中,第2行的变量名message中遗漏了字母s.Python解释器不会对代码做拼写检查,但要求变量名的拼写一致.例如,如果在代码的另一个地方也将message错误地拼写成了mesage,结果将如何呢?

mesage = "Hello Python Crash Course reader!" 
print(mesage)

在这种情况下,程序将成功地运行:

计算机一丝不苟,但不关心拼写是否正确.因此,创建变量名和编写代码时,你无需考虑英语中的拼写和语法规则.

很多编程错误都很简单,只是在程序的某一行输错了一个字符.为找出这种错误而花费很长时间的大有人在.很多程序员天资聪颖、经验丰富,却为找出这种细微的错误花费数小时.你可能觉得这很好笑,但别忘了,在你的编程生涯中,经常会有同样的遭遇.

2.3专业命名:遵循snake_case

PEP 8 建议普通变量和函数使用小写单词加下划线:

student_name = "Grace Hopper"
completed_lessons = 8

好名称应同时做到"有含义"和"不过度冗长":

n = "Ada"                    # 太含糊
name = "Ada"                 # 小作用域中清楚
student_name = "Ada"         # 存在多类姓名时更清楚
the_name_of_current_student = "Ada"  # 信息重复

还应避免遮蔽内置函数:

list = [1, 2, 3]  # 不推荐:覆盖了内置名称 list
id = "A-001"     # 虽常见,但也会覆盖内置函数 id

更好的名称是 numbersstudent_id.另外应慎用小写 l、大写 O 和大写 I,它们容易与 10 混淆.

2.4从NameError学会读 traceback

message = "欢迎学习 Python"
print(mesage)

这里的 mesage 少了一个 s,执行时会得到 NameError.一段 traceback 通常至少告诉你:

  1. 哪个文件出错
  2. 哪一行出错
  3. Python 执行到什么表达式时失败
  4. 异常类型和说明

排查 NameError 时按下面的顺序检查:

不要只盯着最后一句错误信息,也要查看 traceback 指向的代码行和调用链.

3.字符串

大多数程序都定义并收集某种数据,然后使用它们来做些有意义的事情.鉴于此,对数据进行分类大有裨益.我们将介绍的第一种数据类型是字符串.字符串虽然看似简单,但能够以很多不同的方式使用它们.

字符串就是一系列字符.在Python中,用引号括起的都是字符串,其中的引号可以是单引号,也可以是双引号,如下所示:

"This is a string." 
'This is also a string.'

这种灵活性让你能够在字符串中包含引号和撇号:

'I told my friend, "Python is my favorite language!"' 
"The language 'Python' is named after Monty Python, not the snake." 
"One of Python's strengths is its diverse and supportive community."

3.1使用方法修改字符串的大小写

对于字符串,可执行的最简单的操作之一是修改其中的单词的大小写.请看下面的代码,并尝试判断其作用:

name = "ada lovelace" 
print(name.title())

将这个文件保存为name.py,再运行它.你将看到如下输出:

在这个示例中,小写的字符串"ada lovelace"存储到了变量name中.在print()语句中,方法title()出现在这个变量的后面.方法是Python可对数据执行的操作.在name.title()中,name后面的句点(.)让Python对变量name执行方法title()指定的操作.每个方法后面都跟着一对括号,这是因为方法通常需要额外的信息来完成其工作.这种信息是在括号内提供的.函数title()不需要额外的信息,因此它后面的括号是空的.

title()以首字母大写的方式显示每个单词,即将每个单词的首字母都改为大写.这很有用,因为你经常需要将名字视为信息.例如,你可能希望程序将值Ada、ADA和ada视为同一个名字,并将它们都显示为Ada.

还有其他几个很有用的大小写处理方法.例如,要将字符串改为全部大写或全部小写,可以像下面这样做:

name = "Ada Lovelace" 
print(name.upper()) 
print(name.lower())

这些代码的输出如下:

存储数据时,方法lower()很有用.很多时候,你无法依靠用户来提供正确的大小写,因此需要将字符串先转换为小写,再存储它们.以后需要显示这些信息时,再将其转换为最合适的大小写方式.

3.2合并(拼接)字符串

在很多情况下,都需要合并字符串.例如,你可能想将姓和名存储在不同的变量中,等要显示姓名时再将它们合而为一:

first_name = "ada" 
last_name = "lovelace" 
full_name = first_name + " " + last_name 
print(full_name)

Python使用加号(+)来合并字符串.在这个示例中,我们使用+来合并first_name、空格和last_name,以得到完整的姓名,其结果如下:

这种合并字符串的方法称为拼接.通过拼接,可使用存储在变量中的信息来创建完整的消息.下面来看一个例子:

first_name = "ada" 
last_name = "lovelace" 
full_name = first_name + " " + last_name
print("Hello, " + full_name.title() + "!")

在这里,一个问候用户的句子中使用了全名,并使用了方法title()来将姓名设置为合适的格式.这些代码显示一条格式良好的简单问候语:

你可以使用拼接来创建消息,再把整条消息都存储在一个变量中:

first_name = "ada" 
last_name = "lovelace" 
full_name = first_name + " " + last_name 
message = "Hello, " + full_name.title() + "!" 
print(message)

上述代码也显示消息"Hello, Ada Lovelace!",但将这条消息存储在了一个变量中,这让最后的print语句简单得多.

3.3使用制表符或换行符来添加空白

在编程中,空白泛指任何非打印字符,如空格、制表符和换行符.你可使用空白来组织输出,以使其更易读.要在字符串中添加制表符,可使用字符组合\t,如下述代码处所示:

print("Python")
print("\tPython")
print("Languages:\nPython\nC\nJavaScript")
print("Languages:\n\tPython\n\tC\n\tJavaScript")

还可在同一个字符串中同时包含制表符和换行符.字符串"\n\t"让Python换到下一行,并在下一行开头添加一个制表符.

常见转义序列包括:

写法含义
\n换行
\t水平制表符
\\一个反斜杠
\"双引号
\'单引号
menu = "Languages:\n\tPython\n\tJavaScript\n\tGo"
print(menu)

输出:

Languages:
	Python
	JavaScript
	Go

终端对制表符宽度的显示可能不同.需要严格对齐表格时,不要依赖 \t,应使用格式宽度或专门的表格工具.

Windows 路径等包含大量反斜杠的文本,可以使用原始字符串:

path = r"C:\new_project\tests"

原始字符串会减少反斜杠转义,但它不能以单个反斜杠结尾,这一点在拼接目录路径时容易踩坑.真实项目优先使用 pathlib.Path 管理路径.

3.4删除空白

在程序中,额外的空白可能令人迷惑.对程序员来说,'python’和’python '看起来几乎没什么两样,但对程序来说,它们却是两个不同的字符串.Python能够发现’python '中额外的空白,并认为它是有意义的——除非你告诉它不是这样的.

空白很重要,因为你经常需要比较两个字符串是否相同.例如,一个重要的示例是,在用户登录网站时检查其用户名.但在一些简单得多的情形下,额外的空格也可能令人迷惑.所幸在Python中,删除用户输入的数据中的多余的空白易如反掌.Python能够找出字符串开头和末尾多余的空白.要确保字符串末尾没有空白,可使用方法rstrip().

favorite_language = 'python '
favorite_language
favorite_language.rstrip()
favorite_language

存储在变量favorite_language中的字符串末尾包含多余的空白.你在终端会话中向Python询问这个变量的值时,可看到末尾的空格.对变量favorite_language调用方法rstrip()后,这个多余的空格被删除了.然而,这种删除只是暂时的,接下来再次询问favorite_language的值时,你会发现这个字符串与输入时一样,依然包含多余的空白.要永久删除这个字符串中的空白,必须将删除操作的结果存回到变量中:

favorite_language = 'python '
favorite_language = favorite_language.rstrip()
favorite_language

为删除这个字符串中的空白,你需要将其末尾的空白剔除,再将结果存回到原来的变量中.在编程中,经常需要修改变量的值,再将新值存回到原来的变量中.这就是变量的值可能随程序的运行或用户输入数据而发生变化的原因.你还可以剔除字符串开头的空白,或同时剔除字符串两端的空白.为此,可分别使用方法lstrip()和strip():

favorite_language = ' python '
favorite_language.rstrip()
favorite_language.lstrip()
favorite_language.strip() 

在这个示例中,我们首先创建了一个开头和末尾都有空白的字符串.接下来,我们分别删除末尾、开头和两端的空格.尝试使用这些剥除函数有助于你熟悉字符串操作.在实际程序中,这些剥除函数最常用于在存储用户输入前对其进行清理.

清理首尾空白

language = "  Python  "
print(language.lstrip())  # 删除左侧空白
print(language.rstrip())  # 删除右侧空白
print(language.strip())   # 删除两侧空白

这些方法默认处理的不只是普通空格,也包括制表符、换行符等空白字符.它们不会删除字符串内部的空格:

text = "  data   science  "
print(text.strip())  # data   science

典型输入处理流程是:先保留原始输入用于必要的审计,再得到一份规范化值用于比较或存储.

图 2:strip()title() 和 f-string 逐步产生新字符串;是否应调用 title() 仍取决于业务语义.

对应代码:

raw_name = "  aDA loveLACE  "
clean_name = raw_name.strip()
display_name = clean_name.title()
greeting = f"你好,{display_name}!"
print(greeting)

3.5使用字符串时避免语法错误

语法错误是一种时不时会遇到的错误.程序中包含非法的Python代码时,就会导致语法错误.例如,在用单引号括起的字符串中,如果包含撇号,就将导致错误.这是因为这会导致Python将第一个单引号和撇号之间的内容视为一个字符串,进而将余下的文本视为Python代码,从而引发错误.下面演示了如何正确地使用单引号和双引号.请将该程序保存为apostrophe.py,再运行它:

message = "One of Python's strengths is its diverse community." 
print(message)

撇号位于两个双引号之间,因此Python解释器能够正确地理解这个字符串:

然而,如果你使用单引号,Python将无法正确地确定字符串的结束位置:

message = 'One of Python's strengths is its diverse community.' 
print(message)

而你将看到如下输出:

从上述输出可知,错误发生在第二个单引号后面.这种语法错误表明,在解释器看来,其中的有些内容不是有效的Python代码.错误的来源多种多样,这里指出一些常见的.学习编写Python代码时,你可能会经常遇到语法错误.语法错误也是最不具体的错误类型,因此可能难以找出并修复.受困于非常棘手的错误时,请参阅相关建议.

3.6Python2中的print语句

在Python2中,print语句的语法稍有不同:

python2.7 
print "Hello Python 2.7 world!"

在Python 2中,无需将要打印的内容放在括号内.从技术上说,Python 3中的print是一个函数,因此括号必不可少.有些Python 2 print语句也包含括号,但其行为与Python 3中稍有不同.简单地说,在Python 2代码中,有些print语句包含括号,有些不包含.

4.数字

在编程中,经常使用数字来记录游戏得分、表示可视化数据、存储Web应用信息等.Python根据数字的用法以不同的方式处理它们.鉴于整数使用起来最简单,下面就先来看看Python是如何管理它们的.

4.1整数

在Python中,可对整数执行加(+)减(-)乘(*)除(/)运算.

print(2+3)
print(3-2)
print(2*3)
print(3/2)
print(3 ** 2)
print(3 ** 3)
print(10 ** 6)

在终端会话中,Python直接返回运算结果.Python使用两个乘号表示乘方运算.
Python还支持运算次序,因此你可在同一个表达式中使用多种运算.你还可以使用括号来修改运算次序,让Python按你指定的次序执行运算,如下所示:

print(2 + 3*4)
print((2 + 3) * 4)

在这些示例中,空格不影响Python计算表达式的方式,它们的存在旨在让你阅读代码时,能迅速确定先执行哪些运算.

Python 的常见算术运算符:

运算运算符示例结果
加法+5 + 38
减法-10 - 28
乘法*4 * 28
真除法/16 / 28.0
整除//17 // 28
取余%17 % 21
**2 ** 38

Python 3 中 / 总执行真除法,两个整数相除也会得到浮点结果.// 执行向下取整的整除:

print(7 / 2)    # 3.5
print(7 // 2)   # 3
print(-7 // 2)  # -4

最后一行很重要:// 是向负无穷方向取整,不是简单砍掉小数部分.

divmod() 可一次得到商和余数:

hours, minutes = divmod(135, 60)
print(hours, minutes)  # 2 15

运算优先级:明确胜过炫技

result_1 = 2 + 3 * 4       # 14
result_2 = (2 + 3) * 4     # 20

幂运算优先于乘除,乘除优先于加减,括号可以改变次序.即使你记得所有优先级,只要表达式稍复杂,也应使用括号表达意图:

average = (score_a + score_b + score_c) / 3

括号不是"多余字符",而是写给下一位维护者的说明.

4.2浮点数

Python将带小数点的数字都称为浮点数.大多数编程语言都使用了这个术语,它指出了这样一个事实:小数点可出现在数字的任何位置.每种编程语言都须细心设计,以妥善地处理浮点数,确保不管小数点出现在什么位置,数字的行为都是正常的.

从很大程度上说,使用浮点数时都无需考虑其行为.你只需输入要使用的数字,Python通常都会按你期望的方式处理它们:

print(0.1 + 0.1)
print(0.2 + 0.2)
print(2 * 0.1)
print(2 * 0.2)

但需要注意的是,结果包含的小数位数可能是不确定的:

print(0.2 + 0.1)
print(3 * 0.1)

所有语言都存在这种问题,没有什么可担心的.Python会尽力找到一种方式,以尽可能精确地表示结果,但鉴于计算机内部表示数字的方式,这在有些情况下很难.就现在而言,暂时忽略多余的小数位数即可;在第二部分的项目中,你将学习在需要时处理多余小数位的方式.

浮点数为什么会出现长尾

print(0.1 + 0.2)
# 0.30000000000000004

原因不是 Python 算错,而是大多数十进制小数无法用有限位二进制小数精确表示.计算机保存的是最接近目标值的二进制近似数,连续运算可能让微小误差显现.

图 3:整数、浮点数和字符串是不同类型;浮点数通常是二进制近似值,金额等十进制精确场景可考虑 Decimal.

不要用 == 判断两个计算得到的浮点结果是否"足够接近":

import math
result = 0.1 + 0.2
print(result == 0.3)              # False
print(math.isclose(result, 0.3))  # True

math.isclose() 支持相对容差与绝对容差.接近零时,通常要显式考虑 abs_tol

math.isclose(measured_value, 0.0, abs_tol=1e-9)

容差不是随便填一个极小数字,而应来自问题域允许的误差,例如传感器精度或数值算法的误差上界.

金额与精确十进制:使用 Decimal
金额、会计等需要十进制规则和固定舍入策略的场景,优先考虑 decimal.Decimal

from decimal import Decimal
price = Decimal("19.90")
tax = Decimal("0.10")
total = price + tax
print(total)  # 20.00

应从字符串创建小数:

Decimal("0.1")  # 推荐:表达精确的十进制 0.1
Decimal(0.1)    # 会精确接收那个已经近似的二进制 float

Decimal 不是"把所有浮点问题自动修好"的开关.系统仍需明确精度、舍入模式、货币最小单位以及不同币种的规则.

数字与字符串不能含糊相加

age = 23
message = "年龄:" + age

字符串拼接要求两侧都是字符串,上面的代码会产生 TypeError.传统修复方式是显式转换:

message = "年龄:" + str(age)

现代写法更自然:

message = f"年龄:{age}"

反方向转换时,要处理非法输入:

raw_age = "23"
age = int(raw_age)
raw_age = "二十三"
age = int(raw_age)  # ValueError

健壮的边界处理:

raw_age = input("请输入年龄:").strip()
try:
    age = int(raw_age)
except ValueError:
    print("年龄必须是整数。")
else:
    print(f"明年你将 {age + 1} 岁。")

这里区分了三件事:字符串输入、整数转换、转换失败.不要通过硬编码某几个输入来假装完成校验.

4.3使用函数str()避免类型错误

你经常需要在消息中使用变量的值.例如,假设你要祝人生日快乐,可能会编写类似于下面的代码:

age = 23 
message = "Happy " + age + "rd Birthday!"
print(message)

你可能认为,上述代码会打印一条简单的生日祝福语:Happy 23rd birthday!.但如果你运行这些代码,将发现它们会引发错误:

这是一个类型错误.意味着Python无法识别你使用的信息/在这个示例中.Python发现你使用了一个值为整数(int)的变量.但它不知道该如何解读这个值.Python知道,这个变量表示的可能是数值23,也可能是字符2和3.像上面这样在字符串中使用整数时,需要显式地指出你希望Python将这个整数用作字符串.为此,可调用函数str(),它让Python将非字符串值表示为字符串:

age = 23 
message = "Happy " + str(age) + "rd Birthday!" 
print(message)

这样,Python就知道你要将数值23转换为字符串,进而在生日祝福消息中显示字符2和3.经过上述处理后,将显示你期望的消息,而不会引发错误:

大多数情况下,在Python中使用数字都非常简单.如果结果出乎意料,请检查Python是否按你期望的方式将数字解读为了数值或字符串.

4.4Python2中的整数

在Python 2中,将两个整数相除得到的结果稍有不同:

>>> python2.7
>>> 3 / 2
1

Python返回的结果为1,而不是1.5.在Python 2中,整数除法的结果只包含整数部分,小数部分被删除.请注意,计算整数结果时,采取的方式不是四舍五入,而是将小数部分直接删除.在Python 2中,若要避免这种情况,务必确保至少有一个操作数为浮点数,这样结果也将为浮点数:

>>> 3 / 2
1 
>>> 3.0 / 2
1.5 
>>> 3 / 2.0
1.5 
>>> 3.0 / 2.0
1.5

从Python 3转而用Python 2或从Python 2转而用Python 3时,这种除法行为常常会令人迷惑.使用或编写同时使用浮点数和整数的代码时,一定要注意这种异常行为.

5.注释

在大多数编程语言中,注释都是一项很有用的功能.前面编写的程序中都只包含Python代码,但随着程序越来越大、越来越复杂,就应在其中添加说明,对你解决问题的方法进行大致的阐述.注释让你能够使用自然语言在程序中添加说明.

5.1如何编写注释

在Python中,注释用井号(#)标识.井号后面的内容都会被Python解释器忽略,如下所示:

# 向大家问好
print("Hello Python people!")

Python解释器将忽略第1行,只执行第2行.

注释:解释为什么,而不是翻译代码
单行注释以 # 开始:

# 采用向上取整,确保每位参与者都有一个座位
required_tables = (guest_count + seats_per_table - 1) // seats_per_table

解释器会忽略 # 后面的内容,但维护代码的人不会.

不好的注释

# 把数量加 1
count += 1

它只重复代码,没有提供新信息.更糟糕的是,代码修改后注释可能没有同步,最终变成误导.

有价值的注释

# 第三方接口把空字符串当成“删除昵称”,因此缺失值必须传 None。
nickname = cleaned_name or None

有价值的注释通常说明:

如果一个复杂代码块需要大量注释才能看懂,先考虑拆分函数和改进命名.清楚的代码是第一层文档,注释是第二层.

注释不等于文档字符串
三引号字符串放在模块、类或函数开头时,可以成为文档字符串(docstring):

def normalize_name(raw_name: str) -> str:
    """清理姓名两端空白,并返回适合展示的形式。"""
    return raw_name.strip().title()

文档字符串描述公共接口的用途、参数、返回值和异常,可被 help() 和文档工具读取;普通注释主要帮助理解实现.不要用未赋值的三引号字符串假装多行注释.

5.2该编写什么样的注释

编写注释的主要目的是阐述代码要做什么,以及是如何做的.在开发项目期间,你对各个部分如何协同工作了如指掌,但过段时间后,有些细节你可能不记得了.当然,你总是可以通过研究代码来确定各个部分的工作原理,但通过编写注释,以清晰的自然语言对解决方案进行概述,可节省很多时间.

要成为专业程序员或与其他程序员合作,就必须编写有意义的注释.当前,大多数软件都是合作编写的,编写者可能是同一家公司的多名员工,也可能是众多致力于同一个开源项目的人员.训练有素的程序员都希望代码中包含注释,因此你最好从现在开始就在程序中添加描述性注释.作为新手,最值得养成的习惯之一是,在代码中编写清晰、简洁的注释.

如果不确定是否要编写注释,就问问自己,找到合理的解决方案前,是否考虑了多个解决方案.如果答案是肯定的,就编写注释对你的解决方案进行说明吧.相比回过头去再添加注释,删除多余的注释要容易得多.

6.Python 之禅:把风格原则落到代码上

编程语言Perl曾在互联网领域长期占据着统治地位,早期的大多数交互式网站使用的都是Perl脚本.彼时,"解决问题的办法有多个"被Perl社区奉为座右铭.这种理念一度深受大家的喜爱,因为这种语言固有的灵活性使得大多数问题都有很多不同的解决之道.在开发项目期间,这种灵活性是可以接受的,但大家最终认识到,过于强调灵活性会导致大型项目难以维护:要通过研究代码搞清楚当时解决复杂问题的人是怎么想的,既困难又麻烦,还会耗费大量的时间.

经验丰富的程序员倡导尽可能避繁就简.Python社区的理念都包含在Tim Peters撰写的"Python之禅"中.要获悉这些有关编写优秀Python代码的指导原则,只需在解释器中执行命令import this.这里不打算赘述整个"Python之禅",而只与大家分享其中的几条原则,让你明白为何它们对Python新手来说至关重要.

>>> import this
The Zen of Python, by Tim Peters 
Beautiful is better than ugly.

Python程序员笃信代码可以编写得漂亮而优雅.编程是要解决问题的,设计良好、高效而漂亮的解决方案都会让程序员心生敬意.随着你对Python的认识越来越深入,并使用它来编写越来越多的代码,有一天也许会有人站在你后面惊呼:"哇,代码编写得真是漂亮!

如果有两个解决方案,一个简单,一个复杂,但都行之有效,就选择简单的解决方案吧.这样,你编写的代码将更容易维护,你或他人以后改进这些代码时也会更容易.

现实是复杂的,有时候可能没有简单的解决方案.在这种情况下,就选择最简单可行的解决方案吧.

即便是复杂的代码,也要让它易于理解.开发的项目涉及复杂代码时,一定要为这些代码编写有益的注释.

如果让两名Python程序员去解决同一个问题,他们提供的解决方案应大致相同.这并不是说编程没有创意空间,而是恰恰相反!然而,大部分编程工作都是使用常见解决方案来解决简单的小问题,但这些小问题都包含在更庞大、更有创意空间的项目中.在你的程序中,各种具体细节对其他Python程序员来说都应易于理解.

你可以将余生都用来学习Python和编程的纷繁难懂之处,但这样你什么项目都完不成.不要企图编写完美无缺的代码;先编写行之有效的代码,再决定是对其做进一步改进,还是转而去编写新代码.

6.1简单优于复杂

# 绕远的写法
is_adult = True if age >= 18 else False
# 直接表达条件结果
is_adult = age >= 18

6.2可读性很重要

# 含义不清
x = p * (1 + r) ** y
# 同一公式,名称说明业务含义
future_value = principal * (1 + annual_rate) ** years

6.3现在做胜过永远不做

这不是鼓励草率,而是反对为了想象中的完美迟迟不验证.更可靠的节奏是:

  1. 写出最小可工作的版本
  2. 用真实输入和测试验证
  3. 根据证据改进命名、结构和错误处理
  4. 保持每次修改小而可回退

"先工作、再改进"与"写完就不管"完全不同.

7.综合示例:从原始输入生成学习进度消息

下面的示例把变量、字符串清理、类型转换、数字格式化、注释和异常处理串起来:

from decimal import Decimal, InvalidOperation
def build_progress_message(
    raw_name: str,
    raw_completed: str,
    raw_total: str,
    raw_cost: str,
) -> str:
    """验证输入并返回一条学习进度摘要。"""
    display_name = raw_name.strip().title()
    if not display_name:
        raise ValueError("姓名不能为空")
    try:
        completed = int(raw_completed)
        total = int(raw_total)
    except ValueError as exc:
        raise ValueError("课程数量必须是整数") from exc
    if total <= 0:
        raise ValueError("课程总数必须大于 0")
    if not 0 <= completed <= total:
        raise ValueError("已完成数量必须在 0 和总数之间")
    try:
        cost = Decimal(raw_cost)
    except InvalidOperation as exc:
        raise ValueError("费用必须是十进制数字") from exc
    # 比例适合使用 float;费用保留为 Decimal,避免混合两种数值语义。
    progress = completed / total
    return (
        f"{display_name}:已完成 {completed}/{total} 节课程 "
        f"({progress:.1%}),累计费用 ¥{cost:.2f}。"
    )
message = build_progress_message(
    raw_name="  ada lovelace  ",
    raw_completed="7",
    raw_total="8",
    raw_cost="199.90",
)
print(message)

输出:

这个例子刻意做了边界划分:

8.常见错误速查

现象常见异常最可能的原因优先检查
名称找不到NameError未赋值、拼写错误、作用域不对出错行、名称大小写、赋值路径
源代码无法解析SyntaxError引号、括号、冒号或缩进结构有误当前行和上一行、成对符号
字符串与整数相加TypeError操作不支持这些类型组合type()、转换边界、是否该用 f-string
int("abc") 失败ValueError类型目标合理,但值的格式非法输入内容、空白、小数点、符号
小数比较意外失败通常无异常二进制浮点近似误差math.isclose()、问题域容差
strip(chars) 删除过多通常无异常把字符集合误当固定子串removeprefix() / removesuffix()

调试时建议先做最小实验:打印 repr(value)type(value),前者能暴露不可见空白,后者能确认真实类型.

value = "Python\n"
print(repr(value))  # 'Python\n'
print(type(value))  # <class 'str'>

不要在真实认证信息、令牌或个人数据上随意打印调试日志.

9. 分层练习

练习A:变量与重新绑定

创建变量 status,先存储"准备学习",打印后把它重新赋值为"正在学习",再打印.用一句话解释两次赋值发生了什么.

# 练习 A:变量与重新绑定
print("【练习 A:变量与重新绑定】")
status = "准备学习"
print(status)
status = "正在学习"
print(status)
print("解释:第二次赋值让变量 status 重新指向了一个新的字符串对象,"
      "原来的字符串本身并没有被修改。")

练习B:姓名规范化

给定:

raw_name = "\n\tgrace HOPPER  "

分别输出:

  1. 使用 repr() 显示的原始值
  2. 去掉两端空白的值
  3. 全小写值
  4. 适合演示的标题格式值
  5. 使用 f-string 生成的问候语

思考:真实姓名系统中,为什么不能保证 title() 总是正确?

# 练习 B:姓名规范化
print("\n【练习 B:姓名规范化】")
raw_name = "\n\tgrace HOPPER  "
# 1. 使用 repr() 显示原始值
print("1. 原始值:", repr(raw_name))
# 2. 去掉两端空白
clean_name = raw_name.strip()
print("2. 去除两端空白:", clean_name)
# 3. 全小写
lower_name = clean_name.lower()
print("3. 全小写:", lower_name)
# 4. 适合演示的标题格式
title_name = clean_name.title()
print("4. 标题格式:", title_name)
# 5. 使用 f-string 生成问候语
greeting = f"你好,{title_name}!欢迎开始今天的学习。"
print("5. 问候语:", greeting)
print(
    "思考:title() 只是按照简单规则把单词首字母大写,"
    "无法正确处理所有真实姓名,例如 McDonald、van Gogh、de la Cruz 等,"
    "因此真实系统通常应保存并尊重用户输入的姓名格式。"
)

练习C:时间换算

3671 秒换算为小时、分钟和秒.要求至少使用一次 divmod(),输出格式为:

1 小时 1 分钟 11 秒
# 练习 C:时间换算
# ============================================================
print("\n【练习 C:时间换算】")
total_seconds = 3671
hours, remaining_seconds = divmod(total_seconds, 3600)
minutes, seconds = divmod(remaining_seconds, 60)
print(f"{hours} 小时 {minutes} 分钟 {seconds} 秒")

练习D:浮点比较

验证 0.1 + 0.1 + 0.1 == 0.3 的结果,再使用 math.isclose() 做合理比较.解释为什么对运算前的每个 0.1 调用 round() 不能从根本上消除二进制表示误差.

# 练习 D:浮点比较
# ============================================================
print("\n【练习 D:浮点比较】")
result = 0.1 + 0.1 + 0.1
print("实际结果:", repr(result))
print("直接与 0.3 比较:", result == 0.3)
print("使用 math.isclose():", math.isclose(result, 0.3))
print(
    "解释:0.1 无法用二进制浮点数精确表示,因此连续运算后会产生微小误差。"
)
print(
    "round() 只能把结果舍入到指定的小数位,不能改变 float 使用二进制近似表示"
    "小数这一根本性质,因此不适合作为所有浮点误差问题的通用解决方案。"
)

练习E:金额计算

使用 Decimal 计算单价 19.90、数量 3、折扣 0.85 后的金额,并用两位小数显示.所有带小数的 Decimal 都必须从字符串创建.

# 练习 E:金额计算
print("\n【练习 E:金额计算】")
# 所有带小数的 Decimal 都从字符串创建
unit_price = Decimal("19.90")
quantity = Decimal("3")
discount = Decimal("0.85")
amount = unit_price * quantity * discount
# 金额保留两位小数,采用常见的“四舍五入”规则
final_amount = amount.quantize(
    Decimal("0.01"),
    rounding=ROUND_HALF_UP
)
print("计算前金额:", amount)
print(f"最终金额:{final_amount}")

练习F:主动制造并修复错误

分别写出会触发 NameErrorSyntaxErrorTypeErrorValueError 的最小代码.逐个记录:

主动观察错误,是建立调试能力最快的方法之一.

import traceback
# 练习 F:主动制造并分析异常
print("【练习 F:主动制造并修复错误】")
def trigger_name_error():
    """触发 NameError:使用不存在的变量。"""
    eval("undefined_variable")
def trigger_syntax_error():
    """触发 SyntaxError:if 语句缺少冒号。"""
    exec("if True print('hello')")
def trigger_type_error():
    """触发 TypeError:字符串不能直接和整数相加。"""
    eval("'年龄:' + 18")
def trigger_value_error():
    """触发 ValueError:无法把普通文字转换成整数。"""
    int("十二")
cases = [
    {
        "name": "NameError",
        "function": trigger_name_error,
        "cause": "使用了没有定义的变量 undefined_variable。",
        "fix": "先定义变量,例如:undefined_variable = 10",
    },
    {
        "name": "SyntaxError",
        "function": trigger_syntax_error,
        "cause": "if 条件后缺少冒号。",
        "fix": "改为:if True: print('hello')",
    },
    {
        "name": "TypeError",
        "function": trigger_type_error,
        "cause": "字符串和整数不能直接使用 + 运算。",
        "fix": "改为:'年龄:' + str(18),或使用 f'年龄:{18}'",
    },
    {
        "name": "ValueError",
        "function": trigger_value_error,
        "cause": "'十二' 不是 int() 能识别的数字字符串。",
        "fix": "改为合法数字字符串,例如:int('12')",
    },
]
for case in cases:
    print(f"\n===== {case['name']} =====")
    try:
        case["function"]()
    except Exception as error:
        print("异常类型:", type(error).__name__)
        print("异常信息:", error)
        # SyntaxError 自带具体的行号和列号
        if isinstance(error, SyntaxError):
            print(f"traceback 位置:第 {error.lineno} 行,第 {error.offset} 列")
        # 其他异常从 traceback 中提取位置
        else:
            traceback_list = traceback.extract_tb(error.__traceback__)
            last_traceback = traceback_list[-1]
            print(
                "traceback 位置:"
                f"函数 {last_traceback.name},"
                f"第 {last_traceback.lineno} 行"
            )
        print("根本原因:", case["cause"])
        print("最小修复:", case["fix"])

10.总结

表面上,本文在讲变量、字符串和数字;更深一层,它在训练三种能力:

  1. 建立数据模型:名称绑定什么对象,对象是什么类型,操作会返回什么;
  2. 控制数据边界:输入先清理和验证,内部使用明确类型,输出再格式化;
  3. 用证据调试:从 traceback、repr()type() 观察真实状态,而不是猜.

只要牢牢记住下面几句话,后续学习列表、条件、循环和函数会顺畅很多:

到此这篇关于Python编程从入门到实践指南(变量和简单数据类型:从“会写“到真正理解)的文章就介绍到这了,更多相关python编程入门到实践内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文