@[toc]
一、再谈print和import
下面就来看看print
和import
隐藏的几个特性。
1. 打印多个参数
print
可用于打印多个表达式,条件是用逗号分隔它们。默认会在输出时在逗号两边的内容之间加入一个空格。
>>> print('Age:',42) #Age: 42
默认会在输出时在逗号两边的内容之间加入一个空格。
我们可以使用sep='xxx'
自定义分隔符:
>>> print("I","wish","to","register","a","complaint",sep='_')
#I_wish_to_register_a_complaint
还可以自定义结束字符串,以替换默认的换行符。例如,如果将结束字符串指定为空字符串,以后就可继续打印到当前行。
>>> print('Hello,', end='')
>>> Print('world!')
#'Hello, world!
仅当这些代码包含在脚本中时才如此。
2. 导入时重命名
从模块导入时,通常使用import somemodule
或使用from somemodule import somefunction
或from somemodule import somefunction, anotherfunction, yetanotherfunction
或from somemodule import *
仅当你确定要导入模块中的一切时,采用使用最后一种方式。
import math
的意思是从Python标准库中引入math.py
模块,这是Python中定义的引入模块的方法。当解释器遇到import语句时,如果模块在当前搜索路径就会被导入。搜索路径是一个解释器,会先搜索所有目录的列表。
Python的搜索路径由一系列目录名组成,Python解释器会依次从这些目录中寻找引入的模块。 搜索路径是在Python编译或安装时确定的,被存储在sys
模块的path
变量中。查看搜索路径的方式如下:
>>> import sys
>>> print('Python的搜索路径为:%s'%sys.path)
#['C:/Users/MIC/Desktop', 'C:\\Users\\MIC\\AppData\\Local\\Programs\\Python\\Python36\\Lib\\idlelib', 'C:\\Users\\MIC\\AppData\\Local\\Programs\\Python\\Python36\\python36.zip', 'C:\\Users\\MIC\\AppData\\Local\\Programs\\Python\\Python36\\DLLs', 'C:\\Users\\MIC\\AppData\\Local\\Programs\\Python\\Python36\\lib', 'C:\\Users\\MIC\\AppData\\Local\\Programs\\Python\\Python36', 'C:\\Users\\MIC\\AppData\\Local\\Programs\\Python\\Python36\\lib\\site-packages']
sys.path
输出了一个列表,第一项输出的是执行文件所在的目录,即我们执行Python解释器的目录(如果是脚本,就是运行脚本所在的目录)。
了解搜索路径的概念后,可以在脚本中修改sys.path
引入一些不在搜索路径中的模块。
在Python中,from语句可以从模块中导入指定部分到当前命名空间中,语法如下:
>>> from modname import name1[,name2[,…nameN]]
如果导入模块,就会得到模块中所有对象;如果指定导入某个对象,就只能得到改对象。
如果要访问模块中的多个对象,可以从一个导入语句导入多个函数,多个函数之间用都好分割。
>>> from math import pi,sin
可以将所有对象引入。
>>> from math import *
但如果有两个模块,它们都包含函数open
,该如何办呢?你可使用第一种方式导入这两个模块,并像下面这样调用函数:
module1.open(…)
module2.open(…)
但还有一种办法:在语句末尾添加as
子句并指定别名。
导入整个模块并给它指定别名
>>> import math as foobar
>>> foobar.sqrt(4) #2.0
导入特定函数并给它指定别名
>>> from math import sqrt as foobar
>>> foobar(4) #2.0
二、赋值魔法
赋值语句也蕴含着一些使用窍门。
1. 序列解包
可以同时(并行)给多个变量赋值。
>>> x,y,z = 1, 3, 5
>>> print(x,y,z) #1 3 5
可以交换多个变量的值。
>>> x, y = y, x
>>> print(x,y,z) #3 1 5
实际上,这里的操作称为序列解包(或可迭代对象解包):将一个序列(或任何可迭代对象)解包,并将得到值存储到一些列的变量中。
>>> values = 1,2,3
>>> x,y,z = values
>>> x #1
这在使用返回元组(或其他序列或可迭代对象)的函数火方法时很有用。假设要从字典中随便获取(或删除)一个键-值对,可使用方法popitem
,他随便获取一个键-值对并以元组的方式返回。接下来,可直接将返回的元组解包到两个变量中。
>>> scoundrel = {'name': 'Robin', 'girlfriend': 'Marion'}
>>> key ,value = scoundrel.popitem()
>>> key #'girlfriend'
>>> value #'Marion'
这让函数能够返回被打包成元组的多个值,然后通过一条赋值语句轻松地访问这些值。要解包的序列包含的元素个数必须与你在等号左边列出的目标个数相同,否则Python将引发异常。
>>> x,y,z =1,2
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
x,y,z =1,2
ValueError: not enough values to unpack (expected 3, got 2)
可使用星号运算符(*
)来收集多余的值,这样无需确保值和变量的个数相同。
>>> a,b,*rest = [1,2,4,5,6,7]
>>> rest #[4, 5, 6, 7]
还可将带星号的变量放在其他位置。
>>> name = "Albus Percival Wulfric Brian Dumbledore"
>>> first, *middle, last = name.split()
>>> middle #['Percival', 'Wulfric', 'Brian']
赋值语句的右边可以是任何类型的序列,但带星号的变量最终包含的总是一个列表。在变量和值的个数相同时亦如此。
>>> a, *b, c = "abc"
>>> a, b, c #('a', ['b'], 'c')
2. 链式赋值
链式赋值是一种快捷方式,用于将多个变量关联到一个值。
x = y = somefunction()
与下面的代码y = somefunction()
x = y
等价
但不一定与x = somefunction()
y = somefunction()
等价
3. 增强赋值
可以不编写代码x = x + 1
,而将右边表达式中的运算符移到赋值运算符的前面,从而写成x += 1
。这成为增强赋值,适用于所有标准运算符,如/
*
%
-
等。
增强赋值也可用于其他数据类型(只要使用的双目运算符可用于这些数据类型)
>>> fnord = 'foo'
>>> fnord += 'bar'
>>> fnord *= 2
>>> fnord #'foobarfoobar'
三、代码块:缩进的乐趣
代码块是一组语句,可在满足条件时执行(if语句),可执行多次(循环),等等。代码块是通过缩进代码(即在前面加空格)来创建的。
注意:也可使用制表符来缩进代码块。Python制表符解释为移到下一个制表位(相邻制表位相距8个空格),但标准(也是更佳)的做法是只使用空格来缩进,且每级缩进4个空格。
在同一个代码块中,各行代码的缩进量必须相同。
在Python中,使用冒号(:)指出接下来是一个代码块,并将该代码块中的每行代码都缩进相同的程度。发现缩进量与之前相同时,你就知道当前代码块到此结束了。
四、条件和条件语句
1. 这正是布尔值的用武之地
真值也称布尔值。
用作布尔表达式时,下面的值都将被解释器视为假:
False
None
0
""
()
{}
[]
其他值都被视为真.
换而言之,标准值False
和None
、各种类型(包括浮点数、复数等)的数值0
、空序列(如空字符串、空元组和空列表)以及空映射(如字典)都被视为假,而其他各种值都被视为真,包括特殊值True
。
如果你看到一个返回1
或0
的表达式,就知道这实际上意味着True
或False
。
布尔值True
和False
属于类型bool
,而bool
和list
、str
和tuple
一样,可用来转换其他的值。
>>> bool(42) #True
因为所有值都可以用作布尔值,所以几乎不需要对它们进行显示转换,Python会自动转换这些值。
2. 有条件地执行和if语句
if语句,让你能够有条件地执行代码。这意味着当条件(if和冒号之间的部分)为真,就执行后续代码块;如果条件为假,就不执行。
>>> name = input("what's you name ")
>>> if name.endswith("Gumby"):
print("hello ,Mr.Gumby)
3. else子句
可以使用else子句增加一种选择(之所以叫子句是因为else不是独立的语句,而是if语句的一部分),修改上面的代码。
>>> name = input("what's you name ")
>>> if name.endswith("Gumby"):
print("hello ,Mr.Gumby")
else :
print("hello ,stranger")
如果没有执行第一个代码块(因为条件为假),将执行else后面的语句
还有一个与if语句很像的"亲戚",它就是条件表达式—C语言中三目运算符的Python版本。
>>> status = "friend" if name.endswith("Gumby") else "stranger"
如果条件(紧跟在if后面)为真,表达式的结果为提供的第一个值(这里为"friend"),否则为第二个值(这里为"stranger")。
4. elif子句
要检查多个条件,可使用elif。elif需要和if联合使用,不能单独使用。
>>> a = 1
>>> b = int(input('Enter a number:')) #input 提示输入 输出的是字符串
>>> if a>b: #if 必须接判断语句
print(a)
elif a == b: # elif 也必须接判断语句 可以多个
print('相等')
else: #不能接判断语句
print(b)
#pass 占位
必须满足判断条件才会执行相应的语句
5. 代码块嵌套
条件语句可以嵌套,注意缩进即可。所谓嵌套代码,是指把if
、else
、elif
等条件语句再放入if
、else
、elif
条件语句块中,作为深层次的条件判定语句。
>>> a=input('请输入: ')
>>> if a.isdigit(): #字符串是否为数字
a=int(a)
if a >= 90: #非0即true
print('A')
elif a >= 80:
print('B')
elif a >= 60:
print('C')
else:
print('差')
else:
print('输入错了')
补充:random
随机数模块
>>> a=random.randint(1,5) # 闭区间 随机生成一个整数
>>> random.random() # 随机生成一个0-1的浮点数
>>> random.randrange(5) #0,1,2,3,4 左闭右开
>>> random.randrange(1,5) #1,2,3,4
>>> li = [1,23,3,4,5]
>>> random.sample(li,2) #从序列中生成两个
>>> random.choice(li) #从序列中生成
6. 更复杂的条件
下面来说说条件本身,因为它们是有条件执行中最有趣的部分。
6.1 比较运算符
在条件表达式中,最基本的就是比较运算符,它们用于执行比较。
==
<
>
<=
>=
!=
is(is not)
in(not in)
对不兼容的类型进行比较:从理论上说,可使用<
和<=
等运算符比较任意两个对象x和y的相对大小,并获得一个真值,但这种比较仅在x和y的类型相同或相近时(如两个整数或一个整数和一个浮点数)才有意义。
与赋值一样,Python也支持链式比较:可同时使用多个比较运算符,如0<age<100
。
(1) 相等运算符:==
要确定两个对象是否相等,可使用比较运算符,用两个等号(==)表示。
>>> "foo" == "foo" #True
>>> "foo" == "bar" #False
(2) 相同运算符:is
其作用看似与==
一样,但is
检查两个对象是否相同(而不是相等)。
>>> x = y =[1, 4, 5]
>>> z = [1, 4, 5]
>>> x is y #True
>>> x is z #False
>>> x == y == z #True
变量x和y指向同一个列表,而z指向另一个列表。这两个列表虽然相等,但并非同一个对象。
注意:尽量避免用is运算符比较数值和字符串这类不可变值。由于Python内部操作这些对象方式的原因,使用is运算符的结果是不可预测的,除非你对堆栈有一定熟悉程度,否则很难预测运算结果。
(3) 成员资格运算符:in
也可用于条件表达式中。
>>> name = input('What is your name')
>>> if 's' in name:
print('your name contains the letter "s".')
else:
print('your name does not contain the letter "s".')
(4) 字符串和序列的比较
字符串是根据字符的字母排列顺序进行比较的。
>>> "alpha" < "beta" #True
虽然基于的是字母排列顺序,但字母都是Unicode
字符,它们是按码点排序的。
实际上,字符是根据顺序值排列的。要获悉字母的顺序值,可使用函数ord
。这个函数的作用与chr
相反。
>>> ord('a') #97
>>> chr(97) #'a'
其他序列的比较方式与此相同,但这些序列包含的元素可能不是字符,而是其他类型的值。
>>> [1, 2] < [2, 1] #True
如果序列的元素为其他序列,将根据同样的规则对这些元素进行比较。
>>> [2, [1, 4]]] < [2, [1, 5]] #True
6.2 布尔运算符
运算符and是一个布尔运算符。它接受两个真值,并在这两个值都为真时返回真,否则返回假。还有另外两个布尔运算符:or和not。通过使用这三个运算符,能以任何方式组合真值。
>>> if ((cash > price) or customer_has_goo_credit) and not out_of_stock:
Give_goods()
布尔运算符有个有趣的特征:只做必要的计算。这种行为称为短路逻辑。
短路原则:
- 对于 and 来说:
如果第一个条件的结论为假,那么 and 前后两个条件组成的表达式计算结果一定为假,后面的条件计算机不会进行计算。 - 对于 or`来说:
如果第一个条件的结论为真,那么 and 前后两个条件组成的表达式计算结果一定为真,后面的条件计算机不会进行计算。
>>> a, b = 0, 1
>>> (c = a) and (b = 3) #c=0; b=1
7. 断言
if语句有一个很有用的亲戚,其工作原理类似于下面的伪代码:
>>> if not condition:
crash program
因为让程序在错误条件出现时立即崩溃胜过以后再崩溃。基本上,你可要求某些条件得到满足(如核实函数参数满足要求或为出事测试和调试提供帮助),为此可在语句中使用关键字assert
。
>>> age = 1
>>> assert 0< age <10
>>> age = -1
>>> assert 0<age<10
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
assert 0<age<10
AssertionError
这里看到,当assert
后面的条件为真时,程序正常运行当assert
后面的条件为假时,输出错误信息。
如果知道必须满足特定条件,程序才能正确地运行,可在程序中添加assert
语句充当检查点,这很有帮助。
还可以在条件后面加一个字符串充当报错信息的补充说明。这个错误提示信息可以称为异常参数。
>>> assert 0<age<10, 'The age must be realistic'
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
assert 0<age<10
AssertionError: The age must be realistic
使用assert
断言时,要注意以下几点:
-
assert
断言用来声明某个条件是真的。 - 如果你非常确信你使用的列表中至少有一个元素,想要检验这一点,并在它非真时引发一个错误,那么
assert
语句是应用在这种情形下的理想语句。 -
assert
语句失败时,会引发一个AssertionError
。
五、循环
1. while循环
在Python编程中,while
语句用于循环执行程序,以处理需要重复处理的额任务。基本语法形式为:
while 判断条件:
执行语句…
执行语句可以是单个语或语句块。判断条件可以是任何表达式,所有非零、非空的值都为真。当判断条件为假时,循环结束。
>>> while True: #满足条件执行,需要终止条件
print(1)
这样会一直打印1,因为没有结束条件
>>> while a < 5:
>>> a+=1
>>> print(a)
>>> name = ''
>>> while not name:
name = input("name:")
print("hello ,{}".format(name))
请尝试运行这些代码,并在要求你输入名字时直接按回车。你会看到提示信息再次出现,因为name还是为空字符串,这相当于假。
break
终止本循环
>>> a=10
>>> while a>4:
a-=1
if a == 5:
break
print(a)
continue #跳过本次循环,但是不结束循环
>>> a=10
>>> while a>4:
a-=1
if a == 5:
break
print(a)
while语句非常灵活,可用于在条件为真时反复执行代码块。
2. for循环
为序列(或其他可迭代对象)中每个元素执行代码块。
基本上,可迭代对象是可使用for循环进行遍历的对象。因为代码更简洁,代码量也少。所以,能用for循环,就不要用while循环。
在Python中,for关键字叫做for循环,for循环可以遍历任何序列的项目,如一个列表或字符串。语法格式如下:
for iterating_var in sequence:
statements(s)
sequence
是任意序列,iterating_var
是序列中需要遍历的元素。statements
是待执行的语句块。
>>> num = [1,3,4,5,7,8]
>>> for n in num:
print(n)
鉴于迭代(也就是遍历)特定范围内的数是一种常见的任务,Python提供了一个创建范围的内置函数range()
。它的范围是左闭右开的。
>>> range(10) # range(0, 10)
>>> range(0,10) # range(0, 10)
>>> range(0,10,2) # range(0, 10, 2)
>>> list(range(0,10)) #[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(range(0,10,2)) #[0, 2, 4, 6, 8]
范围类似于切片。它们包含起始位置,但不包含结束位置。
>>> for i in range(0,6): #in后面是可迭代对象
print(num[i])
相同点:都是具有循环功能。
不同点:while 需要写终止条件。
3. 迭代字典
要遍历字典的所有关键字,可像遍历序列那样使用普通的for语句。
>>> d = {1: 'a', 2: 'b', 3: 'c'}
>>> for key in d:
print(key, '--', d[key])
#1 -- a
#2 -- b
#3 – c
for循环的优点之一是,可在其中使用序列解包。
>>> for key, value in d.items():
print(key, 'corresponds to', value)
#1 corresponds to a
#2 corresponds to b
#3 corresponds to c
注意:字典元素的排列顺序是不确定的。换而言之,迭代字典的键或值时,一定会处理所有的键或值,但不知道处理的顺序。如果顺序很重要,可将键或值存储在一个列表中并对列表排序,再进行迭代。要让映射记住其项的插入顺序,可使用模块collections
中的OrderedDict
类。
4. 一些迭代工具
Python提供了多个可帮助迭代序列(或其他可迭代对象)的函数,其中一些位于第10张的模块itertools
中,但还有一些内置函数使用起来也很方便。
4.1 并行迭代
当你想同时迭代两个序列时,你可以这样:
>>> names = ['a', 'b', 'c', 'd']
>>> ages = [1, 3, 5, 45]
>>> for i in range(len(names)):
print(names[i], 'is', ages[i], 'years old .')
#a is 1 years old .
#b is 3 years old .
#c is 5 years old .
#d is 45 years old .
i
是用作循环索引的变量的标准名称。
一个很有用的并行迭代工具是内置函数zip
,它将两个序列"缝合"起来,并返回一个由元组组成的序列。返回值是一个适合迭代的对象,要查看其内容,可使用list将其转换为列表。
>>> list(zip(names,ages))
#[('a', 1), ('b', 3), ('c', 5), ('d', 45)]
缝合后,可在循环中将元组解包。
>>> for name, age in zip(names,ages):
print(name, 'is', age, 'years old .')
函数zip
可用于缝合任意数量的序列。需要之处的是,当序列的长度不同时,函数zip将在最短的序列用完后停止缝合。
>>> list(zip(range(5), range(100000)))
#[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]
4.2 迭代时获取索引
在有些情况下,你需要在迭代对象序列的同时获取当前对象的索引。例如,你可能想替换一个字符串列表中所有包含子串xxx
的字符串。
内置函数:enumerate
,用于在迭代对象序列的同时获取当前对象的索引。
>>> strings = ['ab', 'cd', 'ef']
>>> for index, string in enumerate(strings):
if 'ab' in string:
strings[index] = 'AB'
print(strings) #['AB', 'cd', 'ef']
这个函数让你能够迭代索引-值对,其中的索引是自动提供的。
4.3反向迭代和排序后再迭代
两个很有用的函数:reversed
和sorted
。,可用于任何序列或可迭代的对象,且不就地修改对象,而是返回反转和排序后的版本。
>>> sorted([5,2,1,4,8,6]) #[1, 2, 4, 5, 6, 8]
>>> sorted('Hello!') #['!', 'H', 'e', 'l', 'l', 'o']
>>> list(reversed('Hello!')) #['!', 'o', 'l', 'l', 'e', 'H']
>>> ''.join(reversed("Hello!")) #'!olleH'
请注意,sorted
返回一个列表,而reversed
像zip那样返回一个更神秘的可迭代对象。
>>> reversed('gege') #<reversed object at 0x000001D57476D550>
提示:要将字母表排序,可先转换为小写。为此,可将sort
或sorted
的参数设置为str.lower
。
>>> sorted('aBc', key=str.lower) # ['a', 'B', 'c']
5. 跳出循环
通常,循环会不断地执行代码块,知道条件为假或使用完序列中的所有元素。但在有些情况下,你可能想中断循环、开始新迭代(进入下一轮代码块执行流程)或直接结束循环。
5.1 break
要结束(跳出)循环,可使用break
,即使循环条件中没有False条件或序列还没有遍历完,也会停止执行循环语句。
这里要找出小于100的最大平方值,从100开始向下迭代。找到一个平方值后,无需再迭代,因此直接跳出循环。
>>> from math import sqrt#求小于100的最大平方数
>>> for i in range(99, 0 , -1):
n = sqrt(i)
if n==int(n):
print(int(n))
break;
range
传递了第三个参数—步长,即序列中相邻数的差。通过将步长设置为负数,可让range
向下迭代,也可以跳过一些数。
5.2 continue
用于结束当前迭代,并跳到下一次迭代开头。用的也不如break
多。
>>> for letter in 'hello':
if letter == 'l':
continue
print('当前字母:',letter)
#当前字母: h
#当前字母: e
#当前字母: o
只是跳过一次循环,不会跳出整个循环。
5.3 while True/break成例
先让其“死循环”,再利用break
,可以实现当满足一定条件时直接退出循环。
例如,假设你要在用户根据提示输入单词时执行某种操作,并在用户没有提供单词时循环。为此,一种方法如下:
>>> word = 'dummy'
>>> while word:
word = unput('Please enter a word:')
# 使用这个单词做一些事情
print('The word was', word)
如你所见,这些代码有点难看。为进入循环,你需要将一个哑值(未用的值)赋给word
。像这样的哑值通常昭示着你的做法不太对。下面来消除这个哑值。
>>> word = input('Please enter a word:')
>>> while word:
# 使用这个单词做一些事情
print('The word was', word)
word = input('Please enter a word:')
哑值消除了,但包含重复的代码(这样也不好):需要在这两个地方使用相同的赋值语句并调用input
。如何避免这样的重复呢?可使用成例while True/break
。
>>> while True;
word = input('Please enter a word:')
if not word: break
# 使用这个单词做些事情
print('The word was', word)
while True
导致循环永不结束,但你将条件放在了循环体内的一条if语句中,而这条if语句将在条件满足时调用break。这说明并非只能像常规while循环那样在循环开头结束循环,而是可在循环体的任何地方结束循环。if/while行将整个循环分成两个部分:第一部分负责设置(如果使用常规while循环,将重复这部分),第二部分在循环条件为真时使用第一部分初始化的数据。
6. 循环中的else子句
这种语法要解决一种很常见的问题:循环可能是break
结束或者循环正常结束, 而python则很神奇的使用了else
子句来实现这一功能。
>>> from math import sqrt
>>> for i in range(99, 0 , -1):
n = sqrt(i)
if n == int(n):
print(int(n))
break;
>>> else:
print("Don't find it")
无论是在for循环还是while循环中,都可使用continue
、break
和else
子句。
六、简单推导
列表推导是一种从其他列表创建列表的方式,类似于数学中的集合推导。列表推导的工作原理非常简单,有点类似于for循环。
>>> [x * x for x in range(10)] #[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
这个列表由range(10)
内每个值的平方组成。
如果y能被3整除,y % 3
将返回0。可在列表推导中添加一条if语句。
>>> [x * x for x in range(10) if x % 3 == 0] #[0, 9, 36, 81]
还可以添加更多的for部分
>>> [(x,y) for x in range(3) for y in range(3)]
#[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
男孩/女孩首字母配对。
>>> girls = ['alice','bernice','clarice']
>>> boys = ['chris','arnold','bob']
>>> letterGirls = {}
>>> for girl in girls:
letterGirls.setdefault(girl[0],[]).append(girl)
#{}.setdefault()返回其值[]。
>>> print([b+'+'+g for b in boys for g in letterGirls[b[0]]])
#['chris+clarice', 'arnold+alice', 'bob+bernice']
这个程序创建一个名为letterGirls
的字典,其中每项的键都是一个字母,而值为以这个字母打头的女孩名字组成的列表。创建这个字典后,列表推导遍历所有的男孩,并查找名字首字母与当前男孩相同的所有女孩。这样,这个列表推导就无需尝试所有的男孩和女孩组合并检查他们的名字首字母是否相同了。
使用圆括号代替方括号并不能实现元组推导,而是将创建生成器,详细信息在第9章。然而,可使用花括号来执行字典推导。
>>> squares = {i:"{} squared is {}".format(i, i**2) for i in range(4)}
>>> squares[3] # '3 squared is 9'
>>> squares
#{0: '0 squared is 0', 1: '1 squared is 1', 2: '2 squared is 4', 3: '3 squared is 9'}
在列表推导中,for前面只有一个表达式,而在字典推导中,for前面有两个用冒号分隔的表达式。这两个表达式分别为键及其对应的值。
七、三人行
在本章结束前,大致介绍下另外三条语句:pass
、del
和exec
。
1. 什么都不做
pass
,直接略过,看着没什么用处,但有时当你某个部分的代码暂时没有写出来,既可以用pass
来占位,来保持程序结构的完整性。
>>> a, b = 1, 2
>>> if a>b:
print(a)
elif a == b:
print('相等')
else:
pass # 占位
注意:也可不使用注释和pass
语句,而是插入一个字符串。这种做法尤其适用于未完成的函数和类,因为这种字符串将充当文档字符串(将在第6章介绍)。
2. 使用del删除
Python中,当某个值不再使用(没有任何变量或数据结构成员指向它),系统会将其自动回收。
>>> robin = {'age': 52}
>>> robin = None
>>> robin #None
当将robin
设置为None
之后,这个字典就漂浮在计算机内存中,没有任何名称与之相关联,再也无法获取或使用它了。因此,Python解释器直接将其删除。这被称为垃圾收集。
另一种方法是使用del
语句。这不仅会删除到对象的引用,还会删除名称本身。但删除的是变量(相当于变量和值之间的联系被切断,但值可能还存在)
>>> x = [1, 3, 4]
>>> y = x
>>> del x
>>> y #[1, 3, 4]
事实上,在Python中,根本就没有办法删除值,而且你也不需要这么做,因为对于你不再使用的值,Python解释器会立即将其删除。
3. 使用exec和eval执行字符串及计算其结果
如果你有一天突发奇想,想执行一个字符串或者计算式(前提是他们能够执行),你可以考虑使用exec
和eval
。
警告:本节介绍如何执行存储在字符串中的Python代码,这样做可能带来严重的安全隐含。如果将部分内容由用户提供的字符串作为代码执行,将无法控制代码的行为。在网络应用程序,如第15章将介绍的通用网关接口(CGI)脚本中,这样做尤其危险。
3.1 exec
函数exec
将字符串作为代码执行。
>>> str = 'print("hello ")'
>>> exec(str) #hello
然而,调用函数exec
时只给它提供一个参数绝非好事。如果你的变量/函数名 与字符串中有重复,那就麻烦了。在大多数情况下,还应向它传递一个命名空间—用于放置变量的地方;否则代码将污染你的命名空间,即修改你的变量。
>>> str = 'sqrt = 1'
>>> from math import sqrt
>>> sqrt(4) #2.0
>>> exec(str)
>>> sqrt(4)
Traceback (most recent call last):
File "<pyshell#14>", line 1, in <module>
sqrt(4)
TypeError: 'int' object is not callable
函数exec
主要用于动态地创建代码字符串。如果这种字符串来自其他地方(可能是用户),就几乎无法确定它将包含什么内容。因此为了安全起见,要提供一个字典以充当命名空间。
注意:命名空间(作用域)是个重要的概念,将在下一章深入讨论,但就目前而言,你可将命名空间视为放置变量的地方,类似于一个看不见的字典。因此,当你执行赋值语句x=1
时,将在当前命名空间存储键x和1。当前命名空间通常是全局命名空间(到目前为止,我们使用的大都是全局命名空间),但并非必然如此。
为此,你添加第二个参数—字典,用作代码字符串的命名空间。实际上,可向exec
提供两个命名空间:一个全局的和一个局部的。提供的全局命名空间必须是字典,而提供的局部命名空间可以是任何映射。这一点也适用于eval
。
>>> from math import sqrt
>>> scope = {}
>>> exec('sqrt = 1',scope)
>>> sqrt(4) #2.0
>>> scope['sqrt'] #1
如你所见,可能带来破坏的代码并非覆盖函数sqrt
。函数sqrt
该怎样还怎样,而通过exec
执行赋值语句创建的变量位于scope
中。
请注意:如果你尝试将scope
打印出来,将发现它包含很多内容,这是因为自动在其中添加了包含所有内置函数和值的字典__builtins__
。
>>> len(scope) #2
>>> scope.keys() #dict_keys(['__builtins__', 'sqrt'])
3.2 eval
exec
执行一系列Python语句,而eval
计算用字符串表示的Python表达式的值,返回结果(exec
什么都不返回,因为它本身是条语句)。
>>> eval(input('input :'))
#input :2*4 + 5
#13
与exec
一样,也可向eval
提供一个命名空间,虽然表达式通常不会像语句那样给变量重新赋值。
警告:虽然表达式通常不会给变量重新赋值,但绝对能够这样做,如调用给能量重新赋值的函数。因此,将eval
用于不可信任的代码并不比使用exec
安全。当前,在Python个执行不可信任的代码时,没有安全的办法。一种替代解决方案是使用Jython(参见第17章)等Python实现,以使用Java沙箱等原生机制。
3.3 浅谈作用域
向exec
或eval
提供命名空间时,可在使用这个命名空间前在其中添加一些值。
>>> scope = {}
>>> scope['x'] = 2
>>> scope['y'] = 3
>>> eval('x * y', scope) #6
同样,同一个命名空间可用于多次调用exec
或eval
。
>>> scope = {}
>>> exec('x = 2', scope)
>>> eval('x * x', scope) #4