基本知识
第一个Python程序:
print("hello world")
如何退出Python的解释器提示符:
在Linux或OS X的shell程序中,可以通过按下[ctrl+d]或输入exit()。
在windows命令提示符中,可以按下[ctrl+z]组合键,然后敲击[center]键来退出。
注意:
Python 是区分大小写的,如 print 和 Print 是不同的——注意前者的 p 是小写的,而后者的 P 是大写的。此外,你需要确保每一行的第一个字符前面都没有任何空格或制表格。
注释
注释 是任何存在于 # 号右侧的文字,其主要用作写给程序读者看的笔记。
举个例子:
print('hello world') #注意到 print 是一个函数
或者:
# 注意到 print 是一个函数
print('hello world')
字面常量
可以直接表示其字面意思的量,例如5、1.23这样的数字或字符串文本。
数字
数字主要分为两种类型——整数(Integers)与浮点数(Floats)。
针对有经验的程序员的提示
没有单独的 long 类型。 int 类型可以指任何大小的整数。
字符串
一串字符串(String)是 字符(Characters) 的 序列(Sequence)。基本上,字符串就是一串词汇。
单引号和双引号的作用差不多,都是指定字符串:
'我是一个字符串'和“我是一个字符串”的效果一样。
三引号,主要用于多行字符串,在三引号中可以任意使用单引号和双引号。例如:
'''这是一段多行字符串。这是它的第一行。
This is the second line.
"What's your name?," I asked.
He said "Bond, James Bond."
'''
字符串是不可变的。
format可以使用内容填充字符串中的占位符(格式)。
print输出字符串时,会在末尾自动加上换行符“\n”,可以使用end参数替换掉换行符。例如:
print('a',end='')
print('b',end='')
输出结果如下:
ab
转义序列
对于有特殊含义的字符出现在字符串中,需要使用转义序列""来进行转义,例如'What's your name'在python中会报错,你需要对其中的但引号进行转义,'What's your name'。
最重要的转义序列:\n代表换行,\t代表制表符
在一个字符串中,一个放置在末尾的反斜杠表示字符串将在下一行继续,但不会添加新的一行。来看看例子:
"This is the first sentence. \
This is the second sentence."
相当于:
"This is the first sentence. This is the second sentence."
原始字符串
在字符串前面使用r或R可以表示这个字符串没有经过任何处理,也就是原样输出。
r"Newlines are indicated by \n"
在使用正则表达式时,应该全程使用原样字符串。举例说明的话,反向引用可以通过 '\1' 或 r'\1' 来实现。
变量
可以存储任何信息,并且也能够操作它们。
标识符命名
就是给某些东西一个名称。标识符命名规则和C语言、Java的一样,标识符中只能有字母、数字、下划线,并且开头只能是字母或数字。
数据类型
变量可以将各种形式的值保存为不同的数据类型(Data Type)。基本的类型是我们已经讨论过的数字与字符串。
对象
Python将程序中的任何内容统称为对象(Object)。
针对面向对象编程语言用户的提示:
Python 是强(Strongly)面向对象的,因为所有的一切都是对象, 包括数字、字符串与
函数。
针对静态编程语言程序员的提示:变量只需被赋予某一值。不需要声明或定义数据类型。
物理行与逻辑行
在编辑器中看到的一行代码就是物理行,Python所读到的一个语句就是一个逻辑行。
Python隐藏的期望是在一行中只写一个语句,也就是尽量不要使用分号。
如果一行代码实在是太长了,可以使用反斜杠""将其拆分成多个物理行,这被称作显式行连接。
例如:
i=\
5
#等价于
i=5
在某些情况下,会存在一个隐含的假设,允许你不使用反斜杠。这一情况即逻辑行以括号开始,它可以是方括号或花括号,但不能是右括号。
缩进
每个语句 前面的空白区就是缩进,空白区可以确定逻辑行的缩进级别,而缩进级别又可以用于确定语句的分组。
有一件事你需要记住:错误的缩进可能会导致错误。
Python官方的建议是使用四个空格来缩进。
运算符与表达式
表达式可以拆分成运算符(Operators)与操作数(Operands)。
各个操作符的详细信息参考《简明Python教程》的p45-46的运算符的速览。
基本的操作符都和Java的一样,需要注意的是逻辑非用“not”、逻辑与用“and”、逻辑或用“or”。
数值运算与赋值的快捷方式
其实就是Python的数值运算支持乘赋“*=”等快捷操作。例如:
a=2
a=a*3
#等价于
a=2
a *= 3
求值顺序
Python中对于运算符的优先级也是有规定的,《简明Python教程》中有说明,在P47页,也可以参考Python的手册。最好是使用圆括号对运算符和表达式进行分组。
控制流
在Python中有三种控制流语句——if、for和while。
if 语句
示例:
number = 23
guess = int(input('Enter an integer : '))
if guess == number : # 我们借助这个冒号向Python指定接下来会有一块语句在后头
# 新块从这里开始
print('Congratulations, you guessed it.')
print('(but you do not win any prizes!)')
elif guess < number :
# 另一代码块
print('No, it is a little higher than that ')
# 你可以在此做任何你希望在改代码块内进行的事情
else :
print('No, it is a little lower than that')
# 你必须通过猜测一个大于(>)设置数的数字来到达这里。
print('Done')
# 这最后一句话将在 if 语句执行完毕后执行
输出:
$ python if.py
Enter an integer : 50
No, it is a little lower than that
Done
$ python if.py
Enter an integer : 22
No, it is a little higher than that
Done
$ python if.py
Enter an integer : 23
Congratulations, you guessed it.
(but you do not win any prizes!)
Done
针对 C/C++ 程序员的提示
Python 中不存在 switch 语句。你可以通过使用 if..elif..else 语句来实现同样的事情(在某些情况下,使用一部字典能够更快速地完成)。
while 语句
while语句是循环语句的一种。while语句同样可以拥有else子句作为可选项。
number = 23
running = True
while running :
guess = int(input('Enter an integer : '))
if guess == number :
print('Congratulations, you guessed it. ')
# 这将导致while循环终止
running = False
elif guess < number :
print('No, it is a little higher than that. ')
else :
print('No, it is a little lower than that. ')
else : # 用冒号告诉Python下面有语句块
print('The while loop is over. ')
# 在这里你可以做你想做的任何事情
print('Done')
输出:
$ python while.py
Enter an integer : 50
No, it is a little lower than that.
Enter an integer : 22
No, it is a little higher than that.
Enter an integer : 23
Congratulations, you guessed it.
The while loop is over.
Done
如果 while 循环中存在一个 else 代码块,它将总是被执行,除非你通过 break 语句来中断这一循环。
针对 C/C++ 程序员的提示
你可以在 while 循环中使用 else 从句。
for 循环
for...in 语句是另一种循环语句,其特点是会在一系列对象上进行迭代(Iterates),意即它会遍历序列中的每一个项目。
案例:
for i in range(1, 5) :
print(i)
else:
print('The for loop is over')
输出:
$ python for.py
1
2
3
4
The for loop is over
要记住, else 部分是可选的。当循环中包含他时,它总会在 for 循环结束后开始执行,除非程序遇到了 break 语句。
break 语句
break语句用以中断循环语句,即使循环条件没有变成False,也可以被终止。
需要注意的是,如果你中断了一个For或while循环,任何相应循环中的else块都将不会被执行。
示例:
while True :
s = input('Enter something : ')
if s == 'quit' : #zhg: 注意字符串判断相等的方式
break
print('Length of the string is', len(s))
print('Done')
输出:
$ python break.py
Enter something : Programming is fun
Length of the string is 18
Enter something : When the work is done
Length of the string is 21
Enter something : if you wanna make your work also fun:
Length of the string is 37
Enter something : use Python!
Length of the string is 11
Enter something : quit
Done
记住, break 语句同样可以适用于 for 循环。
Continue语句
continue 语句用以告诉 Python 跳过当前循环块中的剩余语句,并继续该循环的下一次迭代。
示例:
while True :
s = input('Enter something : ')
if s == 'quit' :
break
if len(s) < 3 :
print('Too small')
continue
print('Input is of sufficient length')
# 自此处起继续进行其它任何处理
输出:
$ python continue.py
Enter something : a
Too small
Enter something : 12
Too small
Enter something : abc
Input is of sufficient length
Enter something : quit
要注意 continue 语句同样能用于 for 循环。
函数
函数(Functions)是指可重复使用的程序片段。
函数可以通过关键字 def 来定义。这一关键字后跟一个函数的标识符名称,再跟一对圆括号,其中可以包括一些变量的名称,再以冒号结尾,结束这一行。随后而来的语句块是函数的一部分。下面的案例将会展示出这其实非常简单:
def say_hello() :
# 该块属于这一函数
print('hello world')
# 函数结束
say_hello() # 调用函数
say_hello() # 再次调用函数
输出:
$ python function1.py
hello world
hello world
函数参数
函数的参数就是定义在那个圆括号中变量,通过它们可以向函数传递信息。
在定义函数时给定的名称称作“形参”(Parameters),在调用函数时你所提供给函数的值称作“实参”(Arguments)。
案例:
def print_max(a, b) :
if a > b :
print(a, 'is maximum')
elif a == b :
print(a, 'is equal to', b)
else :
print(b, 'is maximum')
#直接传递字面值
print_max(3,4)
x=5
y=7
# 以参数的形式传递变量
print_max(x, y)
输出:
$ python function_param.py
4 is maximum
7 is maximum
局部变量
在函数定义中声明的变量就是局部变量,它们不会以任何形式与函数外的同名变量发生联系。
所有变量的作用域是它们被定义的块,从定义它们的名字的定义点开始。
示例:
x = 50
def func(x) :
print('x is', x)
x = 2
print('changed local x to', x)
func(x)
print('x is still', x)
输出:
$ python function_local.py
x is 50
Changed local x to 2
x is still 50
zhg: python的函数也是遵从值传递规则,即传递给函数参数的是值,而不是地址。
global 语句
使用global语句可以使用并更改函数之外的变量的值。
案例:
x = 50
def func() :
global x
print('x is', x)
x = 2
print('changed global x to', x)
func()
print('value of x is', x)
输出:
$ python function_global.py
x is 50
Changed global x to 2
Value of x is 2
global 语句用以声明 x 是一个全局变量——因此,当我们在函数中为 x 进行赋值时,这一改动将影响到我们在主代码块中使用的 x 的值。
你可以在同一句 global 语句中指定不止一个的全局变量,例如 global x, y, z 。
默认参数值
你可以通过在函数定义时附加一个赋值运算符( = )来为参数指定默认参数值。
默认参数值应该是常量,即是不可变的。
示例:
def say(message, times=1):
print(message * times)
say('Hello')
say('World', 5)
输出:
$ python function_default.py
Hello
WorldWorldWorldWorldWorld
注意
只有那些位于参数列表末尾的参数才能被赋予默认参数值,意即在函数的参数列表中拥
有默认参数值的参数不能位于没有默认参数值的参数之前。
这是因为值是按参数所处的位置依次分配的。举例来说, def func(a, b=5) 是有效的,
但 def func(a=5, b) 是无效的。
关键字参数
可以通过命名的防止给制定参数赋值。
示例:
def func(a, b=5, c=10):
print('a is', a, 'and b is', b, 'and c is', c)
func(3, 7)
func(25, c=24)
func(c=50, a=100)
输出:
$ python function_keyword.py
a is 3 and b is 7 and c is 10
a is 25 and b is 5 and c is 24
a is 100 and b is 5 and c is 50
可变参数
可以通过星号来实现任意数量的变量。
示例:
def total(a=5, *numbers, **phonebook):
print('a', a)
#遍历元组中的所有项目
for single_item in numbers :
print('single_item', single_item)
#遍历字典中的所有项目
for first_part, second_part in phonebook.items() :
print(first_part, second_part)
print(total(10, 1, 2, 3, Jack=1123, John=2231, Inge=1560))
输出:
$ python function_varargs.py
a 10
single_item 1
single_item 2
single_item 3
Inge 1560
John 2231
Jack 1123
None
当我们声明一个诸如 *param 的星号参数时,从此处开始直到结束的所有位置参数(Positional Arguments)都将被收集并汇集成一个称为“param”的元组(Tuple)。
类似地,当我们声明一个诸如 **param 的双星号参数时,从此处开始直至结束的所有关键字参数都将被收集并汇集成一个名为 param 的字典(Dictionary)。
return 语句
return语句用于在函数中返回,我们可以选择从函数中返回一个值。
示例:
def maximum(x, y):
if x > y :
return x
elif x == y :
return 'The numbers are equal'
else :
return y
print(maximum(2, 3))
输出:
$ python function_return.py
3
如果return语句没有搭配任何一个值,则代表返回None。None在Python中是一个特殊的类型,代表着虚无。
每一个函数都在其末尾隐含了一句 return None ,除非你写了你自己的 return 语句。你可以运行 print(some_function()) ,其中 some_function 函数不使用 return 语句,就像这样:
def some_function():
pass
Python 中的 pass 语句用于指示一个没有内容的语句块。
提示:有一个名为 max 的内置函数已经实现了“找到最大数”这一功能,所以尽可能地使
用这一内置函数。
DocStrings
文档字符串就是对函数的一个说明,其实和Java的类似,就是位置和规范不同。
示例:
def print_max(x, y) :
'''打印两个数值中的最大数
这两个数都应该是整数'''
# 如果可能,将其转换成整数类型
x = int(x)
y = int(y)
if x > y :
print(x, 'is maximum')
else :
print(y, 'is maximum')
print_max(3, 5)
print(print_max.__doc__)
输出:
$ python function_docstring.py
5 is maximum
打印两个数值中的最大数。
这两个数都应该是整数
函数的第一行逻辑行中的字符串是该函数的 文档字符串(DocString)。
该文档字符串所约定的是一串多行字符串,其中第一行以某一大写字母开始,以句号结束。第二行为空行,后跟的第三行开始是任何详细的解释说明。在此强烈建议你在你所有重要功能的所有文档字符串中都遵循这一约定。
我们可以通过使用函数的 doc (注意其中的双下划綫)属性(属于函数的名称)来获取函数 print_max 的文档字符串属性。Python将所有东西都视为一个对象,其中包括函数。
help()函数所做的便是获取函数的 doc 属性并以一种整洁的方式将其呈现给你。
Python发行版中附带的pydoc 命令与 help() 使用文档字符串的方式类似。
模块
模块就是在不同程序间复用代码的手段。
编写模块有很多种方法,其中最简单的一种便是创建一个包含函数与变量、以 .py 为后缀的文件。
另一种方法是使用撰写 Python 解释器本身的本地语言来编写模块。
一个模块可以被其它程序导入并运用其功能。
案例:
import sys
print('The command line arguments are:')
for i in sys.argv:
print(i)
print('\n\nThe PYTHONPATH is', sys.path, '\n')
输出:
$ python module_using_sys.py we are arguments
The command line arguments are:
module_using_sys.py
we
are
arguments
The PYTHONPATH is ['/tmp/py',
# many entries here, not shown here
'/Library/Python/2.7/site-packages',
'/usr/local/lib/python2.7/site-packages']
需要注意的是,运行的脚本名称在 sys.argv 的列表中总会位列第一。
按字节码编译的 .pyc 文件
可以将Python文件按字节编译成中间文件,这样有利于模块的加载速度。
from...import语句
from...import语句可以避免每次使用argv时都要输入"sys.",即使用from sys import argv 语句来实现这一点。
示例:
from math import sqrt
print("Square root of 16 is", sqrt(16))
模块的__name__
每个模块都有一个名称,可以通过模块的名称来确定它是独立运行的还是被导入进来的,这可以通过使用模块的__name__ 属性来实现。。
案例:
if __name__ == '__main__' :
print('This program is being run by itself')
else:
print('I am being imported from another module')
输出:
$ python module_using_name.py
This program is being run by itself
$ python
>>> import module_using_name
I am being imported from another module
>>>
每一个 Python 模块都定义了它的 name 属性。如果它与 main 属性相同则代表这一模块是由用户独立运行的。
编写你自己的模块
每一个 Python 程序同时也是一个模块。你只需要保证它以 .py 为扩展名即可。
案例:
def say_hi():
print('Hi, this is mymodule speaking.')
__version__ = '0.1'
注意:自己定义的模块要放在要导入该模块的程序的所在目录,或者在sys.path所列出的某个目录下。
另一个模块:
import mymodule
mymodule.say_hi()
print('Version', mymodule.__version__)
输出:
$ python mymodule_demo.py
Hi, this is mymodule speaking.
Version 0.1
下面是一个使用 from...import 语法的范本:
from mymodule import say_hi, __version__
say_hi()
print('Version', __version__)
你还可以使用:
from mymodule import *
这将导入诸如 say_hi 等所有公共名称,但不会导入 __version__ 名称,因为后者以双下划线开头。
警告:尽量不要使用from...import形式,容易引起变量名冲突。
dir 函数
内置的 dir() 函数能够返回由对象所定义的名称列表。
该函数接受参数。 如果参数是模块名称,函数将返回这一指定模块的名称列表。 如果没有提供参数,函数将返回当前模块的名称列表。
案例:
$ python
>>> import sys
# 给出 sys 模块中的属性名称
>>> dir(sys)
['__displayhook__', '__doc__',
'argv', 'builtin_module_names',
'version', 'version_info']
# 此处只展示部分条目
# 给出当前模块的属性名称
>>> dir()
['__builtins__', '__doc__',
'__name__', '__package__','sys']
# 创建一个新的变量 'a'
>>> a = 5
>>> dir()
['__builtins__', '__doc__', '__name__', '__package__', 'a']
# 删除或移除一个名称
>>> del a
>>> dir()
['__builtins__', '__doc__', '__name__','__package__']
关于 del 的一个小小提示——这一语句用于删除一个变量或名称,当这一语句运行后,在本例中即 del a ,你便不再能访问变量 a ——它将如同从未存在过一般。
包
包是指一个包含模块与一个特殊的 __init__.py 文件的文件夹,后者向 Python 表明这一文件夹是特别的,因为其包含了 Python 模块。
让我们这样设想:你想创建一个名为“world”的包,其中还包含着 “asia”、“africa”等其它子包,同时这些子包都包含了诸如“india”、 “madagascar”等模块。
下面是你会构建出的文件夹的结构:
- /
- world/
- __init__.py
- asia/
- __init__.py
- india/
- __init__.py
- foo.py
- africa/
- __init__.py
- madagascar/
- __init__.py
- bar.py
包是一种能够方便地分层组织模块的方式。你将在 标准库 中看到许多有关于此的实例。
总结
如同函数是程序中的可重用部分那般,模块是一种可重用的程序。包是用以组织模块的另一种层次结构。Python 所附带的标准库就是这样一组有关包与模块的例子。
数据结构
数据结构是用来存储一系列相关数据的集合。
Python 中有四种内置的数据结构——列表(List)、元组(Tuple)、字典(Dictionary)和集合(Set)。
列表
列表 是一种用于保存一系列有序项目的集合。项目的列表应该用方括号括起来,这样 Python 才能理解到你正在指定一张列表。
列表对应的类型是list
示例:
# This is my shopping list
shoplist = ['apple', 'mango', 'carrot', 'banana']
print('I have', len(shoplist), 'item to purchase.')
print('These items are:', end=' ')
for item in shoplist :
print(item, end=' ')
print('\nI also have to buy rice.')
shoplist.append('rice')
print('My shopping list is now', shoplist)
print('I will sort my list now')
shoplist.sort()
print('Sorted shopping list is', shoplist)
print('The first item I will buy is', shoplist[0])
olditem = shoplist[0]
del shoplist[0]
print('I bought the', olditem)
print('My shopping list is now', shoplist)
输出:
$ python ds_using_list.py
I have 4 items to purchase.
These items are: apple mango carrot banana
I also have to buy rice.
My shopping list is now ['apple', 'mango', 'carrot', 'banana', 'rice']
I will sort my list now
Sorted shopping list is ['apple', 'banana', 'carrot', 'mango', 'rice']
The first item I will buy is apple
I bought the apple
My shopping list is now ['banana', 'carrot', 'mango', 'rice']
元组
元组(Tuple)用于将多个对象保存到一起。元组的一大特征类似于字符串,它们是不可变的,也就是说,你不能编辑或更改元组。
元组对应的类型是tuple
案例:
# 我会推荐你总是使用括号
# 来指明元组的开始与结束
# 尽管括号是一个可选选项。
# 明了胜过晦涩,显式优于隐式。
zoo = ('python', 'elephant', 'penguin')
print('Number of animals in the zoo is', len(zoo))
new_zoo = 'monkey', 'camel', zoo
print('Number of cages in the new zoo is', len(new_zoo))
print('All animals in new zoo are', new_zoo)
print('Animals brought from old zoo are', new_zoo[2])
print('Last animal brought from old zoo is', new_zoo[2][2])
print('Number of animals in the new zoo is',
len(new_zoo)-1+len(new_zoo[2]))
输出:
$ python ds_using_tuple.py
Number of animals in the zoo is 3
Number of cages in the new zoo is 3
All animals in new zoo are ('monkey', 'camel', ('python', 'elephant', 'penguin'))
Animals brought from old zoo are ('python', 'elephant', 'penguin')
Last animal brought from old zoo is penguin
Number of animals in the new zoo is 5
包含 0 或 1 个项目的元组
一个空的元组由一对圆括号构成,就像 myempty = () 这样。然而,一个只拥有一个项目的元组并不像这样简单。你必须在第一个(也是唯一一个)项目的后面加上一个逗号来指定它,如此一来 Python 才可以识别出在这个表达式想表达的究竟是一个元组还是只是一个被括号所环绕的对象,也就是说,如果你想指定一个包含项目 2 的元组,你必须指定 singleton = (2, ) 。
字典
字典其实就是一个映射。在字典中键不能重复,并且键必须是不可变的对象,而对应的值可以是可变的或不可变的。
另外需要记住,字典中的成对的键—值配对不会以任何方式进行排序。
下面使用的字典是属于dict类下的实例或对象。
# “ab”是地址(Address)簿(Book)的缩写
ab = {
'Swaroop':'[email protected]',
'Larry' : '[email protected]',
'Matsumoto':'[email protected]',
'Spammer':'[email protected]'
}
print("Swaroop's address is", ab['Swaroop'])
# 删除一对键值—值配对
del ab['Spammer']
print('\nThere are {} contacts in the address-book\n'.format(len(ab)))
for name, address in ab.items() :
print('Contact {} at {} '.format(name, address))
# 添加一堆键——值配对
ab['Guido']='[email protected]'
if 'Guido' in ab :
print("\nGuido's address is", ab['Guido'])
输出:
$ python ds_using_dict.py
Swaroop's address is [email protected]
There are 3 contacts in the address-book
Contact Swaroop at [email protected]
Contact Matsumoto at [email protected]
Contact Larry at [email protected]
Guido's address is [email protected]
序列
序列的主要功能就是判断是否存在和索引操作。
列表、元组和字符串都是序列,它们都拥有切片操作。切片操作可以获取序列中的一部分。
案例:
shoplist = ['apple', 'mango', 'carrot', 'banana']
name = 'swaroop'
# Indexing or 'Subscription' operation #
# 索引或“下标(Subscription)”操作符 #
print('Item 0 is', shoplist[0])
print('Item 1 is', shoplist[1])
print('Item 2 is', shoplist[2])
print('Item 3 is', shoplist[3])
print('Item -1 is', shoplist[-1])
print('Item -2 is', shoplist[-2])
print('Character 0 is', name[0])
# Slicing on a list #
print('Item 1 to 3 is', shoplist[1:3])
print('Item 2 to end is', shoplist[2:])
print('Item 1 to -1 is', shoplist[1:-1])
print('Item start to end is', shoplist[:])
# 从某一字符串中切片 #
print('characters 1 to 3 is', name[1:3])
print('characters 2 to end is', name[2:])
print('characters 1 to -1 is', name[1:-1])
print('characters start to end is', name[:])
输出:
$ python ds_seq.py
Item 0 is apple
Item 1 is mango
Item 2 is carrot
Item 3 is banana
Item -1 is banana
Item -2 is carrot
Character 0 is s
Item 1 to 3 is ['mango', 'carrot']
Item 2 to end is ['carrot', 'banana']
Item 1 to -1 is ['mango', 'carrot']
Item start to end is ['apple', 'mango', 'carrot', 'banana']
characters 1 to 3 is wa
characters 2 to end is aroop
characters 1 to -1 is waroo
characters start to end is swaroop
切片时可以提供第三个数字,这数字就是步长,如果不提供的话就默认为1。
案例:
>>> shoplist = ['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::1]
['apple', 'mango', 'carrot', 'banana']
>>> shoplist[::2]
['apple', 'carrot']
>>> shoplist[::3]
['apple', 'banana']
>>> shoplist[::-1]
['banana', 'carrot', 'mango', 'apple']
你会注意到当步长为 2 时,我们得到的是第 0、2、4…… 位项目。当步长为 3 时,我们得到的是第 0、3……位项目。
集合
集合(Set)是简单对象的无序集合(Collection)。集合中元素无序且唯一,集合主要是用来测试项目是否存在,子集判断和交集运算等。
案例:
>>> bri = set(['brazil', 'russia', 'india'])
>>> 'india' in bri
True
>>> 'usa' in bri
False
>>> bric = bri.copy()
>>> bric.add('china')
>>> bric.issuperset(bri)
True
>>> bri.remove('russia')
>>> bri & bric # OR bri.intersection(bric)
{'brazil', 'india'}
引用
就是将一个变量绑定到一个对象。这个变量不是代表那个对象本身,只是指向了内存中存储相应对象的那一部分。
案例:
print('Simple Assignment')
shoplist = ['apple', 'mango', 'carrot', 'banana']
# mylist 只是指向同一对象的另一种名称
mylist = shoplist
# 我购买了第一项项目,所以我将其从列表中删除
del shoplist[0]
print('shoplist is', shoplist)
print('mylist is', mylist)
# 注意到 shoplist 和 mylist 二者都
# 打印出了其中都没有 apple 的同样的列表,以此我们确认
# 它们指向的是同一个对象
print('Copy by making a full slice')
# 通过生成一份完整的切片制作一份列表的副本
mylist = shoplist[:]
# 删除第一个项目
del mylist[0]
print('shoplist is', shoplist)
print('mylist is', mylist)
输出:
$ python ds_reference.py
Simple Assignment
shoplist is ['mango', 'carrot', 'banana']
mylist is ['mango', 'carrot', 'banana']
Copy by making a full slice
shoplist is ['mango', 'carrot', 'banana']
mylist is ['carrot', 'banana']
有关字符串的更多内容
你在程序中使用的所有字符串都是 str 类下的对象。可以查阅help(str)获得更多关于字符串的信息。
案例:
# 这是一个字符串对象
name = 'Swaroop'
if name.startswith('Swa') :
print('Yes, the string starts with "Swa"')
if 'a' in name :
print('Yes, it contains the string "a"')
if name.find('war') != -1 :
print('Yes, it contains the string "war"')
delimiter = '_*_'
mylist = ['Brazil', 'Russia', 'India', 'China']
print(delimiter.join(mylist))
输出:
$ python ds_str_methods.py
Yes, the string starts with "Swa"
Yes, it contains the string "a"
Yes, it contains the string "war"
Brazil_*_Russia_*_India_*_China
startswith 方法用于查找字符串是否以给定的字符串内容开头。 in 运算符用以检查给定的字符串是否是查询的字符串中的一部分。
find 方法用于定位字符串中给定的子字符串的位置。如果找不到相应的子字符串, find会返回 -1。 str 类同样还拥有一个简洁的方法用以 联结(Join) 序列中的项目,其中字符串将会作为每一项目之间的分隔符,并以此生成并返回一串更大的字符串。
解决问题
待解决的问题:
我想要一款程序来备份我所有的重要文件。
《简明Python教程》解决问题的方式:
- 先分析问题,提出一些需要解决的问题。
- 设计稳定的方案,确定步骤
- 进行编码实现
- 进行测试
解决方案:
import os
import time
# 1. 需要备份的文件与目录将被
# 指定在一个列表中。
# 例如在 Windows 下:
# source = ['"C:\\My Documents"', 'C:\\Code']
# 又例如在 Mac OS X 与 Linux 下:
source = ['/Users/swa/notes']
# 在这里要注意到我们必须在字符串中使用双引号
# 用以括起其中包含空格的名称。
#2. 备份文件必须存储在一个
#主备份目录中
#例如在 Windows 下:
# target_dir = 'E:\\Backup'
# 又例如在 Mac OS X 和 Linux 下:
target_dir = '/Users/swa/backup'
# 要记得将这里的目录地址修改至你将使用的路径
# 3. 备份文件将打包压缩成 zip 文件。
# 4. zip 压缩文件的文件名由当前日期与时间构成。
target = target_dir + os.sep + \
time.strftime('%Y%m%d%H%M%S') + '.zip'
# 如果目标目录还不存在,则进行创建
if not os.path.exists(target_dir):
os.mkdir(target_dir) # 创建目录
# 5. 我们使用 zip 命令将文件打包成 zip 格式
zip_command = 'zip -r {0} {1}'.format(target,
' '.join(source))
# 运行备份
print('Zip command is:')
print(zip_command)
print('Running:')
if os.system(zip_command) == 0:
print('Successful backup to', target)
else:
print('Backup FAILED')
输出:
$ python backup_ver1.py
Zip command is:
zip -r /Users/swa/backup/20140328084844.zip /Users/swa/notes
Running:
adding: Users/swa/notes/ (stored 0%)
adding: Users/swa/notes/blah1.txt (stored 0%)
adding: Users/swa/notes/blah2.txt (stored 0%)
adding: Users/swa/notes/blah3.txt (stored 0%)
Successful backup to /Users/swa/backup/20140328084844.zip
针对 Windows 用户的提示
除了使用双反斜杠转义序列,你还可以使用原始字符串。例如使用 'C:\Documents' 或
r'C:\Documents' 。然而,不要使用 'C:\Documents' ,因为它将被识别为你使用了一个
未知的转义序列 \D 来结束路径的输入。
可以对上述的程序持续的改进,参照《简明Python教程》的P94.
软件开发流程
我们已经经历了开发一款软件的流程中的各个 阶段(Phases) 。现在可以将这些阶段总结如下:
- What/做什么(分析)
- How/怎么做(设计)
- Do It/开始做(执行)
- Test/测试(测试与修复错误)
- Use/使用(操作或开发)
- Maintain/维护(改进)
编写程序时推荐的一种方式是遵循我们在编写备份脚本时所经历的步骤:进行分析与设计;开始实现一个简单版本;测试并修复错误;开始使用以确保工作状况皆如期望那般。现在,你可以添加任何你所希望拥有的功能,并继续去重复这一“开始做—测试—使用”循环,需要做多少次就去做多少次。
要记住:
程序是成长起来的,不是搭建出来的。 (Software is grown, not built.) ——Bill dehÓra
zhg: 这一章节非常的重要,强烈建议看一下《简明Python教程》的“解决问题”章节。
面向对象编程
类与对象是面向对象编程的两个主要方面。一个类(Class)能够创建一种新的类型(Type),其中对象(Object)就是类的实例(Instance)。可以这样来类比:你可以拥有类型 int 的变量,也就是说存储整数的变量是 int 类的实例(对象)。
需要注意的是:在Python中int也是一个类型,整数也会被视为对象。
在对象中存储数据的普通变量称作“字段”。对象可以调用类的函数来实现功能,这些函数叫做“方法”。
总之,字段与方法通称类的属性(Attribute)。
字段有两种类型——它们属于某一类的各个实例或对象,或是从属于某一类本身。它们被分别称作实例变量(Instance Variables)与类变量(Class Variables)。
通过 class 关键字可以创建一个类。这个类的字段与方法可以在缩进代码块中予以列出。
self
类方法与普通函数只有一种特定的区别——前者必须多加一个参数在参数列表开头,这个名字必须添加到参数列表的开头,但是你不用在你调用这个功能时为这个参数赋值,Python 会为它提供。这种特定的变量引用的是对象本身,按照惯例,它被赋予 self 这一名称。
上述参数也可以指定为其他的名称,但是强烈建议使用self,方便其他人识别,也有利于IDE提供编程提示。
你一定会在想 Python 是如何给 self 赋值的,以及为什么你不必给它一个值。一个例子或许会让这些疑问得到解答。假设你有一个 MyClass 的类,这个类下有一个实例 myobject 。当你调用一个这个对象的方法,如 myobject.method(arg1, arg2) 时,Python 将会自动将其转换成 MyClass.method(myobject, arg1, arg2) ——这就是 self 的全部特殊之处所在。
这同时意味着,如果你有一个没有参数的方法,你依旧必须拥有一个参数—— self 。
类
最简单的类如下:
class Person:
pass # 一个空的代码块
p = Person()
print(p)
输出:
$ python oop_simplestclass.py
<__main__.Person instance at 0x10171f518>
方法
类和对象都可以带有方法,但是需要多一个额外的参数。
案例:
class Person:
def say_hi(self):
print('Hello, how are you?')
p = Person()
p.say_hi()
# 前面两行同样可以写作
# Person().say_hi()
输出:
$ python oop_method.py
Hello, how are you?
要注意到 say_hi 这一方法不需要参数,但是依旧在函数定义中拥有 self 变量。
__init__方法
这个方法会在对象被示例化的时候被调用,其实就相当于是构造函数。
案例:
class Person:
def __init__(self, name):
self.name = name
def say_hi(self):
print('Hello, my name is', self.name)
p = Person('Swaroop')
p.say_hi()
# 前面两行同时也能写作
# Person('Swaroop').say_hi()
输出:
$ python oop_init.py
Hello, my name is Swaroop
self.name 中的点号意味着这个叫作“name”的东西是某个叫作“self”的对象的一部分,而另一个 name 则是一个局部变量。
zhg: 注意上面这个案例中创建对象字段的方式,根本就没先定义,而是直接就开始赋值了。
类变量与对象变量
数据部分——也就是字段——只不过是绑定(Bound)到类与对象的命名空间(Namespace)的普通变量。这些名称仅在这些类与对象所在的上下文中有效,这就是它们被称作“命名空间”的原因。
字段有两种类型——类变量和对象变量。
类变量是属于类的,所有类的实例可以共享这个变量,类变量只有一个副本,当一个类的实例对类变量进行改变时,其他的实例都可以看到这种改变。
对象变量是属于实例或对象的,每个实例都会有对象变量的一个单独副本,在一个实例中对对象变量进行改变时,其他实例中的同名对象变量不会有任何的变化。
案例:
# coding=UTF-8
class Robot:
"""表示有一个带名字的机器人"""
# 一个类变量, 用来计数机器人的数量
population = 0
def __init__(self, name):
"""初始化数据"""
self.name = name
print("(Initializing {})".format(self.name))
# 当有人被创建时,机器人
# 将会增加人口数量
Robot.population += 1
def die(self):
"""我挂了"""
print("{} is being destroyed!".format(self.name))
Robot.population -= 1
if Robot.population == 0:
print("{} was the last one.".format(self.name))
else:
print("There are still {:d} robots working.".format(
Robot.population))
def say_hi(self):
"""来自机器人的诚挚问候
没问题,你做得到。"""
print("Greetings, my masters call me {}.".format(self.name))
@classmethod
def how_many(cls):
"""打印出当前的人口数量"""
print("We have {:d} robots.".format(cls.population))
droid1 = Robot("R2-D2")
droid1.say_hi()
Robot.how_many()
droid2 = Robot("C-3P0")
droid2.say_hi()
Robot.how_many()
print("\nRobots can do some work here.\n")
print("Robots have finished their work. So let's destroy them.")
droid1.die()
droid2.die()
Robot.how_many()
输出:
$ python oop_objvar.py
(Initializing R2-D2)
Greetings, my masters call me R2-D2.
We have 1 robots.
(Initializing C-3PO)
Greetings, my masters call me C-3PO.
We have 2 robots.
Robots can do some work here.
Robots have finished their work. So let's destroy them.
R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots.
除了 Robot.popluation ,我们还可以使用 self.__class__.population ,因为每个对象都通过
self.__class__ 属性来引用它的类。
我们使用装饰器(Decorator)将 how_many 方法标记为类方法。
你可以将装饰器想象为调用一个包装器(Wrapper)函数的快捷方式,因此启用@classmethod 装饰器等价于调用:
how_many = classmethod(how_many)
你需要记住你只能使用 self 来引用同一对象的变量与方法。这被称作属性引用(Attribute Reference)。
所有的类成员都是公开的,但是如果使用双下划线作为名称前缀,例如__privatevar,那么Python 会使用名称调整(Namemangling)来使其有效地成为一个私有变量。
针对 C++/Java/C# 程序员的提示
所有类成员(包括数据成员)都是公开的,并且 Python 中所有的方法都是虚拟的(Virtual)。
继承
面向对象编程的一大优点是对代码的重用(Reuse),重用的一种实现方法就是通过继承(Inheritance)机制。继承最好是想象成在类之间实现类型与子类型(Type and Subtype)关系的工具。
对于基类的改动会自动反映到子类型中。在某些情况下可以将子类型对象看作是基类对象,这叫做多态性。
案例:
# coding=UTF-8
class SchoolMember:
'''代表任何学校里的成员'''
def __init__(self, name, age):
self.name = name
self.age = age
print('(Initialized SchoolMember: {})'.format(self.name))
def tell(self):
'''告诉我有关我的细节'''
print('Name:"{}" Age:"{}"'.format(self.name, self.age), end=" ")
class Teacher(SchoolMember) :
'''代表一位老师。'''
def __init__(self, name, age, salary):
SchoolMember.__init__(self, name, age)
self.salary = salary
print('(Initialized Teacher: {})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Salary: "{:d}"'.format(self.salary))
class Student(SchoolMember):
'''代表一位学生'''
def __init__(self, name, age, marks):
SchoolMember.__init__(self, name, age)
self.marks = marks
print('(Initialized Student: {})'.format(self.name))
def tell(self):
SchoolMember.tell(self)
print('Marks: "{:d}"'.format(self.marks))
t = Teacher('Mrs.Shrividya', 40, 30000)
s =Student('Swaroop', 25, 75)
#打印一行空白行
print()
members = [t, s]
for member in members :
#对全体师生工作
member.tell()
输出:
$ python oop_subclass.py
(Initialized SchoolMember: Mrs. Shrividya)
(Initialized Teacher: Mrs. Shrividya)
(Initialized SchoolMember: Swaroop)
(Initialized Student: Swaroop)
Name:"Mrs. Shrividya" Age:"40" Salary: "30000"
Name:"Swaroop" Age:"25" Marks: "75"
下面这一点很重要,需要牢记——因为我们在 Teacher 和 Student 子类中定义了__init__ 方法,Python 不会自动调用基类 SchoolMember 的构造函数,你必须自己显式地调用它。
相反,如果我们没有在一个子类中定义一个 init 方法,Python 将会自动调用基类的构造函数。
定义子类时,子类名称后的括号是一个元组,称作继承元组。
这里有一条有关术语的注释——如果继承元组(Inheritance Tuple)中有超过一个类,这种情况就会被称作多重继承(Multiple Inheritance)。
总结:
Python是高度面向对象,所有的东西都可以看作是对象,包括函数。所以有必要清楚的知道类与对象的知识。
输入与输出
可以用input()获取用户输入,print()打印输出。
对于输入,我们还可以使用 str (String,字符串)类的各种方法。
下面主要是探讨文件的输入与输出。
用户输入内容
案例:
def reverse(text):
return text[::-1]
def is_palindrome(text): # 判断是否是回文
return text == reverse(text)
something = input('Enter text: ')
if is_palindrome(something) :
print('Yes, it is a palindrome')
else:
print('No, it is not a palindrome')
输出:
$ python3 io_input.py
Enter text: sir
No, it is not a palindrome
$ python3 io_input.py
Enter text: madam
Yes, it is a palindrome
$ python3 io_input.py
Enter text: racecar
Yes, it is a palindrome
input() 函数可以接受一个字符串作为参数,并将其展示给用户。尔后它将等待用户输入内容或敲击返回键。一旦用户输入了某些内容并敲下返回键, input() 函数将返回用户输入的文本。
文件
你可以通过创建一个属于 file 类的对象并适当使用它的 read 、 readline 、 write 方法来打开或使用文件,并对它们进行读取或写入。读取或写入文件的能力取决于你指定以何种方式打开文件。
当完成文件操作后,要调用close()方法关闭文件。
案例:
poem = '''\
Programming is fun
When the work is done
if you wanna make your work also fun:
use Python!
'''
# 打开文件以编辑('w'riting)
f = open('poem.txt', 'w')
# 向文件中编写文本
f.write(poem)
# 关闭文件
f.close()
# 如果没有特别指定,
# 将假定启用默认的阅读('r'ead)模式
f = open('poem.txt')
while True :
line = f.readline()
# 零长度指示 EOF
if len(line) == 0:
break
# 每行(`line`)的末尾
# 都已经有了换行符
#因为它是从一个文件中进行读取的
print(line, end='')
#关闭文件
f.close()
输出:
$ python3 io_using_file.py
Programming is fun
When the work is done
if you wanna make your work also fun:
use Python!
open()函数会以指定的模式打开文件,有关更多详细的模式,可以用help(open)进行查看。
Pickle
可以将Python对象输出到文件中,并且稍后可以取回。这叫作持久地存储对象。
案例:
import pickle
# 我们存储相关对象的文件的名称
shoplistfile = 'shoplist.data'
# 需要购买的物品清单
shoplist = ['apple', 'mango', 'carrot']
# 准备写入文件
f = open(shoplistfile, 'wb')
# 转储对象至文件
pickle.dump(shoplist, f)
f.close()
# 清除 shoplist 变量
del shoplist
# 重新打开存储文件
f = open(shoplistfile, 'rb')
# 从文件中载入对象
storedlist = pickle.load(f)
print(storedlist)
输出:
$ python io_pickle.py
['apple', 'mango', 'carrot']
要想将一个对象存储到一个文件中,我们首先需要通过 open 以写入(write)二进制(binary)模式打开文件,然后调用 pickle 模块的 dump 函数。这一过程被称作封装(Pickling)。
接着,我们通过 pickle 模块的 load 函数接收返回的对象。这个过程被称作拆封(Unpickling)。
Unicode
前面使用和操作字符串的时候都没关关注编码问题。
注意:如果你正在使用 Python 2,我们又希望能够读写其它非英语语言,我们需要使用
unicode 类型,它全都以字母 u 开头,例如 u"hello world"
>>> "hello world"
'hello world'
>>> type("hello world")
>>> u"hello world"
'hello world'
>>> type(u"hello world")
zhg: 上述案例中,type()可以查看对象的类型。
若需要制定打开文件的编码是UTF-8,则在open()函数中添加一个关键字参数就可以完成。
案例:
# encoding=utf-8
import io
f = io.open("abc.txt", "wt", encoding="utf-8")
f.write(u"Imagine non-English language here")
f.close()
text = io.open("abc.txt", encoding="utf-8").read()
print(text)
每当我们诸如上面那番使用 Unicode 字面量编写一款程序时,我们必须确保 Python 程序已经被告知我们使用的是 UTF-8,因此我们必须将 # encoding=utf-8 这一注释放置在我们程序的顶端。
异常
当程序发生错误的时候,Python就会通过异常来告诉你哪儿出现了错误。
错误
>>> Print("Hello World")
Traceback (most recent call last):
File "", line 1, in
NameError: name 'Print' is not defined
>>> print("Hello World")
Hello World
你会注意到一个 NameError 错误被抛出,同时 Python 还会打印出检测到的错误发生的位置。这就是一个错误错误处理器(Error Handler) 为这个错误所做的事情。
异常
我们将尝试(Try)去读取用户的输入内容。按下 [ctrl-d] 来看看会发生什么事情。
>>> s = input('Enter something --> ')
Enter something --> Traceback (most recent call last):
File "", line 1, in
EOFError
处理异常
可以通过使用 try..except 来处理异常状况。一般来说我们会把通常的语句放在 try 代码块中,将我们的错误处理器代码放置在 except 代码块中。
类似于Java的try{}catch(){}机制。
案例:
try:
text = input('Enter something --> ')
except EOFError:
print('why did you do an EOF on me?')
except KeyboardInterrupt:
print('You cancelled the operation. ')
else: # 没有异常发生
print('You entered {}'.format(text))
输出:
# Press ctrl + d windows下是ctrl + z代表输入结束
$ python exceptions_handle.py
Enter something --> Why did you do an EOF on me?
# Press ctrl + c
$ python exceptions_handle.py
Enter something --> ^CYou cancelled the operation.
$ python exceptions_handle.py
Enter something --> No exceptions
You entered No exceptions
将所有可能引发异常或错误的语句放在 try 代码块中,并将相应的错误或异常的处理器(Handler)放在 except 子句或代码块中。 except 子句可以处理某种特定的错误或异常,或者是一个在括号中列出的错误或异常。如果没有提供错误或异常的名称,它将处理所有错误与异常。
必须至少有一句 except 字句与每一句 try 字句相关联。不然,有一个 try 代码块又有什么意义?
抛出异常
可以通过 raise 语句来引发一次异常。
你能够引发的错误或异常必须是直接或间接从属于 Exception (异常) 类的派生类。
案例:
# encoding=utf-8
class ShortInputException(Exception):
'''一个由用户定义的异常类'''
def __init__(self, length, atleast):
Exception.__init__(self)
self.length = length
self.atleast = atleast
try:
text = input('Enter something -->')
if len(text) < 3:
raise ShortInputException(len(text), 3)
# 其他工作能在此处继续正常运行
except EOFError:
print('Why did you do an EOF on me?')
except ShortInputException as ex: # 注意这儿的as
print(('ShortInputException: The input was '+
'{0} long, expected ad least {1}')
.format(ex.length, ex.atleast))
else:
print('No exception was raised.')
输出:
$ python exceptions_raise.py
Enter something --> a
ShortInputException: The input was 1 long, expected at least 3
$ python exceptions_raise.py
Enter something --> abc
No exception was raised.
Try ... Finally
Try...Finally块可以确保打开的文件一定被正常的关闭,也就是说无论是否发生异常,finally子句中的代码块都会被执行。
案例:
import sys
import time
f = None
try:
f = open("poem.txt")
# 我们常用的文件阅读风格
while True:
line = f.readline()
if len(line) == 0:
break
print(line, end='')
sys.stdout.flush()
print("Press ctrl+c now")
# 为了确保它能运行一段时间
time.sleep(3)
except IOError:
print("Could not find file poem.txt")
except KeyboardInterrupt:
print("!! You cancelled the reading from the file.")
finally:
if f:
f.close()
print("(Cleaning up: closed the file)")
输出:
$ python exceptions_finally.py
Programming is fun
Press ctrl+c now
^C!! You cancelled the reading from the file.
(Cleaning up: Closed the file)
with语句
在 try 块中获取资源,然后在 finally 块中释放资源是一种常见的模式。因此,还有一个with 语句使得这一过程可以以一种干净的姿态得以完成。
案例:
with open("poem.txt") as f :
for line in f:
print(line, end='')
我们使用的是 open 函数与 with 语句——我们将关闭文件的操作交由 with open 来自动完成。
其实是with使用了一个协议,总会在代码块开始之前调用 thefile.__enter__ 函数,并且总会在代码块执行完毕之后调用 thefile.__exit__ 。
标准库
Python 标准库(Python Standrad Library)中包含了大量有用的模块,同时也是每个标准的Python 安装包中的一部分。
Sys模块
sys 模块包括了一些针对特定系统的功能。我们已经了解过 sys.argv 列表中包括了命令行参数。
日志模块
logging 模块可以记录日志。
案例:
import os
import platform
import logging
if platform.platform().startswith('Windows'):
logging_file = os.path.join(os.getenv('HOMEDRIVE'),
os.getenv('HOMEPATH'),
'test.log')
else:
logging_file = os.path.join(os.getenv('HOME'),
'test.log')
print("logging to", logging_file)
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s : %(levelname)s : %(message)s',
filename=logging_file,
filemode='w',
)
logging.debug("Start of the program")
logging.info("Doing something")
logging.warning("Dying now")
输出:
$ python stdlib_logging.py
Logging to /Users/swa/test.log
$ cat /Users/swa/test.log
2014-03-29 09:27:36,660 : DEBUG : Start of the program
2014-03-29 09:27:36,660 : INFO : Doing something
2014-03-29 09:27:36,660 : WARNING : Dying now
我们使用了三款标准库中的模块—— os 模块用以和操作系统交互, platform 模块用以获取平台——操作系统——的信息, logging 模块用来记录(Log)信息。
每周模块系列:标准库中还有非常多的有用模块,可以阅读Doug Hellmann撰写的优秀的Python Module of the Week或者阅读Python的官方文档。
更多
前面的部分涵盖了将使用到的Python的大部分方面,下面介绍一些其他部分,使得对Python的认识更加全面。
传递元组
你可曾希望从一个函数中返回两个不同的值?你能做到的。只需要使用一个元组。
>>> def get_error_details():
... return (2, 'details')
...
>>> errnum, errstr = get_error_details()
>>> errnum
2
>>> errstr
'details'
要注意到 a, b =
这也意味着在 Python 中交换两个变量的最快方法是:
>>> a = 5; b = 8
>>> a, b
(5, 8)
>>> a, b = b, a
>>> a, b
(8, 5)
特殊方法
诸如 __init__ 和 __del__ 等一些方法对于类来说有特殊意义。
特殊方法用来模拟内置类型的某些行为。如果你希望为你的类使用 x[key] 索引操作(就像你在列表与元组中使用的那样),那么你所需要做的只不过是实现__getitem__() 方法,然后你的工作就完成了。如果你试图理解它,就想想 Python 就是对list 类这样做的!
如果想了解更多的特殊方法,可以参阅Python手册
单语句块
每个语句块都是由其自身的缩进级别与其他部分进行区分。如果你的语句块只有单独的一句,那么你可以在同一行指定它,例如条件语句与循环语句。
案例:
>>> flag = True
>>> if flag: print('Yes')
...
Yes
注意,单个语句是在原地立即使用的,它不会被看作一个单独的块。虽然这个方式可以使得程序更加小巧,但是除非是为了检查错误,建议避免使用这种快捷方式。
Lambda表格
lambda 语句可以创建一个新的函数对象。从本质上说, lambda 需要一个参数,后跟一个表达式作为函数体,这一表达式执行的值将作为这个新函数的返回值。
案例:
points = [{'x': 2, 'y': 3},
{'x': 4, 'y': 1}]
points.sort(key=lambda i : i['y'])
print(points)
输出:
$ python more_lambda.py
[{'y': 1, 'x': 4}, {'y': 3, 'x': 2}]
要注意到一个 list 的 sort 方法可以获得一个 key 参数,用以决定列表的排序方式(通常我们只知道升序与降序)。在我们的案例中,我们希望进行一次自定义排序,为此我们需要编写一个函数,但是又不是为函数编写一个独立的 def 块,只在这一个地方使用,因此我们使用 Lambda 表达式来创建一个新函数。
列表推导
列表推导(List Comprehension)用于从一份现有的列表中得到一份新列表。想象一下,现在你已经有了一份数字列表,你想得到一个相应的列表,其中的数字在大于 2 的情况下将乘以2。列表推导就是这类情况的理想选择。
案例:
listone = [2, 3, 4]
listtwo = [2*i for i in listone if i > 2]
print(listtwo)
输出:
$ python more_list_comprehension.py
[6, 8]
在本案例中,当满足了某些条件时( if i > 2 ),我们进行指定的操作( 2*i ),以此来获得一份新的列表。要注意到原始列表依旧保持不变。
使用列表推导的优点在于可以减少代码。
在函数中接收元组与字典
有一种特殊方法,即分别使用 * 或 ** 作为元组或字典的前缀,来使它们作为一个参数为函数所接收。当函数需要一个可变数量的实参时,这将颇为有用。
>>> def powersum(power, *args):
... '''Return the sum of each argument raised to the specified power.'''
... total = 0
... for i in args:
... total += pow(i, power)
... return total
...
>>> powersum(2, 3, 4)
25
>>> powersum(2, 10)
100
我们在 args 变量前添加了一个 * 前缀,函数的所有其它的额外参数都将传递到args 中,并作为一个元组予以储存。如果采用的是 ** 前缀,则额外的参数将被视为字典的键值—值配对。
assert语句
assert 语句用以断言(Assert)某事是真的。
当assert判断事实为真时,则会继续执行后续代码,如果判断其不是真的,那么就会抛出一个错误。当语句断言失败时,将会抛出 AssertionError 。
案例:
>>> mylist = ['item']
>>> assert len(mylist) >= 1
>>> mylist.pop()
'item'
>>> assert len(mylist) >= 1
Traceback (most recent call last):
File "", line 1, in
AssertionError
zhg: 自己的理解,这个assert就是用来验证自己的猜测和假设的,如果猜测和假设是真的,那么后续的基于假设和猜测的逻辑就不会有有问题,那么assert会通过,并继续执行后续代码;如果猜测和假设是错误的,那么后续的基于假设和猜测的逻辑就是有问题的,那么assert就不会通过,则会抛出错误,进而阻止后续代码的运行。
装饰器
装饰器(Decorators)是应用包装函数的快捷方式。这有助于将某一功能与一些代码一遍又一遍地“包装”。
案例:
from time import sleep
from functools import wraps
import logging
logging.basicConfig()
log = logging.getLogger("retry")
def retry(f):
@wraps(f)
def wrapped_f(*args, **kwargs):
MAX_ATTEMPS = 5
for attempt in range(1, MAX_ATTEMPS + 1):
try:
return f(*args, **kwargs)
except:
log.exception("Attemp %s/%s failed :%s",
attempt,
MAX_ATTEMPS,
(args, kwargs))
sleep(10 * attempt)
log.critical("All %s attemps failed : %s",
MAX_ATTEMPS,
(args, kwargs))
return wrapped_f
counter = 0
@retry
def save_to_database(arg):
print("Write to a database or make a network call or etc.")
print("This will be automatically retried if exception is thrown.")
global counter
counter += 1
# 这将在第一次调用时抛出异常
# 在第二次运行时将正常工作(也就是重试)
if counter < 2:
raise ValueError(arg)
if __name__ == '__main__':
save_to_database("Some bad value")
输出:
$ python more_decorator.py
Write to a database or make a network call or etc.
This will be automatically retried if exception is thrown.
ERROR:retry:Attempt 1/5 failed : (('Some bad value',), {})
Traceback (most recent call last):
File "more_decorator.py", line 14, in wrapped_f
return f(*args, **kwargs)
File "more_decorator.py", line 39, in save_to_database
raise ValueError(arg)
ValueError: Some bad value
Write to a database or make a network call or etc.
This will be automatically retried if exception is thrown.