- 既能面向对象,又能面向过程
- 没有 main() 函数
- 函数定义与类定义语句也会被执行
- 使用缩进而不是花括号 {} 区分代码段的层级
- 语句末尾有没有分号都行(误)
- 字符串可以用单引号,也可以用双引号,还可以用三重引号
- 向下取整除法运算符 // 和幂运算符 **
- pass语句
- 没有switch-case语句
- for循环不需要计数器
- for、while、try语句支持else子句
- 任何非零整数都为真,零为假
- 灵活的import语句
- 运算符 is 和 is not
- 对象中没有私有属性
- 文档字符串
- 默认支持UTF-8
对于新手而言,官方的 Python 教程 是一个非常不错的学习资源,内容安排由浅入深。但对于已经有其他语言编程经验的人来说,学习Python更快的方法可能是先 “找不同”。Python某些特性确实非常 “特别”,甚至会让人惊呼 “还可以这样?” 这里简单罗列一些这样的特性。
既能面向对象,又能面向过程
Python本质上是一门面向对象的语言,即使 int 等内置的基本数据类型都是 class,但使用Python既可以进行面向对象的编程,也可以进行面向过程的编程,甚至能够像Shell脚本一样使用,整个python脚本里可以既没有类定义也没有函数定义。例如:
# helloworld.py # shell-script style s1 = "Hello %s." s2 = "world" print(s1 % s2)
# helloworld.py
# 面向过程编程
def foo(s):
return "Hello %s." % s
if __name__ == "__main__":
s1 = foo("world")
print(s1)
# helloworld.py
# 面向对象编程
class Hello:
def say_hello_to(self, s):
print("Hello %s." % s)
if __name__ == "__main__":
hello = Hello()
hello.say_hello_to("world")
没有 main() 函数
你可能已经注意到,上面三种形式的 helloworld.py 都没有main()函数。但它们都可以通过 python helloworld.py 的方式执行,并输出相同的内容。后面两种形式的代码中,都有 if __name__ == "__main__": 的代码块,当 .py 文件以脚本形式被执行时,内置变量 __name__ 会被赋值为 "__main__",从而此段代码会被执行。但当 .py 文件被以模块形式导入时,__name__ 会被赋值为模块名称,相应的,这段代码就不会执行。
我们加上一行查看 __name__ 的语句:
# helloworld.py
class Hello:
def say_hello_to(self, s):
print("Hello %s." % s)
# 查看内部变量 __name__ 的值
print("__name__ is '" + __name__ + "'n")
if __name__ == "__main__":
hello = Hello()
hello.say_hello_to("world")
以脚本方式执行:
C:UsersuserPyProjects>python helloworld.py __name__ is '__main__' Hello world.
以模块方式导入:
C:UsersuserPyProjects>python Python 3.10.4 (tags/v3.10.4:9d38120, Mar 23 2022, 23:13:41) [MSC v.1929 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import helloworld __name__ is 'helloworld' >>>
函数定义与类定义语句也会被执行
上面的最后一个例子中,输出 __name__ 的语句既没有放在 __main__ 代码段中,也没有放在类的方法中,而是放到了类定义之后、'__main__' 代码段之前的 “顶级” 代码的位置。那么,把这行代码放在最开始或者最后面,它还会执行吗?
事实上,当 .py 文件被加载时,文件中所有的语句都会被顺序执行,包括函数定义 def 语句和类定义 class 语句。理解了这一点,即使你看到某个文件里出现了两段 '__main__' 代码段,也不会觉得奇怪了。
使用缩进而不是花括号 {} 区分代码段的层级
前面的例子已经显而易见。关于缩进,在《Python语言参考》的词法分析一章中有专门的说明。不过一般而言,关于缩进只要记住下面三点:
- 不要使用 tab
- 理论上只要缩进量保持一致即可,无论是1个空格还是8个空格
- IDE会自动将 tab 转换为4个空格
关于编码风格的重要提示:
https://docs.python.org/zh-cn/3/tutorial/controlflow.html#intermezzo-coding-style
Python 项目大多都遵循 PEP 8 的风格指南;它推行的编码风格易于阅读、赏心悦目。Python 开发者均应抽时间悉心研读;以下是该提案中的核心要点:
- 缩进,用 4 个空格,不要用制表符。
4 个空格是小缩进(更深嵌套)和大缩进(更易阅读)之间的折中方案。制表符会引起混乱,最好别用。- 换行,一行不超过 79 个字符。
这样换行的小屏阅读体验更好,还便于在大屏显示器上并排阅读多个代码文件。- 用空行分隔函数和类,及函数内较大的代码块。
- 最好把注释放到单独一行。
- 使用文档字符串。
- 运算符前后、逗号后要用空格,但不要直接在括号内使用: a = f(1, 2) + g(3, 4)。
- 类和函数的命名要一致;按惯例,命名类用 UpperCamelCase,命名函数与方法用 lowercase_with_underscores。命名方法中第一个参数总是用 self (类和方法详见 初探类)。
- 编写用于国际多语环境的代码时,不要用生僻的编码。Python 默认的 UTF-8 或纯 ASCII 可以胜任各种情况。
- 同理,就算多语阅读、维护代码的可能再小,也不要在标识符中使用非 ASCII 字符。
语句末尾有没有分号都行(误)
前面的例子中,我们在语句末尾都没有分号,这也是正确的写法。但如果我们在语句末尾加上分号,程序也不会出错。比如像下面这样:
# helloworld.py
class Hello:
def say_hello_to(self, s):
print("Hello %s." % s);
if __name__ == "__main__":
hello = Hello();
hello.say_hello_to("world");
事实上,使用分号可以将多条简单语句组合成一条复合语句,例如:
if __name__ == "__main__":
# 增加一行调试语句
import ipdb; ipdb.set_trace()
hello = Hello()
hello.say_hello_to("world")
但是,作为一个合格的python程序员,请尽量 不要使用分号。
字符串可以用单引号,也可以用双引号,还可以用三重引号
Python的字符串对应的数据类型为 str,既可以使用单引号',也可以使用双引号 "。区别在于:用单引号时,字符串里的双引号无需转义;而使用双引号时,字符串里的单引号无需转义。
>>> s1 = 'Using '' >>> s2 = "Using '" >>> s3 = "Using '" >>> s1 == s2 == s3 True
这也意味着:'A' 是一个长度为1的字符串,而不是一个字符。
另外,Python中还可以使用三重引号 ''' 或 """ 来表示字符串,它的优势在于可以自动包括换行符,而无需转义。
>>> s1 = 'one linenanother line' >>> s2 = """one line ... another line""" >>> s1 == s2 True >>> print(s1) one line another line >>> print(s2) one line another line >>>
向下取整除法运算符 // 和幂运算符 **
Python提供一种特殊的除法运算符 // ,含义为向下取整除法。
>>> 15/4 3.75 >>> 15//4 3 >>> -11/4 -2.75 >>> -11//4 -3 >>>
Python的幂运算符也与其他很多语言不同,不是 ^ 而是 ** 。
>>> 2**3 8 >>> 5**2 25 >>> 2**-1 0.5
Python中的 ^ 是异或运算符,例如:
>>> bin(0b1001 ^ 0b1111)
'0b110'
>>> hex(0x1 ^ 0xF)
'0xe'
>>> set1 = {1, 2, 3}
>>> set2 = {3, 4, 1}
>>> set1 ^ set2
{2, 4}
pass语句
pass 语句不执行任何操作。语法上需要一个语句,但程序不实际执行任何动作时,可以使用该语句。
class MyClass:
# TODO: ....
pass
def foo(bar):
# TODO: ....
pass
for item in my_list:
# TODO: ....
pass
没有switch-case语句
直到3.10,Python才出现了与switch-case语句类似的match语句。在3.10之前,可以用 if-elif-else 语句实现类似的逻辑,但有时候也可以使用一种更python的方式——字典——像下面这样:
animal_dict = {
1: "Dog",
2: "Cat",
3: "Rabbit",
}
def getAnimal(animal_id):
return animal_dict.get(animal_id, "Invalid Animal")
或者这样:
def Animal1():
return "Dog"
def Animal2():
return "Cat"
def Animal3():
return "Rabbit"
def DefaultAnimal():
return "Invalid Animal"
animal_dict = {
'A': Animal1,
'B': Animal2,
'C': Animal3
}
def getAnimal(animal_id):
foo = animal_dict.get(animal_id, DefaultAnimal)
return foo()
for循环不需要计数器
Python里的许多数据类型都可以用for循环遍历,例如 list(列表)、tuple (元组)、 dict (字典)、 range (范围)、 str (字符串)、 bytes (字节串)、 set (集合)等。
# 列表
my_list = ['A', 'B', 'C', 'D']
for item in my_list:
print(item)
# 元组
my_tuple = ('1', '2', 3, 4)
for item in my_tuple:
print(item)
# 集合
my_set = {'dog', 'cat', 'rabbit'}
for item in my_set:
print(item)
class MyClass:
pass
# 字典
my_dict = {
'A': 10,
2: MyClass(),
'bar': (1, 2)
}
for k,v in my_dict.items():
print(repr(k), repr(v))
# 范围
my_range = range(10,15)
for item in my_range:
print(item)
# 字符串
my_string = "This is a string."
for item in my_string:
print(item)
# 字节串
my_bytes = b"This is a string."
for item in my_bytes:
print(chr(item))
for、while、try语句支持else子句
for 循环的 else 子句(如果有)在循环项耗尽时执行并终止循环(break 语句终止循环且不执行 else 子句体,continue 语句将在执行时将跳过子句体中的剩余部分并转往下一项继续执行,或者在没有下一项时转往 else 子句执行):
>>> for n in range(2, 10): ... for x in range(2, n): ... if n % x == 0: ... print(n, 'equals', x, '*', n//x) ... break ... else: ... # loop fell through without finding a factor ... print(n, 'is a prime number') ... 2 is a prime number 3 is a prime number 4 equals 2 * 2 5 is a prime number 6 equals 2 * 3 7 is a prime number 8 equals 2 * 4 9 equals 3 * 3
while 循环的 else 子句(如果有)在while条件为 False 时执行并终止循环( break 语句在执行时将终止循环且不执行 else 子句体。 continue 语句在执行时将跳过子句体中的剩余部分并返回检验while条件表达式):
>>> x = 0
>>> while x < 100:
... if x % 20 == 0:
... print(x)
... x += 1
... else:
... print("That's all.")
...
0
20
40
60
80
That's all.
try ... except 语句具有可选的 else 子句,该子句如果存在,它必须放在所有 except 子句 之后。 它适用于 try 子句 没有引发异常但又必须要执行 的代码。(参考:错误和异常)
以下例子能够看出 else 和 finally 的区别:
>>> def divide(x, y):
... try:
... result = x / y
... except ZeroDivisionError:
... print("division by zero!")
... else:
... print("result is", result)
... finally:
... print("executing finally clause")
...
>>> divide(3, 1)
result is 3.0
executing finally clause
>>> divide(3, 0)
division by zero!
executing finally clause
>>> divide(3, 'a')
executing finally clause
Traceback (most recent call last):
File "", line 1, in
File "", line 3, in divide
TypeError: unsupported operand type(s) for /: 'int' and 'str'
>>>
任何非零整数都为真,零为假
这一点与C语言一样。进行条件判断时,条件也可以是字符串或列表的值,事实上,任何序列都可以;长度非零就为真,空序列则为假。举例而言,对于字符串类型的变量 x,下面两种写法是等价的:
x = str(some_var)
if x:
# do something
x = str(some_var)
if x != None and x != '':
# do something
需要注意,bool 类型继承自 int ,True == 1 和 False == 0 均成立,但 False == '' 、False == [] 以及 True == 2 等等均不成立。
>>> True == 1 True >>> False == 0 True >>> False == '' False >>> False == [] False >>> False == None False
灵活的import语句
Python中的 import 语句类似其他语言的 #include ,用于导入其他模块中的名称,但import语句的写法更加多样。
可以直接import:
>>> import math >>> math.ceil(10/3) 4
使用 import item.subitem.subsubitem 句法时,除最后一项外,每个 item 都必须是包;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。
>>> import django.utils.encoding.iri_to_uri Traceback (most recent call last): File "", line 1, in ModuleNotFoundError: No module named 'django.utils.encoding.iri_to_uri'; 'django.utils.encoding' is not a package >>> import django.utils.encoding >>> django.utils.encoding.iri_to_uri('中文') '%E4%B8%AD%E6%96%87'
可以导入模块中的一个或几个名称(通过这种方式导入 item 时,item 可以是包的子模块(或子包),也可以是包中定义的函数、类或变量等其他名称。import 语句首先测试包中是否定义了 item;如果未在包中定义,则假定 item 是模块,并尝试加载。如果找不到 item,则触发 ImportError 异常):
>>> from math import pi,degrees >>> pi 3.141592653589793 >>> degrees(pi) 180.0
可以给导入的名称改名:
>>> from decimal import Decimal as D
>>> num = D(1.245)
>>> num
Decimal('1.24500000000000010658141036401502788066864013671875')
虽然也支持使用 * 导入全部名称,但是官方不提倡在生产代码中使用这种做法。
>>> from decimal import *
>>> getcontext().prec = 6
>>> Decimal(1) / Decimal(7)
Decimal('0.142857')
>>> getcontext().prec = 28
>>> Decimal(1) / Decimal(7)
Decimal('0.1428571428571428571428571429')
运算符 is 和 is not
运算符 is 和 is not 用于检测对象的标识号:当且仅当 x 和 y 是同一对象时 x is y 为真。 一个对象的标识号可使用 id() 函数来确定。 x is not y 会产生相反的逻辑值。
>>> class A: ... pass ... >>> a = A() >>> b = A() >>> a is not b True >>> c = a >>> a is c True >>> id(a) 1984982945168 >>> id(b) 1984983270208 >>> id(c) 1984982945168
>>> x = None >>>> x == None True >>> x is None True >>> id(x) 140717859773656 >>> id(None) 140717859773656
对象中没有私有属性
Python中将所有形如 a.b 的名称中的 b 称为属性(Attribute),在类对象中,属性引用 obj.name 中的 name 就是类对象的属性。类对象的属性可以是一个变量,也可以是一个方法/函数。但是在Python中,所有类对象的属性都不是 “私有” 的。
https://docs.python.org/zh-cn/3/tutorial/classes.html#private-variables
那种仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有一个下划线的名称 (例如 _spam) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。
由于存在对于类私有成员的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为 名称改写。 任何形式为 __spam 的标识符(至少带有两个前缀下划线,至多一个后缀下划线)的文本将被替换为 _classname__spam,其中 classname 为去除了前缀下划线的当前类名称。 这种改写不考虑标识符的句法位置,只要它出现在类定义内部就会进行。
下面举例说明这种情况:
# demo.py
# 父类
class A:
x = 1
_y = 2
__z = 3
def foo1(self, v):
print("foo1(%d) is called." % v)
def _foo2(self, v):
print("_foo2(%d) is called." % v)
def __foo3(self, v):
print("__foo3(%d) is called." % v)
# 子类
class B(A):
pass
if __name__ == "__main__":
# 创建子类对象
b = B()
b.foo1(b.x)
b._foo2(b._y)
b._A__foo3(b._A__z)
try:
b.__foo3()
except AttributeError as e:
print(repr(e))
C:UsersuserPyProjects>python demo.py
foo1(1) is called.
_foo2(2) is called.
__foo3(3) is called.
AttributeError("'B' object has no attribute '__foo3'")
文档字符串
三重引号字符串通常用作文档字符串(docstring)。以上面演示Python没有 “私有” 属性的代码为例,我们加上docstring。
# demo.py
# 父类
class A:
"""
Demo Class for "private" attributes.
This is the Base Class.
"""
x = 1
_y = 2
__z = 3
def foo1(self, v):
"""
The public method. print a message with v.
Parameters:
v (int): Any int number.
Return:
None
"""
print("foo1(%d) is called." % v)
def _foo2(self, v):
"""
The method that should not call.
"""
print("_foo2(%d) is called." % v)
def __foo3(self, v):
"""
The "private" method.
"""
print("__foo3(%d) is called." % v)
# 子类
class B(A):
"""
Demo Class for "private" attributes.
This is the Sub Class.
"""
pass
if __name__ == "__main__":
# 创建子类对象
b = B()
b.foo1(b.x)
b._foo2(b._y)
b._A__foo3(b._A__z)
try:
b.__foo3()
except AttributeError as e:
print(repr(e))
然后我们在Python解释器中执行 help() 命令,看看是什么效果。
>>> import demo >>> help(demo.A) Help on class A in module demo: class A(builtins.object) | Demo Class for "private" attributes. | | This is the Base Class. | | Methods defined here: | | foo1(self, v) | The public method. print a message with v. | | Parameters: | v (int): Any int number. | | Return: | None | | ---------------------------------------------------------------------- | Data descriptors defined here: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes defined here: | | x = 1
可以看到,_foo2() 和 __foo3() 的 docstring 都没有显示,不仅如此, _y 和 __z 这两个成员变量也没有显示。
查看 __doc__ 变量:
>>> print(demo.A.__doc__)
Demo Class for "private" attributes.
This is the Base Class.
>>> print(demo.A.foo1.__doc__)
The public method. print a message with v.
Parameters:
v (int): Any int number.
Return:
None
>>> print(demo.A._foo2.__doc__)
The method that should not call.
>>> print(demo.A.__foo3.__doc__)
Traceback (most recent call last):
File "", line 1, in
AttributeError: type object 'A' has no attribute '__foo3'
>>> print(demo.A._A__foo3.__doc__)
The "private" method.
>>>
再看看 class B 的情况:
>>> help(demo.B) Help on class B in module demo: class B(A) | Demo Class for "private" attributes. | | This is the Sub Class. | | Method resolution order: | B | A | builtins.object | | Methods inherited from A: | | foo1(self, v) | The public method. print a message with v. | | Parameters: | v (int): Any int number. | | Return: | None | | ---------------------------------------------------------------------- | Data descriptors inherited from A: | | __dict__ | dictionary for instance variables (if defined) | | __weakref__ | list of weak references to the object (if defined) | | ---------------------------------------------------------------------- | Data and other attributes inherited from A: | | x = 1
默认支持UTF-8
与 Python 2 不同,Python 3 默认支持UTF-8编码,如果 .py 文件以 UTF-8 编码保存,则无需额外声明。如果是以其他编码方式保存,则需要在文件最开始声明编码,例如:
# -*- coding: gb18030 -*-
print("这是一个GB18030编码的文件")
相反,在 Python 2 中,如果使用 UTF-8 编码,则一定要在文件最开始声明:
# -*- coding: utf-8 -*- # Here is your code.
如何查看和修改文件编码?
VSCode右下角,不仅可以查看文件编码,还可以对编码进行转换。



