Python教程-基础语法

一、 标识符
标识符是用来给变量、常量、函数、类等对象命名的名称。
首先需要明确一点,Python语言在任何情况下都严格区分大小写!
在Python中,标识符的命名有以下规定:
- 第一个字符必须是字母(大小写均可)或下划线
_
。 - 其余字符可以是字母(大小写均可)、数字或下划线
_
。
例如,a, Ak, _set_id, green
都是有效的标识符命名,但是 $abc, &_a, ~bashrc, 123abc
不是有效的标识符命名。
那么,有些同学可能会问,中文可以作为标识符吗?很好!我非常欣赏你这种思考和提问的方式,因为它有助于你深入探索核心原理。答案是可以的!下面是一个例子:
>>> 我 = 1
>>> 我
1
>>> 什么 = "apple"
>>> print(什么)
apple
Python 3对Unicode的全面支持使得对中文的兼容性越来越好了。实际上,你可以尝试在IDLE中使用中文作为变量名,你会发现它是有效的。
然而,尽管Python支持中文标识符,但没有人会这样做,我也不建议大家这么做。请遵循第一个字符必须是字母(大小写均可)或下划线 _
的原则。
此外,以下划线开头的标识符通常具有特殊意义。以单下划线开头的变量(例如 _foo
)表示类成员不应该被外部访问,需要通过类提供的接口进行访问,不能使用 from xxx import *
导入;以双下划线开头的变量(例如 __foo
)表示类的私有成员;以双下划线开头和结尾的变量(例如 __foo__
)是Python中专用的特殊方法标识,如 __init__()
表示类的构造函数。关于这些内容,我们将在后面进行专门的讨论,暂时只需要知道它们具有特殊性即可。
标识符的其他部分由字母、数字和下划线组成。也就是说,除了首字符不可以是数字外,其余部分可以包含数字。特殊字符是不被允许的,所以 a123c_, bbc, city_of_china
等都是有效的标识符,而 a&b, king-of-the-world, love@qq.com
都是无效的。
另外要注意的是,由于小写字母 l
和数字 1
、大小写字母 o
和数字 0
在外观上相似,尽量不要让它们相邻出现,以确保清晰地表达语义,避免产生错误的读取情况。
同样地,让我们继续深入思考。在语法上,插入中文在英文中是允许的,但是绝对不要这样做!
>>> a这都能行b舅服你 = 100
>>> a这都能行b舅服你
100
- 标识符对大小写敏感。Python严格区分大小写,因此标识符
abc
和ABC
是不同的。 - 变量名通常使用全小写,常量名使用全大写。虽然你可以使用
PI
来表示一个变量,但通常我们会将其视为表示圆周率的常量。 - 函数和方法名使用小写加下划线。虽然这不是语法要求,但是在命名函数或方法时,通常采用类似
get
,set
,count_apple
,total_number
等命名方式。 - 类名使用大写驼峰命名法。这也不是语法要求,而是代码规范。大写驼峰命名法指的是每个单词的首字母大写,并组合在一起,形如驼峰一样高低起伏排列。例如
ThreadMixIn
,BrokenBarrierError
,_DummyThread
等。 - 模块和包名使用小写。请尽量使用小写命名模块和包,并避免与标准库或知名第三方库重名。
最后需要提醒大家的是:
不要将变量命名为关键字和内置函数的名称!
接下来,我们将介绍Python中的关键字,而有关内置函数的知识将在后面的章节中介绍。
二、Python保留字
Python保留字,也称为关键字,是Python语言官方确定的专用标识符,用于语法功能,不能将它们用作任何自定义标识符的名称。关键字仅包含小写字母。Python的标准库提供了一个名为 keyword
的模块,可以输出当前版本的所有关键字:
>>> import keyword
>>> keyword.kwlist
['False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']
也可以参照下表:
那么如果,真的犯了这个错,会怎么样呢?举个例子:
>>> if = 1
SyntaxError: invalid syntax
>>> print(and)
SyntaxError: invalid syntax
>>> def = "hi"
SyntaxError: invalid syntax
看到没有?直接提示语法错误。
除了不能使用关键字作为标识符,前面我们也强调过了,也不能使用内置函数同名的标识符。Python有很多内置函数,以sum为例,这是一个求和的函数,我们看一下错误的命名导致的后果:
>>> sum([1,2,3,4])
10
>>> sum = "错误标识符名"
>>> sum([1,2,3,4])
Traceback (most recent call last):
File "<pyshell#19>", line 1, in <module>
sum([1,2,3,4])
TypeError: 'str' object is not callable
先不考虑 sum([1,2,3,4])
的含义,它的作用是将 1、2、3、4 加在一起得到 10。然后错误地给一个变量取了名为 sum
的标识符,之后又调用 sum([1,2,3,4])
,程序抛出异常,错误原因是 str
不是可调用的类型。这归根结底是因为 sum
这个名称重名了。
三、注释
在我们编写的程序中,不仅有代码,还有很多注释。注释具有解释性和辅助性质,在代码执行过程中相当于不存在,对代码本身不产生任何影响。然而,在代码维护、解释、测试等方面,注释发挥着不可或缺的重要作用。每个程序员都应该尽量编写高质量的注释。关于注释的具体主题,有许多高水平的文章和论述可供参考,请自行搜索和学习。在这里,我们只讨论 Python 中的注释方法。
- 单行注释
在 Python 中,以 #
符号开头的内容就是单行注释,从 #
开始一直到行末,都被视为注释内容。
#!/usr/bin/python3
# 下面这个方法的作用是…..
# 第一个注释
# 我是单行注释
# 这是一个空的函数,它什么都不干。本条注释也是句废话。
def main():
pass # pass表示占位,什么都不做。那我为什么要注释它呢???
- 多行注释
Python没有真正意义上的多行注释(块注释)语法。你只能在每行的开头打上#号,然后假装自己是个多行注释,囧。(有表情包没?)
# 第一行注释
# 第二行注释
# 第三行注释
def func():
print("这是一个悲伤的故事!")
- 注释文档
在某些特定的位置,用三引号包括起来的部分,也被当做注释。但是,这种注释有专门的作用,用于为__doc__提供文档内容,这些内容可以通过现成的工具,自动收集起来,形成帮助文档。比如,函数和类的说明文档:
def func(a, b):
"""
这个是函数的说明文档。
:param a: 加数
:param b: 加数
:return: 和
"""
return a + b
class Foo:
"""
这个类初始化了一个age变量
"""
def __init__(self, age):
self.age = age
需要强调的是这类注释必须紧跟在定义体下面,不能在任意位置。
四、 代码头两行
很多时候,我们在一些py脚本文件的开头都能看到类似的以#开头的这样两行代码,它们不是注释,是一些设定。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
第一行:用于指定运行该脚本的Python解释器,Linux专用,windows不需要。env方式下,系统会自动使用环境变量里指向的Python。还有一种方式,#!/usr/bin/python3.6
,这会强制要求使用系统中的python3.6解释器执行文件,这种方式不好,一旦你本地的Python3.6版本删除了,会出现找不到解释器的错误。无论两种方式的哪一种,都指的是在linux下使用./test.py
的方式执行脚本时的设置,在使用类似python test.py
或者python3 test.py
的执行方式时,这一行不起作用。
例如,我写了个脚本如下:
#!/usr/bin/python2
print "hello" # 注意这里没有圆括号,这是python2版本的打印方式
仔细看下面的执行过程, 你会发现无论你在代码第一行怎么设置,都会根据命令调用的时的python版本进行执行:
[feixue@feixue-VirtualBox: ~/python]$ cat test.py
#!/usr/bin/python2
print "hello"
[feixue@feixue-VirtualBox: ~/python]$ python2
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
[feixue@feixue-VirtualBox: ~/python]$ python3
Python 3.6.1 (default, Aug 15 2017, 11:19:20)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> exit()
[feixue@feixue-VirtualBox: ~/python]$ python2 test.py
hello
[feixue@feixue-VirtualBox: ~/python]$ python3 test.py
File "test.py", line 3
print "hello"
^
SyntaxError: Missing parentheses in call to 'print'
那这行有什么用呢?是这么用的。
[feixue@feixue-VirtualBox: ~/python]$ chmod +x test.py
[feixue@feixue-VirtualBox: ~/python]$ ll test.py
-rwxrwxr-x 1 feixue feixue 34 9月 5 22:57 test.py*
[feixue@feixue-VirtualBox: ~/python]$ ./test.py
hello
第二行:代码的编码方式。这不是程序要处理的数据的编码方式,而是程序自身的字符编码。在 Python 3 中,完全支持 Unicode,并默认使用 UTF-8 编码,因此我们不再需要担心中文和乱码的问题,所以这一行其实可以省略。但在 Python 2 中,字符编码是一个令人头疼的问题,通常需要指定这一行。如果需要使用其他编码类型,可以像这样指定:# -*- coding: cp-1252 -*-
。但如果没有强制要求,建议坚持使用 UTF-8 编码。
这两行应该出现在文件的顶部左侧,不要有空格和空行。utf8
和 utf-8
都可以使用。
PS:这里的 -*-
是什么意思呢?其实没有什么实际意义,只是为了装饰和美观而已。
除了这两行,有时候我们还会在文件中包含作者、联系地址、版权说明、版本说明等信息,这完全根据个人喜好。
五、语句与缩进
语句:在代码中,能够完整表达某个意义、操作或逻辑的最小代码单元被称为语句。语句通常不超过一行,超过一行的语句称为多行语句。
a = apple
from . import modles
print("haha")
lis.append(item)
Python的标准语句不需要使用分号或逗号来表示语句结束,简简单单的换个行就表示本语句已经结束,下一句开始。
代码块:为完成某一特定功能而联系在一起的一组语句构成一个代码块。有判断、循环、函数、类等各种代码块。代码块的首行通常以关键字开始,以冒号( : )结束。比如:
# 这是一个判断流程代码块
if expression :
pass
elif expression :
pass
else :
pass
又比如:
# 这是一个类代码块
class Foo:
def __init__(self, name, age):
self.name = name
self.age = age
def get_name(self):
return self.name
# 这是一个函数代码块
def func(a, b):
summer = a+b
return summer*2
Python最具特色的语法就是使用缩进来表示代码块,不需要使用大括号({})。
缩进的空格数是可变的,但是同一个代码块的语句必须包含相同的缩进空格数。
如果缩进数的空格数不一致,会抛出缩进异常(请记住IndentationError这个异常名!新手会经常和它做朋友):
File "test.py", line 6
print ("False") # 缩进不一致,会导致运行错误
^
IndentationError: unindent does not match any outer indentation level
PEP 8(Python 官方的代码规范)建议使用四个空格作为缩进。在文本编辑器中,应该设置将 Tab 键自动转换为四个空格,以确保不混用 Tab 和空格。在 PyCharm 中,默认将 Tab 键转换为四个空格缩进。在 Linux 环境中,例如使用 Vim 编辑器时,请务必使用空格缩进,而不是使用 Tab 键。
那么什么是正确的缩进方式呢?
- 所有普通语句应从行首开始编写,不需要缩进。
- 所有语句块应在冒号(:)之后的下一行开始,并进行缩进。
- 语句块应在结束的冒号(:)之后退回到原来的缩进级别,表示当前块的结束。
- 语句块可以嵌套,因此缩进也可以嵌套。
例如,如果我们编写一个 if/else 的条件控制语句块:
首先,第一行不需要缩进:
if i > 10:
然后是第2句,这时已经进入if内部了,需要缩进:
if i > 10:
i = i + 10 # 左边有4个空格
接下来是第3句,因为还在if内部,所以保持和第二句一样的缩进
if i > 10:
i = i + 10 # 左边有4个空格
print(i) # 左边有4个空格
第4句,我们if分支走完了,要走else分支,那么if语句块算结束了,缩进要回退。
if i > 10:
i = i + 10 # 左边有4个空格
print(i) # 左边有4个空格
else:
第5句,进入else语句块了,要缩进4个空格:
if i > 10:
i = i + 10 # 左边有4个空格
print(i) # 左边有4个空格
else:
i = i - 10 # 左边有4个空格
第6句,else分支也走完了,整个if/else流程完毕,下面是别的代码了,那么else分支的缩进也要退回。
if i > 10:
i = i + 10 # 左边有4个空格
print(i) # 左边有4个空格
else:
i = i - 10 # 左边有4个空格
print("apple") # 缩进回退了,这里已经和if、else没什么关系了。
再看一个缩进嵌套的例子,以两个for循环嵌套为例:
for i in range(10):
for k in range(10): # 同样也是for循环,由于嵌套在另一个for内部,需要缩进
if k != i: # 双重循环内的if,需要2次缩进,也就是顶左开始8个空格
print(k) # 又嵌套了一个if语句块,再次缩进4个空格,共计12个
else: # if结束,回退4个,开始else分支
print(i+k) # 又增加缩进
print(i) # 这次不但else结束了,连内部的for也结束了,回退8个空格
确实,在刚开始接触缩进语法时,可能会感到困惑,不知道何时缩进、何时回退,以及应该缩进多少个空格。但通过多写、多看,很快就能掌握,并且不再犯错。Python的缩进语法是与其他语言不同的独特设计,引发了广泛的争议。喜欢的人非常喜欢,认为它简洁明了,节省了不少功夫,并且强制代码整洁。不喜欢的人则认为缩进来缩去,容易头晕,容易犯语法错误,并且不能使用格式化工具。
关于在同一行写多条语句:
前面我们已经说过,通常情况下,Python 中的一行代码对应一条语句,而且一条语句通常不会超过一行。实际上,在语法上,Python 并没有完全禁止在一行中使用多条语句,你可以使用分号来在一行中写多条语句,例如:
import sys; x = ‘多条语句'; sys.stdout.write(x + '\n')
上面这一行其实包含3条语句,用分号分隔了,但是强烈建议不要这么做,这样会导致代码阅读困难、维护耗时,容易出错,老老实实地用三行来表示,不但更优雅,还增加了代码行数,可以跟老板说今天多写了2行,^_^。
多行语句: 前面是多条语句在一行,但如果一条语句实在太长,也是可以占用多行的,可以使用反斜杠(\)
来实现多行语句:
string = "i love this country,"\
+"because it is very beautiful!"\
+ "how do you think about it?" \
+ "Do you like it too?"
在 [], {}, 或 () 中的多行语句,可以不需要使用反斜杠(\)
,直接回车,接着写。例如:
result = subprocess.Popen("ipconfig /all", stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True, check=True)
PEP8建议:每一行的字符不超过79个。该换行时咱就换行!
六、 pass语句
pass语句是一种占位语句,它并不执行任何操作,只是为了保证语法的正确性而存在。以下是可以使用pass语句的场景:
- 当你不知道后续代码该如何编写时,可以使用pass语句作为占位符,以后再补充代码细节。
- 当你不需要编写代码细节,但语法上需要有一个语句存在时,可以使用pass语句。
- 在某些必须有语句的语法结构中,但实际上并不需要执行任何操作时,可以使用pass语句,如空的类、空的函数等。
- 在其他你认为需要的场景中,如果你希望在代码中显式地表达某种意图或占位符,你可以使用pass语句。
举个栗子:
# 我只想说明这个func函数需要三个参数,具体执行什么并不重要
# 但是函数体内如果什么都没有,会出现语法错误
# 这个时候,pass就是最好的选择。
def func(a,b,c):
pass
七、 空白字符与空白行
空白行、空白字符和代码缩进在Python中确实不是语法的一部分,它们会被解释器忽略。连续的空白行或空白字符与单独的空白行或空白字符没有区别。在书写代码时,可以不插入空白行或空白字符,Python解释器也不会报错。然而,空白行和空白字符在代码中的作用是分隔不同功能或含义的代码块,有助于代码的可读性、维护性和重构性。
根据PEP8的建议,以下是关于空行留白的一些规范:
- 函数之间或类的方法之间应使用空行进行分隔,以表示不同代码块的开始。
- 类和函数的入口之间也应使用一行空行进行分隔,以突出函数入口的开始。
- 在变量赋值时,等号的左右应留有一个空格。
- 在逗号后面应跟一个空格。
看下面的代码,作为标准的代码规范,在规定的地方留有规定的空白行和空白字符。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
class Foo:
pass
def func(a, b):
pass
if __name__ == '__main__':
pass
八、 字符串的表示形式
后面的字符串数据类型章节会有更深入的讲解,现在只是作为前期的知识铺垫。
字符串是一段连续的字符,可以是有意义的单词、词组、语句,也可以是无意义的字符组合。字符串与其内部的字符组成无关,而取决于是否被引号引起来。
例如,abc
可能是一个变量,但是"abc"
肯定是一个字符串!请注意,在代码的世界中,没有中文标点,所有的符号类型都是英文半角,千万不要搞错了!
在Python中,字符串可以使用单引号、双引号或者三引号括起来。Python中的单引号和双引号的作用完全相同,都可以用来表示字符串。需要注意的是,在其他一些编程语言中,双引号引起来的是字符串,而单引号引起来的是字符,例如C语言或JSON。因此,在处理JSON格式转换时,经常出现因为使用了单引号而导致转换失败的问题。
使用三引号(''''''
或""""""
)可以指定一个多行字符串。
转义符 \
用来进行特殊转义,例如 \r\n
表示换行,\\
表示一个普通的反斜杠字符。转义符可以将引号转义为普通的引号,没有其他特殊的作用。
原生字符串可以通过在字符串前加r
或R
来表示,例如 r"this is a line with \n"
,这样字符串中的斜杠将不会被转义,会直接显示出来。因此,例子中的\n
会被显示为 \n
,而不是换行符。
Unicode字符串可以通过在字符串前加u
或U
来表示,例如 u"this is an unicode string"
。
字节类型字符串可以通过在字符串前加b
来表示,例如 b"this is a bytes data"
。但是类似 s=b"哈哈"
这样的写法是不支持的。
字符串是不可变类型,即不能直接修改字符串的某个字符。
字符串可以自动拼接,例如 "I" "love" "you"
会被自动转换为 "I love you"
。
这些内容可能有些抽象,如果不好理解,不要紧,后面的内容会进一步解释。
九、如何阅读错误调用栈信息?
现在,我们已经开始编写一些简单的Python代码和语句了,出现错误是很常见的。Python提供了详细的错误调用栈信息,帮助我们定位错误。下面以一段示例代码为例(每一行都带有行号信息,包括空行):
def foo(s): #1
return 10 / int(s) #2
#3
def bar(s): #4
return foo(s) * 2 #5
#6
def main(): #7
bar('0') #8
#9
main() #10
运行代码后,会弹出如下的错误:
Traceback (most recent call last):
File "F:/Python/pycharm/201705/1.py", line 10, in <module>
main()
File "F:/Python/pycharm/201705/1.py", line 8, in main
bar('0')
File "F:/Python/pycharm/201705/1.py", line 5, in bar
return foo(s) * 2
File "F:/Python/pycharm/201705/1.py", line 2, in foo
return 10 / int(s)
ZeroDivisionError: division by zero
这些信息就是错误提示。我们需要从上往下读,英文看起来很复杂,其实很易懂。Traceback...表示追踪错误开始,说在某个位置的1.py文件的第10行中的main这个函数出了问题。那么问题出在哪呢?是1.py文件中的第8行的bar函数调用出了问题,那么出什么问题呢?还没找到根子,又说在第5行,第2行调用出了问题,然后最终发现是return 10 / int(s)
这行语句的问题。问题是什么,发生了除零错误!ZeroDivisionError是一个除零异常。到这里,我们恍然大悟,我让10除0了,所以出现错误了,那么就知道怎么改了。
其实查找错误,排除错误就是这么简单,只是需要你耐心的一行行地查看错误调用栈,再根据最后的错误类型,就可以判断错误的发生原因并修改错误,没有想象中的那么复杂。更不要直接将错误甩给别人或者老师!那种张嘴就是“我这里出错了,是什么原因?”的提问方式,是最没有水平的。