Python基础进阶

  • Post author:
  • Post category:python




4.字符串与正则表达式(难)

Python2.x对中文支持不够,因此常常需要在不同的编码之间互相转换;Python3.x中则完全支持中文,无论是一个数字、英文字母、还是一个汉字,都按一个字符对待处理。甚至在Python3.x中可以使用中文作为变量名。



4.1.字符串

在Python中,字符串

属于不可变序列类型



使用单引号、双引号、三单引号或三双引号作为界定符

,并且

不同界定符之间可以互相嵌套

除了

支持序列通用方法

(包括

比较、计算长度、元素访问、分片等操作

)以外,字符串类型

还支持一些特有的操作方法。例如:

格式化操作、字符串查找、字符串替换等


但由于字符串属于

不可变序列

,不能对字符串对象进行元素增加、修改与删除等操作。

字符串对象提供的

replace()和translate()方法

并不是对原字符串直接修改替换,而是

返回一个修改替换后的结果字符串


Python支持

字符串驻留机制

,即:

对于短字符串,将其赋值给多个不同的对象时,

内存中只有一个副本



多个对象共享其副本

。这一点不适用于长字符串,即长字符串不遵循驻留机制

,下面代码演示了短字符串和长字符串在这方面区别。

a='1234'
b='1234'
id(a)==id(b)
True
a='1234'*50
b='1234'*50
id(a)==id(b)
False

在Python2.x中,字符串有str和unicode两种,其基类都是basestring,在Python3.x中合二为一了,只提供str类型。在Python3.x中,程序源文件默认为UTF-8编码,全面支持中文,字符串对象不再支持decode()方法。



4.2.字符串格式化(格式字符、format)

如果

需要将其他类型数据转换为字符串或另一种数字格式,或者嵌入其他字符或模板中再进行输出,就需要用到字符串格式化

‘% [-] [+] [0] [m] [.n] 格式字符 ‘% x

格式标志,表示格式开始

指定左对齐输出

对正数加正号

指定空位填0

指定最小宽度

指定精度

指定类型,如下表格

格式运算符

待转换的表达式

格式字符 说明 格式字符 说明
%s 字符串(采用str()的显示) %x 十六进制整数
%r 字符串(采用repr()的显示) %e 指数(基底写为e)
%c 单个字符 %E 指数(基底写为E)
%% 字符”%“ %f、%F 浮点数
%d 十进制整数 %g 指数(e)或浮点数(根据显示长度)
%i 十进制整数 %G 指数(E)或浮点数(根据显示长度)
%o 八进制整数

下面的代码简单演示了字符串格式化的用法:

x=1235
so="%o" %x
so
"2323"		#八进制整数
sh="%x" %x
sh
"4d3"		#十六进制整数
se="%e" %x
se
"1.235000e+03"	#指数
chr(ord("3")+1	#chr(i)函数的作用是 返回整数 i 所对应的 Unicode 字符,ord()函数作用则相反。
"4"
"%s" %65		#类似于str(),字符串。
"65"
"%s" %65333
"65333"
'%d,%c'%(65,65)		#使用元组对字符串进行格式化,按位置对应
'65,A'
"%d"%"555"		#试图将字符串转换为整数进行输出,抛出异常
TypeError:
int('555')		#可以使用int()函数将合法的数字字符串转换为整数
555
'%s'%[1,2,3]
'[1,2,3]'
str((1,2,3))	#可以使用str()函数将任意类型数据转换为字符串
'(1,2,3)'
str([1,2,3])
'[1,2,3]'

目前Python社区更推荐使用

format()方法进行格式化

,该方法更加灵活,

不仅可以

使用位置进行格式化

,还支持

使用与位置无关的参数名字来进行格式化

,并且

支持序列解包格式化字符串


,为程序员提供了非常大的方便。

print("The number {0:,} in hex is: {0:#x},the number {1} in oct is {1:#o}".format(5555,55))
The number 5555 in hex is:0x15b3,the number 55 in oct is 0o67
print("The number {1:,} in hex is:{1:#x},the number {0} in oct is {0:#o}".format(5555,55))
The number 55 in hex is:0x37,the number 5555 in oct is 0o12663
print("my name is {name},my age is {age},and my QQ is {qq}".format(name="DongFuguo",qq="306467355",age=37))
my name is DongFuguo,my age is 37,and my QQ is 306467355
position=(5,8,13)
print("X:{0[0]};Y:{0[1]};Z:{0[2]}".format(position))
X:5;Y:8;Z:13
weather=[("Monday","rain"),("Tuesday","sunny"),("Wednesday","sunny"),("Thursday","rain"),("Friday","Cloudy")]
formatter="Weather of '{0[0]}' is '{0[1]}'".format
>>>for item in map(formatter,weather):
	print(item)
#或者可以写为
>>>for item in weather:
    print(formatter(item))
Weather of 'Monday' is 'rain'
Weather of 'Tuesday' is 'sunny'
Weather of 'Wednesday' is 'sunny'
Weather of 'Thursday' is 'rain'
Weather of 'Friday' is 'Cloudy'



4.3.map()方法,Python 3.x 返回迭代器

map(function, iterable, ...)
>>> def square(x) :         # 计算平方数
...     return x ** 2
...
>>> map(square, [1,2,3,4,5])    # 计算列表各个元素的平方
<map object at 0x100d3d550>     # 返回迭代器
>>> list(map(square, [1,2,3,4,5]))   # 使用 list() 转换为列表
[1, 4, 9, 16, 25]
>>> list(map(lambda x: x ** 2, [1, 2, 3, 4, 5]))   # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]



4.3.字符串常用方法

字符串是非常重要的数据类型,Python提供了

大量的函数

支持字符串操作。

本节通过大量示例来演示部分函数的用法,可以

使用dir(” “)查看所有字符串操作函数列表

,并使用**内置函数help()**查看每个函数的帮助。

因为字符串也是Python序列的一种,除了本节介绍的字符串处理函数,

很多Python内置函数也支持对字符串的操作

,例如用来计算序列长度的len()方法,求最大值的max()方法等等。

####1、find()、rfind()、index()、rindex()、count()方法实现查询



find()和rfind()方法

分别用来

查找一个字符串在另一个字符串指定范围内(

默认是整个字符串

)中

首次





最后一次

出现的位置

,如果

不存在则返回-1



index()和rindex()方法

用来

返回一个字符串在另一个字符串指定范围中

首次





最后一次

出现的位置

,如果

不存在则抛出异常



count()方法

用来

返回一个字符串在另一个字符串中出现的次数

>>> s="apple,peach,banana,peach,pear"
>>> s.find("peach")				#返回第一次出现的位置
6
>>> s.find("peach",7)			 #从指定位置开始查找
19
>>> s.find("peach",7,20)		 #从指定范围中查找
-1
>>> s.rfind('p')				 #从字符串尾部向前查找
25
>>> s.index('p')				 #返回首次出现的位置
1
>>> s.index('pe')
6
>>> s.index('pear')
25
>>> s.index('ppp')				 #指定子字符串不存在时抛出异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: substring not found
>>> s.count('p')				 #统计子字符串出现次数
5
>>> s.count('pp')
1
>>> s.count('ppp')
0

####2、split()、rsplit()、partition()、rpartition()方法实现分隔字符串



split()和rsplit()方法

分别用来



指定字符

为分隔符

,从字符串左端和右端开始将其分割成多个字符串,并

返回包含分割结果的

列表




partition()和rpartition()

用来

以指定字符串为分割符将原字符串分割为3部分

,即

分隔符前的字符串、分隔符字符串、分隔符后的字符串



如果指定的分隔符不在原字符串中,则返回原字符串和两个空字符串

>>> s="apple,peach,banana,pear"
>>> li=s.split(",")			#使用逗号分割
>>> li
['apple', 'peach', 'banana', 'pear']
>>> s.partition(',')
('apple', ',', 'peach,banana,pear')
>>> s.rpartition(',')
('apple,peach,banana', ',', 'pear')
>>> s.rpartition('banana')
('apple,peach,', 'banana', ',pear')
>>> s="2014-10-31"
>>> t=s.split("-")
>>> t
['2014', '10', '31']
>>> list(map(int,t))
[2014, 10, 31]


对于split()和rsplit()方法

,如果不指定分隔符,则字符串中的

任何空白符号

(包括空格、换行符\n、制表符\t等)都将被认为是

分隔符



返回包含最终分割结果的列表

>>> s='\n\nhello\t\t world \n\n\n My name\t is Dong   '
>>> s.split()
['hello', 'world', 'My', 'name', 'is', 'Dong']


split()和rsplit()方法还允许

指定最大分割次数


,例如:

>>> s='\n\nhello\t\t world \n\n\n My name\t is Dong   '
>>> s.split(None,2)
['hello', 'world', 'My name\t is Dong   ']
>>> s.rsplit(None,2)
['\n\nhello\t\t world \n\n\n My name', 'is', 'Dong']
>>> s.rsplit(None,6)
['hello', 'world', 'My', 'name', 'is', 'Dong']

####3、join()方法实现连接字符串

与split()相反,

join()方法用来将列表中多个字符串进行连接



并在相邻两个字符串之间插入指定字符

>>> li=["apple","peach","banana","pear"]
>>> sep=","
>>> s=sep.join(li)
>>> s
'apple,peach,banana,pear'

使用运算符“+”也可以连接字符串,但效率较低,应

优先使用join()方法



4、lower()、upper()、capitalize()、title()、swapcase()方法实现字母大小写互换

这几个方法分别用来

将字符串转换为小写



大写字符串



将字符串首字母变为大写



将每个单词的首字母变为大写以及大小写互换

>>> s="What is Your Name?"
>>> s2=s.lower()		#将字符串转换为小写
>>> s2
'what is your name?'
>>> s.upper()			#将字符串转换为大写
'WHAT IS YOUR NAME?'
>>> s2.capitalize()		 #将字符串首字母变为大写
'What is your name?'
>>> s.title()			 #将每个单词的首字母变为大写
'What Is Your Name?'
>>> s.swapcase()		 #大小写互换
'wHAT IS yOUR nAME?'


5、replace()方法用来替换字符串中指定字符或子字符串的所有重复出现,每次替换一个字符或子字符串
>>> s="中国,中国"
>>> print(s)
中国,中国
>>> s2=s.replace("中国","中华人民共和国")
>>> print(s2)
中华人民共和国,中华人民共和国


6、maketrans()用来生成字符映射表、translate()方法则按映射表关系转换字符串并替换其中字符

使用这两种方法的组合

可以同时处理多个不同的字符



replace()方法则无法满足这一要求

。当然也可以

定义自己的字符映射表,然后用来对字符串进行

加密


#将字符"abcdef123"一一对应地转换为"uvwxyz@#$"
>>> table=''.maketrans("abcdef123","uvwxyz@#$")
>>> s="Python is a greate programming language.I like it!"
>>> s.translate(table)
'Python is u gryuty progrumming lunguugy.I liky it!'


7、strip()、rstrip()、Istrip()方法用来删除两端、右段或左端的空白字符或连续的指定字符
>>> s="abc   "
>>> s2=s.strip()		#删除空白字符
>>> s2
'abc'
>>> '\n\nhello world    \n\n'.strip()	#删除空白字符
'hello world'
>>> "aaaassddf".strip("a")		#删除指定字符
'ssddf'
>>> "aaaassddf".strip("af")		#删除指定字符
'ssdd'
>>> "aaaassddfaaa".rstrip("a")		#删除字符串右端指定字符
'aaaassddf'
>>> "aaaassddfaaa".lstrip("a")		#删除字符串左端指定字符
'ssddfaaa'


8、eval()内置方法尝试把任意字符串转化为Python表达式并求值
>>> eval("3+4")
7
>>> a=3
>>> b=5
>>> eval('a+b')
8
>>> import math
>>> eval('help(math.sqrt)')
Help on build-in function sqrt in module math:
sqrt(...)
	sqrt(x)
Return the square root of x.
>>> eval('math.sqrt(3)')
1.7320508075688772
>>> eval('aa')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'aa' is not defined. Did you mean: 'a'?

使用

eval()

时要注意的一个问题是,它

可以计算任意合法表达式的值,如果用户巧妙地构造输入的字符串,可以执行任意外部程序

。例如:下面的代码运行后可以启动记事本程序:

>>> a=input("please input:")
please input:__import__('os').startfile(r'D:\全球学术快报 Setup 0.1.16.exe')
>>> eval(a)

再执行下面的代码试试,然后看看当前工作目录中多了什么。当然还可以调用命令来删除这个文件夹或其他文件,或者精心构造其他字符串来达到特殊目的。

eval("__import__('os').system('md testtest')")

在当前工作目录中多出来了名为“testtest”的文件夹。



9、关键字in和not in来判断一个字符串是否出现在另一个字符串中

与列表、元组、字典、集合一样,也可以

使用关键字in和not in来判断一个字符串是否出现在另一个字符串中

,返回True或False。

>>> "a" in "abcde"
True
>>> 'ab' in 'abcde'
True
>>> "j" in "abcde"
False


10、startswith()、endswith()方法用来判断字符串是否以指定字符串开始或结束

这两个方法可以

接收两个整数参数来限定字符串的检测范围

>>> s='Beautiful is better than ugly.'
>>> s.startswith('Be')
True
>>> s.startswith('Be',5)
False
>>> s.startswith('Be',0,5)
True

另外这两个方法还可以

接受一个字符串元组作为参数来表示前缀或者后缀

,例如:下面的代码可以

列出指定文件夹下所有扩展名为bmp、jpg或exe的文件

>>> [filename for filename in os.listdir(r'D:\\') if filename.endswith(('.bmp','.jpg','.exe'))]
['adguardInstaller.exe', 'Battle.net-Setup-CN.exe', 'CAJViewer 7.3.149.self.exe', 'cloudmusicsetup2.7.4.198374.exe', 'DeliOCRSetup.exe', 'DeskGo_2_
9_1051_127_lite.exe', 'eclipse-inst-jre-win64.exe', 'eclipse-inst-win64.exe', 'Evernote_6.22.64.4484.exe', 'Git-2.34.1-64-bit.exe', 'ideaIU-2020.3.
4.exe', 'iZipToolInstaller.1.2.0.0_baidu_search_10102.exe', 'jdk-8u311-windows-x64.exe', 'LeiGodSetup.7.2.0.0.exe', 'libmmd.dll_156_50115.exe', 'ma
rkdownpad2-setup.exe', 'npp.8.1.9.Installer.exe', 'Postman-win64-9.12.2-Setup.exe', 'pycharm-professional-2020.3.5.exe', 'pycharm-professional-2021
.3.1.exe', 'pycharm-professional-2021.3.exe', 'python-3.10.1-amd64.exe', 'standardreader.exe', 'StarUML Setup 4.0.1.exe', 'SteamSetup.exe', 'STEAM_
bdygh01_1.0.0.1476@1fzozw@download.exe', 'TencentMeeting_1410000356_2.6.0.493.publish.exe', 'typora-setup-x64.exe', 'vs_Community.exe', 'vs_communi
ty__1144856713.1625841887 (1).exe', 'vs_community__1144856713.1625841887.exe', 'W.P.S.10132.12012.2019.exe', 'WeChatSetup.exe', 'WeGameMiniLoader.3
.25.1.8081.gw.exe', 'WeGameMiniLoader.std.2.07.29.1736.exe', 'YoudaoDictSetup.exe', 'ZhiyunT_setup7.7.2F.exe', '全球学术快报 Setup 0.1.16.exe', '快
剪辑.exe', '火绒安全-5.0.25.11.exe']


11、isalnum()、isalpha()、isdigit()、isspace()、isupper()、islower()方法用来测试字符串是否为数字或字母、是否为字母、是否为数字字符、是否为空白字符、是否为大写字母以及是否为小写字母
>>> '1234abcd'.isalnum()	#是否为数字或字母
True
>>> '1234abcd'.isalpha()	#是否为字母
False
>>> '1234abcd'.isdigit()	#是否为数字字符
False
>>> 'abcd'.isalpha()		
True
>>> '1234.0'.isdigit()		
False
>>> '1234'.isdigit()
True
>>> ' '.isspace()		#是否为空白字符
True
>>> 'abcd'.islower()	#是否为小写字母
True
>>> 'ASD'.isupper()		#是否为大写字母
True


12、center()、ljust()、rjust()方法返回指定宽度的新字符串

原字符串居中、左对齐或右对齐出现在新字符串中,如果指定的宽度大于字符串长度,则使用指定的字符(默认为空格)填充。

>>> 'Hello world!'.center(20)	#指定宽度居中
'    Hello world!    '
>>> 'Hello world!'.center(20,'=')	#指定用=填充
'====Hello world!===='
>>> 'Hello world!'.ljust(20,'=')	#指定宽度左对齐,用=填充
'Hello world!========'
>>> 'Hello world!'.rjust(20,'=')	#指定宽度右对齐,用=填充
'========Hello world!'



4.3.字符串常量

在string模块中定义了多个字符串常量,包括数字字符、标点符号、英文字母、大写字母、小写字母等,用户可以直接使用这些常量。

>>> import string
>>> string.digits			#数字字符常量
'0123456789'
>>> string.punctuation		#标点符号常量
'!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
>>> string.letters	#python2.x中使用错误
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'string' has no attribute 'letters'
>>> string.ascii_letters		#英文字母常量
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> string.printable			#可打印字符
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
>>> string.ascii_lowercase		#小写字母
'abcdefghijklmnopqrstuvwxyz'
>>> string.ascii_uppercase
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'	#大写字母
>>> string.whitespace		#空白符号,包括空格、制表、换行、回车、进纸和纵向制表符。
' \t\n\r\x0b\x0c'

下面代码演示了8位长度随机密码生成算法的原理

>>> import string
>>> x=string.digits+string.ascii_letters+string.punctuation
>>> import random
>>> ''.join([random.choice(x) for i in range(8)])
'X:[7M6Dq'
>>> ''.join(random.sample(x,8))
'f<H(t8Y@'

==注意:==random是与随机数有关的Python标准库,

除了用于从序列中任意选择一个元素的函数choice()

,random还提供了

用于生成指定二进制位数的随机整数的函数getrandbits()



生成指定范围内随机数的函数randrange()和randint()



列表原地乱序函数shuffle()

、从序列中

随机选择指定数量不重复元素的函数sample()



返回[0,1]区间内符合beta分布的随机数函数betavariate()

、符合gamma分布的随机数函数gammavariate()、符合gauss分布的随机数函数gauss()等,同时还提供了SystemRandom类支持生成加密级别要求的不可再现伪随机序列。

>>> import random
>>> random.getrandbits(17)
116252
>>> random.getrandbits(17)
40423
>>> x=list(range(20))
>>> random.shuffle(x)
>>> x
[18, 7, 13, 16, 19, 0, 5, 4, 1, 6, 8, 11, 3, 9, 12, 2, 10, 15, 14, 17]
>>> random.sample(x,3)
[1, 9, 13]

下面代码使用Python标准库random中的方法模拟了发红包算法。

import random

def hongbao(total,num):
    #total表示拟发红包总金额
    #num表示拟发红包数量
    each=[]
    #已发红包总金额
    already=0
    for i in range(1,num):
        #为当前抢红包的人随机分配金额
        #至少给剩下的人每人留一分钱
        t=random.randint(1,(total-already)-(num-i))
        each.append(t)
        already=already+t
    #剩余所有钱发给最后一个人
    each.append(total-already)
    return each

if __name__=='__main__':
    total=6
    num=5
    #模拟30次
    for i in range(30):
        each=hongbao(total,num)
        print(each)



4.4.可变字符串*

在Python中,

字符串属于不可变对象

,不支持原地修改,如果需要修改其中的值,只能重新创建一个新的字符串对象。

然而,如果确实需要一个支持修改的Unicode数据对象,

可以使用io.StringIO对象或array模块


>>> import io
>>> s="Hello, world"
>>> sio=io.StringIO(s)	#内存中文本流也可以作为 StringIO 对象使用
>>> sio.getvalue()		#返回包含整个缓冲区内容的 bytes。
'Hello, world'
>>> sio.seek(7)		#将流位置修改到给定的字节 offset
7
>>> sio.write("there!")		#将给定的bytes-like object b写入到下层的原始流,并返回所写入的字节数
6
>>> sio.getvalue()
'Hello, there!'
>>> import array
>>> a=array.array('u',s)	#'u'为类型码,一个包含由 typecode 限制类型的条目的新数组,并由可选的 initializer 值进行初始化
>>> print(a)
array('u', 'Hello, world')
>>> a[0]='y'
>>> print(a)
array('u', 'yello, world')
>>> a.tounicode()		#将数组转换为一个 Unicode 字符串。 数组必须是类型为 'u' 的数组
'yello, world'


seek

(

offset

,

whence=SEEK_SET

)

将流位置修改到给定的字节

offset



offset

将相对于由

whence

指定的位置进行解析。

whence

的默认值为

SEEK_SET



whence

的可用值有:


  • SEEK_SET



    0

    – 流的开头(默认值);

    offset

    应为零或正值

  • SEEK_CUR

    or

    1

    – 当前流位置;

    offset

    可以为负值

  • SEEK_END

    or

    2

    – 流的末尾;

    offset

    通常为负值



***4.5.正则表达式

正则表达式是

字符串处理

的有力工具和技术,正则表达式使用

预定义的特定模式去匹配一类具有共同特征的字符串

,主要

用于字符串处理,可以快速、准确地完成复杂的查找、替换等处理要求

Python中,re模块提供了正则表达式操作所需要的功能。

正则表达式

由元字符及其不同组合来构成

,通过

巧妙地构造正则表达式可以匹配任意字符串

,并完成复杂的字符串处理任务。

如果以”“开头的元字符与转义字符相同,则需要使用”\“或者原始字符串,在字符串前加上字符”r”或”R”。原始字符串可以减少用户的输入,主要用于正则表达式和文件路径字符串,如果字符串以一个斜线”“结束,则需要多写一个斜线,以”\”结束。具体应用时,可以

单独使用某种类型的元字符,但处理复杂字符串时,经常需要将多个正则表达式元字符进行组合

,下面给出了几个简单的示例:

(1)最简单的正则表达式是普通字符串,可以匹配自身。

(2)’[pjc]ython’可以匹配’python’、‘jython’、‘cython’。

(3)’[a-zA-Z0-9]’可以匹配一个任意大小写字母或数字。

(4)’[^abc]‘可以一个匹配任意除’a’、‘b’、’c’之外的字符。

(5)’python|perl’或’p(ython|erl)‘都可以匹配’python’或’perl’

(6)子模式后面加上问号表示可选。r’(http://)?(www\.)?python\.org’只能匹配’http://www.python.org’、‘http://python.org’、‘www.python.org’和’python.org’。

(7)’^http’只能匹配所有以’http’开头的字符串。

(8)’(pattern)’*:允许模式重复0次或多次。

(9)’(pattern)+’:允许模式重复1次或多次。

(10)’(pattern){m,n}’:允许模式重复m~n次。

(11)’(a|b) * c’:匹配多个(包括0个)a或b,后面紧跟一个字母c。

(12)‘ab{1,}’:等价于’ab+’,匹配以字母a开头后面带1个至多个字母b的字符串。

(13)’


1


{1} ([a-zA-Z0-9._]) {4,19} $’:匹配长度为5~20的字符串,必须以字母开头、可带数字、”_”、”.”的字符串。

(14)’^(\w) {6,20} $ ‘:匹配长度为6~20的字符串,可以包含字母、数字、下划线。

(15)’^\d{1,3}\. \d{1,3}\. \d{1,3}\. \d{1,3} $’:检查给定字符串是否为合法IP地址。

(16)’^(13[4-9]\d{8}) | (15[01289]\d{8}) $’:检查给定字符串是否为移动手机号码。

(17)’


2


+$’:检查给定字符串是否只包含英文字母大小写。

(18)’^\w + @(\w+\.) + \w+ $’:检查给定字符串是否为合法电子邮件地址。

(19)’^(\-) ? \d+ (\. \d{1,2}) ? $’:检查给定字符串是否为最多带有2位小数的正数或负数。

(20)’[\u4e00-\u9fa5]’:匹配给定字符串中所有汉字。

(21)’^\d{18} | \d{15} $’:检查给定字符串是否为合法身份证格式。

(22)’\d{4}-\d{1,2}-\d{1,2}’:匹配指定格式的日期,例如2016-1-31。

(23)’^ (? = . *[a-z]) (? =. *[A-Z]) (? =. *\d) (? =. * [,._]).{8,} $’:检查给定字符串是否为强密码,必须同时包含英语大写字母、英语小写字母、数字或特殊符号(如英文逗号、英语句号、下划线),并且长度必须至少8位。

(24)”(?!. * [’”/;=%?]).+”:如果给定字符串中包含’、”、/,;、=、%、?则匹配失败,关于子模式语法。

(25)’( . )\\1+’:匹配任意字符的一次或多次重复出现。

在具体构造正则表达式时,要注意到可能会发生的错误,尤其是涉及特殊字符的时候。作用是用来匹配Python程序中的运算符,但是因为

有些运算符与正则表达式的元字符相同而引起歧义,如果处理不当则会造成理解错误,需要进行必要的转义处理



4.6.re模块主要方法

在Python中,主要使用

re模块

来实现正则表达式的操作。该模块的

常用方法

如表所示,具体使用时,既可以

直接使用re模块的方法进行字符串处理

,也可以

将模式编译为正则表达式对象,然后使用正则表达式对象的方法来操作字符串

方法 功能说明
compile(pattern[,flags]) 创建模式对象
search(pattern,string[,flags]) 在整个字符串中寻找模式,返回Match对象或None
match(pattern,string[,flags]) 从字符串的开始处匹配模式,返回Match对象或None
findall(pattern,string[,flags]) 列出字符串中模式的所有匹配项
split(pattern,string[,maxsplit=0] 根据模式匹配项分割字符串
sub(pat,repl,string[,count=0]) 将字符串中所有pat的匹配项用repl替换
escape(string) 将字符串中所有特殊正则表达式字符转义

其中,

函数参数flags

的值可以是

re.I(忽略大小写)



re.L



re.M(多行匹配模式)



re.S(使元字符“ . ”匹配任意字符,包括换行符)



re.U(匹配 Unicode 字符)



re.X(忽略模式中的空格,并可以使用#注释)的不同组合(使用“|”进行组合)



4.7.直接使用re模块方法

可以

直接使用re模块

的方法来

实现正则表达式

操作。

>>> import re
>>> text='alpha.beta....gamma delta'
>>> re.split('[\. ]+',text)
['alpha', 'beta', 'gamma', 'delta']
>>> re.split('[\.]+',text)
['alpha', 'beta', 'gamma delta']
>>> re.split('[\. ]+',text,maxsplit=2)		#分割2次
['alpha', 'beta', 'gamma delta']
>>> pat='[a-zA-Z]+'
>>> re.findall(pat,text)				   #查找所有单词
['alpha', 'beta', 'gamma', 'delta']
>>> pat='{name}'
>>> text='Dear {name}...'
>>> re.sub(pat,'Mr.Dong',text)				#字符串替换
'Dear Mr.Dong...'
>>> s='a s d'
>>> re.sub('a|s|d','good',s)				#字符串替换
'good good good'
>>> re.escape('http://www.python.org')		 #字符串转义
'http://www\\.python\\.org'
>>> print(re.match('done|quit','done'))		 #匹配成功
<re.Match object; span=(0, 4), match='done'>
>>> print(re.match('done|quit','done!'))	 #匹配成功
<re.Match object; span=(0, 4), match='done'>
>>> print(re.match('done|quit','doe!'))		 #匹配不成功
None
>>> print(re.match('done|quit','d!one!done'))
None
>>> print(re.fullmatch('done|quit','d!one!done'))
None
>>> print(re.search('done|quit','d!one!done'))		#匹配成功
<re.Match object; span=(6, 10), match='done'>

re.match()的概念是从头匹配一个符合规则的字符串,从起始位置开始匹配,匹配成功返回一个对象,未匹配成功返回None。

re.escape(pattern):转义pattern中的特殊字符。

下面的代码

使用不同的方法删除字符串中多余的空格,连续多个空格只保留一个

>>> import re
>>> s='aaa       bb     c d e    fff   '
>>> re.sub('\s+',' ',s)			#直接使用re模块的字符串替换方法
'aaa bb c d e fff '
>>> re.split('[\s]+',s)
['aaa', 'bb', 'c', 'd', 'e', 'fff', '']
>>> re.split('[\s]+',s.strip())		#同时删除了字符串尾部的空格
['aaa', 'bb', 'c', 'd', 'e', 'fff']
>>> ''.join(re.split('[\s]+',s.strip()))
'aaabbcdefff'
>>> ' '.join(re.split('[\s]+',s.strip()))
'aaa bb c d e fff'
>>> ' '.join(re.split('\s+',s.strip()))
'aaa bb c d e fff'
>>> re.sub('\s+',' ',s.strip())
'aaa bb c d e fff'
>>> s.split()			#也可以不用正则表达式
['aaa', 'bb', 'c', 'd', 'e', 'fff']
>>> ' '.join(s.split())
'aaa bb c d e fff'


' '.join(re.split('[\s]+',s.strip()))



' '.join(re.split('\s+',s.strip()))

有什么不同呢?

下面代码

使用以“”开头的元字符来实现字符串的特定搜索

>>> import re
>>> example='ShanDong Institute of Business and Technology is a very beautiful school.'
>>> re.findall('\\ba.+?\\b',example)	#以a开头的完整单词
['and', 'a ']
>>> re.findall('\\ba\w*\\b',example)
['and', 'a']
>>> re.findall('\\Bo.+?\\b',example)	#含有o字母的单词中第一个非首字母O的剩余部分
['ong', 'ology', 'ool']
>>> re.findall('\\b\w.+?\\b',example)	#所有单词
['ShanDong', 'Institute', 'of', 'Business', 'and', 'Technology', 'is', 'a ', 'very', 'beautiful', 'school']
>>> re.findall('\b\w.+?\b',example)
[]
>>> re.findall(r'\b\w.+?\b',example)	#使用原始字符串,减少需要输入的符号数量
['ShanDong', 'Institute', 'of', 'Business', 'and', 'Technology', 'is', 'a ', 'very', 'beautiful', 'school']
>>> re.split('\s',example)				#使用任何空白字符分割字符串
['ShanDong', 'Institute', 'of', 'Business', 'and', 'Technology', 'is', 'a', 'very', 'beautiful', 'school.']
>>> re.findall('\d\.\d\.\d+ ','Python 2.7.11')		#查找并返回x.x.x形式的数字
[]
>>> re.findall('\d\.\d\.\d+ ', 'Python 2.7.11,Python 3.5.1')
[]



4.8.使用正则表达式对象

​ 首先使用

re模块的compile()方法将正则表达式编译生成正则表达式对象

,然后

再使用正则表达式对象提供的方法进行了字符串处理

,然后

编译后的正则表达式对象可以提高字符串处理速度

​ 正则表达式对象的

match(string[,pos[,endpos]])方法

在字符串开头或指定位置进行搜索,模式必须出现在字符串开头或指定位置;

search(string[,pos[,endpos]])方法在整个字符串或指定范围内进行搜索



findall(string[,pos[,endpos]])方法在字符串中

查找所有符合正则表达式的字符串并以列表形式返回


>>> import re
>>> example='ShanDong Institute of Business and Technology'
>>> pattern=re.compile(r'\bB\w+\b')		#以B开头的单词
>>> pattern.findall(example)
['Business']
>>> pattern=re.compile(r'\w+g\b')		#以g结尾的单词
>>> pattern.findall(example)
['ShanDong']
>>> pattern=re.compile(r'\b[a-zA-Z]{3}\b')		#查找3个字母长的单词
>>> pattern.findall(example)
['and']
>>> pattern.match(example)					#从字符串开头开始匹配,不成功,没有返回值
>>> pattern.search(example)					#在整个字符串中搜索,成功
<re.Match object; span=(31, 34), match='and'>
>>> pattern=re.compile(r'\b\w*a\w*\b')		#查找所有含有字母a的单词
>>> pattern.findall(example)
['ShanDong', 'and']
>>> text="He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly",text)				#查找所有副词
['carefully', 'quickly']

正则表达式对象的

sub(repl,string[,count=0])和subn(repl,string[,count=0])方法用来实现字符串替换功能

>>> import re
>>> example='''Beautiful is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat
 is better than nested.Sparse is better than dense.Readability counts.'''
>>> pattern=re.compile(r'\bb\w*\b',re.I)
>>> pattern.sub('*',example)		#将以字母b和B开头的单词替换为*
'* is * than ugly.Explicit is * than implicit.Simple is * than complex.Complex is * than complicated.Flat is * than nested.Sparse is * than dense.R
eadability counts.'
>>> pattern.sub('*',example,1)		#只替换一次
'* is better than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat is better than nested
.Sparse is better than dense.Readability counts.'
>>> pattern=re.compile(r'\bb\w*\b')
>>> pattern.sub('*',example,1)		#将第一个以字母b开头的单词替换为*
'Beautiful is * than ugly.Explicit is better than implicit.Simple is better than complex.Complex is better than complicated.Flat is better than nes
ted.Sparse is better than dense.Readability counts.'

正则表达式对象的

split(string[,maxsplit=0])方法用来实现字符串分割

>>> example=r'one,two,three.four/five\six? seven[eight]nine|ten'
>>> pattern=re.compile(r'[,./\\?[\]\|]')
>>> pattern.split(example)
['one', 'two', 'three', 'four', 'five', 'six', ' seven', 'eight', 'nine', 'ten']
>>> example=r'one two   three  four,five.six.seven,eight,nine9ten'
>>> pattern=re.compile(r'[\s,.\d]+')
>>> pattern.split(example)
['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']



4.9.子模式与Match对象

使用

圆括号”()“表示一个子模式,圆括号内的内容作为一个整体出现



例如”(red)+“可以匹配redred、redredred等多个重复red的情况

>>> telNumber='''Suppose my Phone No. is 0535-1234567,yours is 010-12345678,his is 025-87654321.'''
>>> pattern=re.compile(r'(\d{3,4})-(\d{7,8})')
>>> pattern.findall(telNumber)
[('0535', '1234567'), ('010', '12345678'), ('025', '87654321')]

正则表达式模块或正则表达式对象的

match()方法和search()方法匹配成功后都会返回Match对象

。**Match对象的主要方法有

group()

(返回匹配的一个或多个子模式内容)、

groups()

(返回一个包含匹配的所有子模式内容的元组)、

groupdict()

(返回包含匹配的所有命名子模式的字典)、

start()

(返回指定子模式内容的起始位置)、

end()

(返回指定子模式内容的结束位置的前一个位置)、

span()

(返回一个包含指定子模式内容起始位置和结束位置前一个位置的元组)**等。例如:下面的代码使用re模块的search()方法返回的Match对象来删除字符串中指定内容。

>>> email="tony@tiremove_thisger.net"
>>> import re
>>> m=re.search("remove_this",email)
>>> email[:m.start()]+email[m.end():]
'tony@tiger.net'

下面的代码演示了

Match对象的group()方法

的用法。

>>> m=re.match(r"(\w+) (\w+)","Isaac Newton,physicist")
>>> m.group(0)
'Isaac Newton'
>>> m.group(1)
'Isaac'
>>> m.group(2)
'Newton'
>>> m.group(1,2)
('Isaac', 'Newton')
>>> m=re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)","Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'

下面的代码演示了Match对象的

groups()方法

的用法。

>>> m=re.match(r"(\d+)\.(\d+)","24.1632")
>>> m.groups()
('24', '1632')

下面的代码演示了Match对象的groupdict()方法。

>>> m=re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)","Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}



5.函数定义与调用

在Python中,定义函数的语法如下:

def 函数名([参数列表]):
	'''注释'''
	函数体

在Python中使用def关键字来定义函数,然后是一个空格和函数名称,接下来是一对圆括号,在圆括号内是形式参数列表,如果有多个参数则使用逗号分隔开,圆括号之后是一个冒号和换行,最后是必要的注释和函数体代码。定义函数时需要注意:

​ (1)函数形参不需要声明其类型,也不需要指定函数返回值类型。

​ (2)即使该函数不需要接收任何参数,也必须保留一对空的圆括号。

​ (3)括号后面的冒号必不可少。

​ (4)函数体相对于def关键字必须保持一定的空格缩进。

最后,Python允许

嵌套定义函数,并且所有包含__call__()方法的类的对象均被认为是可调用的

​ 例如:下面的函数用来计算斐波那契数列中小于参数n的所有值:

#计算斐波那契数列F(n)=F(n-1)+F(n-2)中小于参数n的所有值:
def fib(n):
    a,b=1,1
    while a<n:
        print(a,end=' ')
        a,b=b,a+b
    print()

fib(1000)

在定义函数时,开头部分的注释并不是必须的,但是如果为函数的定义加上一段注释,可以为用户提供友好的提示和使用帮助。例如:把上面生成斐波那契数列的函数定义修改为下面的形式,在函数开头加上一段注释。

def fib(n):
    '''accept an integer n.
        return the numbers less than n in Fibonacci sequence.'''
    a,b=1,1
    while a<n:
        print(a,end=' ')
        a,b=b,a+b
    print()

fib(1000)



5.1.形参与实参

​ 函数

定义时圆括号内是使用逗号分隔开的形参列表

(parameter),

一个函数可以没有形参,但是定义时一对圆括号必须有,表示这是一个函数并且不接收参数

​ 函数

调用时向其传递实参

(arguments),

根据不同的参数类型,将实参的值或引用传递给形参

例如,在之前

定义函数fib()时括号内的n就是该函数的形参,而调用该函数时括号内的1000则是传递给该函数的实参

​ 在定义函数时,对参数个数并没有限制,如果有多个形参,则需要使用逗号进行分隔。

def printMax(a,b):
    if a<b:
        print(b,'is the max')
    else:
        print(a,'is the max')

printMax(100,234)

如果输入的参数不支持比较运算,则会出错,可以参考异常处理结构来解决这个问题。

对于绝大多数情况下,在

函数内部直接修改形参的值不会影响实参

>>> def addOne(a):
...     print(a)
...     a+=1
...     print(a)
>>> a=3
>>> addOne(a)
3
4
>>> a
3

​ 从运行结果可以看出,在

函数内部修改了形参a的值,但是当函数运行结束以后,实参a的值并没有被修改

,可以参考关于变量作用域的讨论。当然,

在有些情况下,可以通过特殊的方式在函数内部修改实参的值

>>> def modify(v):			#修改列表元素值
...     v[0]=v[0]+1
>>> a=[2]
>>> modify(a)
>>> a
[3]
>>> def modify(v,item):		#为列表增加元素
...     v.append(item)
>>> a=[2]
>>> modify(a,3)
>>> a
[2, 3]
>>> def modify(d):			#修改字典元素值或为字典增加元素
...     d['age']=38
>>> a={'name':'Dong','age':37,'sex':'Male'}
>>> modify(a)
>>> a
{'name': 'Dong', 'age': 38, 'sex': 'Male'}

也就是说,如果传递给函数的是Python**

可变序列


,并且

在函数内部使用下标或其他方式为可变序列增加、删除元素或修改元素值时**,

修改后的结果是可以反映到函数之外的,即实参也得到相应的修改



5.2.参数类型

在Python中,函数参数有很多种,主要可以分为

普通参数、默认值参数、关键参数、可变长度参数

等。

Python函数定义也非常灵活,在定义函数时不需要指定参数的类型,形参的类型完全由调用者传递的实参类型以及Python解释器的理解和推断来决定;同样,也不需要指定函数的返回值类型,这将由函数中的return语句来决定。


函数的返回值类型由return语句返回值的类型来决定



如果函数中没有return语句或者没有执行到return语句而返回或者执行了不带任何值的return语句,则函数都默认为返回空值None



5.2.1.默认值参数

​ 在定义函数时,Python

支持默认参数,即在定义函数时为形参设置默认值



在调用带有默认值参数的函数时,可以不用为设置默认值的形参进行传值,此时函数将会直接使用函数定义时设置的默认值



默认值参数与可变长度参数可以实现类似于函数重载的目的

。带有默认值参数的函数定义语法如下:

​ def 函数名(…,形参名=默认值):

​ 函数体

​ 调用带有默认值参数的函数时,可以不对默认值参数进行赋值,也可以通过显示赋值来替换其默认值,具有较大的灵活性。如果需要,可以

使用”函数名.func_defaults“(在Python3.x中使用”函数名.__defaults__“)随时查看函数所有默认值参数的当前值,其返回值为一个元组,其中的元素依次表示每个默认值参数的当前值

。例如下面的函数定义:

>>> def say(message,times=1):
...     print((message+' ') * times)
...
>>> say.__defaults__
(1,)

​ 调用该函数时,

如果只为第一个参数传递实参,则第二个参数使用默认值1,如果为第二个参数传递实参,则不再使用默认值1,而是使用调用者显式传递的值

>>> say('hello')
hello
>>> say('hello',3)
hello hello hello
>>> say('hi',7)
hi hi hi hi hi hi hi

​ 需要注意的是,在定义带有默认值参数的函数时,

默认值参数必须出现在函数形参列表的最右端,且任何一个默认值参数右边都不能再出现非默认值参数

。否则从而会导致函数定义失败。

>>> def f(a=3,b,c=5):
  File "<stdin>", line 1
    def f(a=3,b,c=5):
              ^
SyntaxError: non-default argument follows default argument
>>> def f(a=3,b):
  File "<stdin>", line 1
    def f(a=3,b):
              ^
SyntaxError: non-default argument follows default argument
>>> def f(a,b,c=3):
...     print a,b,c
另外,特别需要注意的是,**多次调用函数并不为默认值参数传递值时,默认值参数只在第一次调用时进行解释**。**对于列表、字典这样复杂类型的默认值参数**,这一点可能会导致很严重的逻辑错误,而这种错误或许会耗费较多的精力来定位和纠正。例如:
def demo(newitem,old_list=[]):		#列表作为默认值
    old_list.append(newitem)
    return old_list
print(demo('5',[1,2,3,4]))
print(demo('aaa',['a','b']))
print(demo('a'))
print(demo('b'))
[1, 2, 3, 4, '5']
['a', 'b', 'aaa']
['a']
['a', 'b']

然后把代码修改为下面的样子,再运行一下,看看区别在哪里。

def demo1(newitem,old_list=None):		#非复杂默认值参数
    if old_list is None:
        old_list=[]
    old_list.append(newitem)
    return old_list
print(demo1('5',[1,2,3,4]))
print(demo1('aaa',['a','b']))
print(demo1('a'))
print(demo1('b'))
[1, 2, 3, 4, '5']
['a', 'b', 'aaa']
['a']
['b']


5.2.2.关键参数



关键参数主要指调用函数时的参数传递方式,而与函数定义无关



通过关键参数

可以按参数名字传递值,实参顺序可以和形参顺序不一致,但不影响参数值的传递结果,避免了用户需要牢记参数位置和顺序的麻烦

,使得函数的调用和参数传递更加灵活方便

>>> def demo(a,b,c=5):
...     print(a,b,c)
...
>>> demo(3,7)
3 7 5
>>> demo(c=8,a=9,b=0)
9 0 8


5.2.3.可变长度参数

​ 可变长度参数在


定义函数时主要有两种形式:

parameter和**parameter




前者

用来


接收任意多个实参并将其放在一个(元组)中

*,

后者


接收类似于关键参数一样显式赋值形式的多个实参并将其放入{字典}中

下面的代码演示了第一种形式可变长度参数的用法,即

无论调用该函数时传递了多少实参,一律将其放入元组中

>>> def demo(*p):
...     print(p)
...
>>> demo(1,2,3)
(1, 2, 3)
>>> demo(1,2,3,4,5,6,7)
(1, 2, 3, 4, 5, 6, 7)

下面代码则演示了第二种形式可变长度参数的用法,即在调用该函数时自动

将接收的参数转换为字典

>>> def demo(**p):
...     for item in p.items():
...             print(item)
>>> demo(x=1,y=2,z=3)
('x', 1)
('y', 2)
('z', 3)

​ 下面的代码

演示了定义函数时几种不同形式的参数混合使用的用法

。虽然Python完成支持这样做,但是除非真的很必要,否则请不要这样用,因为这会使得代码非常混乱而严重降低可读性,并导致程序查错非常困难。另外,一般而言,一个函数如果可以接收很多参数,很可能是函数设计得不好,例如,函数功能过多,需要进行必要得拆分和重新设计,以满足高内聚得要求。

>>> def func_4(a,b,c=4,*aa,**bb):
...     print((a,b,c))
...     print(aa)
...     print(bb)
...
>>> func_4(1,2,3,4,5,6,7,8,9,xx='1',yy='2',zz=3)
(1, 2, 3)
(4, 5, 6, 7, 8, 9)
{'xx': '1', 'yy': '2', 'zz': 3}


5.2.4.参数传递时的序列解包

​ 为含有

多个变量

的函数传递参数时,

可以使用Python列表、元组、集合、字典以及其他可迭代对象作为实参,并在实参名称前加个星号,Python解释器将自动进行解包,然后传递给多个单变量形参

。如果

使用字典对象作为实参,则默认使用字典的”键“,如果需要将字典中的”键-值对“作为参数则需要使用items()方法,如果需要将字典的”值“作为参数则需要调用字典的value()方法

。最后,请务必保证实参中元素个数与形参个数相等,否则将出现错误。

>>> def demo(a,b,c):
...     print(a+b+c)
...
>>> seq=[1,2,3]
>>> demo(*seq)
6
>>> tup=(1,2,3)
>>> demo(*tup)
6
>>> dic={1:'a',2:'b',3:'c'}
>>> demo(*dic)
6
>>> Set={1,2,3}
>>> demo(*Set)
6
>>> demo(*dic.values())
abc



5.3.return语句

​ return语句用来从一个函数中返回并结束函数的执行,同时还可以通过return语句从函数中返回一个任意类型的值。不论return语句出现在函数的什么位置,一旦得到执行将直接结束函数的执行。

如果函数没有return语句或者执行了不返回任何值的return语句,Python将认为该函数以return None结束,即返回空值

​ 作为使用者,

在调用函数时,一定要注意函数有没有返回值,以及是否会对参数的值进行修改

。例如第二章介绍过的列表对象方法sort()属于原地操作,没有返回值,而内置函数sorted()则返回排序后的列表,并不对原列表做任何修改。

>>> a_list=[1,2,3,4,9,5,7]
>>> print(sorted(a_list))
[1, 2, 3, 4, 5, 7, 9]
>>> print(a_list)
[1, 2, 3, 4, 9, 5, 7]
>>> print(a_list.sort())
None
>>> print(a_list)
[1, 2, 3, 4, 5, 7, 9]



5.4.变量作用域

​ 变量起作用的代码范围称为变量的作用域,不同作用域内同名变量之间互不影响。一个变量在函数外部定义和在函数内部定义,其作用域是不同的,函数内部定义的变量一般为局部变量,而不属于任何函数的变量一般为全局变量。



在函数内定义的普通变量只在该函数内起作用,称为局部变量。当函数运行结束后,在该函数内部定义的局部变量被自动删除而不可访问。在函数内部定义的全局变量当函数结束以后依然存在并且可以访问

​ 如果想要在函数内部修改一个定义在函数外的变量值,那么这个变量就不是局部的,其作用域必须是全局的,

能够同时作用于函数内外

,称为全局变量,可以

通过global来声明或定义

​ ==(1)==一个变量已在函数外定义,

如果在函数内需要修改这个变量的值,并将这个赋值结果反映到函数之外



可以在函数内用global声明这个变量为全局变量

,明确声明要使用已定义的同名全局变量。

​ ==(2)==在函数内部

直接使用global关键字将一个变量声明为全局变量

,即使

在函数外没有定义该全局变量,再调用这个函数之后,将自动增加新的全局变量


或者说:在函数内如果只引用某个变量的值而没有为其赋新值,该变量为(隐式的)全局变量;如果在函数内任意位置有为变量赋新值的操作,该变量即被认为是(隐式的)局部变量,除非在函数内显式地用关键字global进行声明。

>>> def demo():
...     global x		#声明或创建全局变量
...     x=3				#修改全局变量的值
...     y=4				#局部变量
...     print(x,y)
...
>>> x=5					#在函数外部定义了全局变量x
>>> demo()				#本次调用修改了全局变量x的值
3 4
>>> x
3
>>> y					#局部变量在函数运行结束后自动删除
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> del x				 #删除了全局变量x
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> demo()				 #本次调用创建了全局变量
3 4
>>> x
3
>>> y					 #局部变量在函数调用和执行结束后自动删除,在函数外部不可访问
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined


而如果局部变量与全局变量具有相同的名字,那么该局部变量会在自己的作用域内隐藏同名的全局变量,例如:

>>> def demo():
...     x=3			#创建了局部变量,并自动隐藏了同名的全局变量
...
>>> x=5
>>> demo()
>>> x
5

最后,如果需要在同一个程序的不同模块之间共享全局变量,可以编写一个专门的模块来实现这一目的,例如:假设在模块A.py中有如下变量定义:

global_variable=0

而在模块B.py中包含以下语句:

import A
A.global_variable=1

在模块C.py中有以下语句:

import A
print(A.global_variable)

从而实现了在不同模块之间共享全局变量的目的。



5.5.lambda表达式

lambda表达式可以用来

声明匿名函数

,即没有函数名字的临时使用的小函数。

lambda表达式只可以包含一个表达式,不允许包含其他复杂的语句,但在表达式中可以调用其他函数,并支持默认值参数和关键参数,该表达式的计算结果就是函数的返回值

>>> f=lambda x,y,z:x+y+z
>>> print(f(1,2,3))
6
>>> g=lambda x,y=2,z=3:x+y+z		#含有默认值参数
>>> print(g(1))
6
>>> print(g(2,z=4,y=5))				#调用时使用关键参数
11
>>> L=[(lambda x:x**2),(lambda x:x**3),(lambda x:x**4)]
>>> print(L[0](2),L[1](2),L[2](2))
4 8 16
>>> D={'f1':(lambda:2+3),'f2':(lambda:2*3),'f3':(lambda:2**3)}
>>> print(D['f1'](),D['f2'](),D['f3']())
5 6 8
>>> L=[1,2,3,4,5]
>>> print(map((lambda x:x+10),L))		#没有名字的lambda表达式
<map object at 0x000001F139BCB100>
>>> print(list(map((lambda x:x+10),L)))
[11, 12, 13, 14, 15]
>>> L
[1, 2, 3, 4, 5]
>>> def demo(n):
...     return n*n
...
>>> demo(5)
25
>>> a_list=[1,2,3,4,5]
>>> list(map(lambda x:demo(x),a_list))		#包含函数调用并且没有名字的lambda表达式
[1, 4, 9, 16, 25]
>>> data=list(range(20))
>>> import random
>>> random.shuffle(data)
>>> data
[18, 4, 5, 11, 13, 3, 9, 7, 1, 0, 14, 10, 2, 8, 19, 16, 17, 15, 12, 6]
>>> data.sort(key=lambda x:x)				#用在列表的sort()方法中
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> data.sort(key=lambda x:len(str(x)))
>>> data
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
>>> data.sort(key=lambda x:len(str(x)),reverse=True)	#将列表里的每个值进行str转换,并求出长度,再用长度进行降序排序(默认就是升序False)
>>> data
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

​ 在使用lambda表达式时,要注意变量作用域带来的问题,在

下面的代码中变量x是在外部作用域中定义的,对lambda表达式而言不是局部变量,从而导致出现了错误

>>> r=[]
>>> for x in range(10):
...     r.append(lambda:x**2)
...
>>> r[0]()
81
>>> r[1]()
81

若修改为下面的代码,则可以得到正确的结果。

>>> r=[]
>>> for x in range(10):
...     r.append(lambda n=x:n**2)
...
>>> r[0]()
0
>>> r[1]()
1
>>> r[5]()
25



5.6.高级话题

​ 在本章的最后,让我们来看几个高级话题,包括

内置map()、reduce()、filter()、生成器、Python字节码、函数嵌套函数定义以及可调用对象的知识

​ (1)内置函数

map()

可以将一个

单参数函数依次作用到一个序列或迭代器对象的每个元素上,并返回一个map对象作为结果,其中每个元素是原序列中元素经过该函数处理后的结果4

,该函数不对原序列或迭代器对象做任何修改。

>>> list(map(str,range(5)))
['0', '1', '2', '3', '4']
>>> def add5(v):
...     return v+5
...
>>> list(map(add5,range(10)))
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

​ (2)内置函数

reduce()

可以将

一个接收两个参数的函数以累积的方式从左到右依次作用到一个序列或迭代器对象的所有元素上

>>> from functools import reduce
>>> seq=[1,2,3,4,5,6,7,8,9]
>>> reduce(lambda x,y:x+y,seq)
45
>>> def add(x,y):
...     return x+y
...
>>> reduce(add,range(10))
45

类似的运算并不局限于数值类型,例如下面的代码使用前面定义的函数**add()**实现了字符串连接。

>>> reduce(add,map(str,range(10)))
'0123456789'

​ (3)内置函数

filter()将一个单参数函数作用到一个序列上,返回该序列中使得该函数返回值为True的那些元素组成的列表、元组或字符串

>>> seq=['foo','x41','?!','***']
>>> def func(x):
...     return x.isalnum()
...
>>> list(filter(func,seq))
['foo', 'x41']
>>> [x for x in seq if x.isalnum()]
['foo', 'x41']
>>> (filter(lambda x:x.isalnum(),seq))
<filter object at 0x000002D638E1AE00>
>>> tuple(filter(lambda x:x.isalnum(),seq))
('foo', 'x41')

​ (4)

包含yield语句的函数用来创建生成器

。迭代器的最大特点是

惰性求值

,尤其

适用于大数据处理

。下面的代码演示了如何

使用生成器来生成斐波那契数列

def f():
    a,b=1,1
    while True:
        yield a
        a,b=b,a+b
        
a=f()
for i in range(10):
    print(a.__next__(),end=' ')
1 1 2 3 5 8 13 21 34 55 

__next__用法:Return the next item from the iterator

上面定义的生成器函数还可以这样使用:

def f():
    a,b=1,1
    while True:
        yield a
        a,b=b,a+b

# a=f()
# for i in range(10):
#     print(a.__next__(),end=' ')
for i in f():
    if i>100:
        break
    print(i,end=' ')
1 1 2 3 5 8 13 21 34 55 89 

(5)使用

dis模块的功能

可以

查看函数的字节码指令

>>> def add(n):
...     n+=1
...     return n
...
>>> import dis
>>> dis.dis(add)
  2           0 LOAD_FAST                0 (n)
              2 LOAD_CONST               1 (1)
              4 INPLACE_ADD
              6 STORE_FAST               0 (n)

  3           8 LOAD_FAST                0 (n)
             10 RETURN_VALUE

(6)函数嵌套定义与可调用对象

​ 在Python中,函数是可以

嵌套定义

的。另外,

任何包含_

call

_()方法的类的对象都是可调用的

。例如下面的代码演示了函数嵌套定义的情况:

>>> def linear(a,b):
...     def result(x):
...             return a*x+b
...     return result

​ 下面的代码演示了可调用对象类的定义:

>>> class linear:
...     def __init__(self,a,b):	#最重要的是,我们没有专门调用__init__方法,只是在创建一个类的新实例的时候,把参数包括在圆括号内跟在类名后面,从而传递给__init__方法。这是这种方法的重要之处。
...             self.a,self.b=a,b
...     def __call__(self,x):	#一个类实例也可以变成一个可调用对象,只需要一个特殊方法__call__()。
#我们把 linear 类变成一个可调用对象:
...             return self.a*x+self.b

​ 使用上面的两种方式中任何一种,都可以通过以下的方式来定义一个可调用对象:

>>> taxes=linear(0.3,2)

​ 然后通过下面的方式来调用该对象:

>>> taxes(5)

下面的代码完整地演示了嵌套函数定义与使用的方法,有效利用了用户名检查功能的代码

def check_permission(func):
    def wrapper(* args,**kwargs):
        if kwargs.get('username')!='admin':
            raise Exception('Sorry.You are not allowed.')
        return func(*args,**kwargs)
    return wrapper

class ReadWriteFile(object):
    # 把函数check_permission作为装饰器使用
    @check_permission
    def read(self,username,filename):
        return open(filename,'r').read()

    def write(self,username,filename,content):
        open(filename,'a+').write(content)
        #把函数check_permission作为普通函数使用
    write=check_permission(write)



6、面向对象程序设计(OOP)

Python完全采用了

面向对象程序设计

的思想,是真正面向对象的高级动态编程语言,完全支持面向对象的基本功能,如

封装、继承、多态以及对基类方法的覆盖或重写

。但与其他面向对象程序设计语言不同的是,

Python中对象的概念很广泛,Python中的一切内容都可以称为对象,而不一定必须是某个类的实例

面向对象程序设计的三要素为:封装、继承和多态。封装就是事物抽象为类,把对外接口暴露,将实现和内部数据隐藏;继承是指可以使用现有类的所有功能并进行扩展;多态性是指允许将父对象设置成为和一个或更多子对象相等的技术。

例如,字符串、列表、字典、元组等内置数据类型都具有和类完全相似的语法和用法。创建类时用

变量形式表示的对象属性称为数据成员或成员属性,用函数形式表示的对象行为称为成员函数或成员方法

,成员属性和成员方法统称为类的成员。

Python属于典型的

解释型语言

,所以运行Python需要解释器支持,只要在不同的平台安装了不同的解释器,代码就可以随时运行,不用担心任何兼容性问题,真正的”一次编写,到处运行“,Pyhon的可移植性是很强的。



6.1.类定义语法

Python使用

class关键字来定义类

,class关键字之后是一个空格,然后是类的名字,再后是一个冒号,最后换行并定义类的内部实现。

类名的首字母一般要大写,当然也可以按照自己的习惯定义类名

,但是一般推荐参考惯例来命名,并在整个系统的设计和实现中保持风格一致,这一点对于团队合作尤其重要。

class Car:
	def info(self):
		print("This is a car")


定义了类之后,可以用来实例化对象,并通过”对象名.成员“的方式来访问其中的数据成员或成员方法

,例如下面代码:

car=Car()
car.info()
This is a car

在Python中,可以使用**内置方法isinstance()**来测试一个对象是否为某个类的实例,下面的代码演示了isinstance()的用法。

isinstance(car,Car)
True
isinstance(car,str)
False

最后,Python提供了一个关键字pass,类似于空语句,可以用在类和函数的定义中或者选择结构中。当暂时没有确定如何实现功能,或者为以后的软件升级预留空间时,可以使用该关键字来”占位“。

class A
pass
def demo():
	pass
if 5>3:
	pass



6.2.self参数

​ 类的

所有实例方法都必须至少有一个名为self的参数,并且必须是方法的第一个形参

(如果有多个形参),

self参数代表对象本身




在类的实例方法中访问实例属性时需要以self为前缀,但在外部通过对象名调用对象方法时并不需要传递这个参数,如果在外部通过类名调用方法则需要显式为self参数传值


。参考后面讨论。

class A:
	def __init__(hahaha,v):
		hahaha.value=v
	def show(hahaha):
		print(hahaha.value)
a=A(3)
a.show()
3



6.3.类成员与实例成员

​ 这里主要指数据成员,或者广义上的属性。可以说属性有两种:

一种是实例属性;另一种是类属性



实例属性一般是指在构造函数_

init

_()中定义的,定义和使用时必须以self为前缀



类属性是在类中所有方法之外定义的数据成员

。在主程序中(或类的外部),

实例属性属于实例(对象),只能通过对象名访问



而类属性属于类,可以通过类名或对象名访问

​ 在类的方法中**

可以调用类本身的其他方法


,也

可以访问类属性以及对象属性**。

在Python中比较特殊的是,

可以动态地为类和对象增加成员

,这一点是和很多面向对象程序设计语言不同的,也是Python动态类型特点的一种重要体现

#OOP
class Car:
    price=100000            #定义类属性
    def __init__(self,c):
        self.color=c        #定义实例属性

car1=Car("Red")
car2=Car("Blue")
print(car1.color,Car.price)
Car.price=110000
Car.name='QQ'               #增加类属性
car1.color="Yellow"
print(car2.color,Car.price,Car.name)
print(car1.color,Car.price,Car.name)
def setSpeed(self,s):
    self.speed=s
import types
car1.setSpeed=types.MethodType(setSpeed,car1)   #动态为对象增加成员方法,为实例增加方法
car1.setSpeed(50)               #调用对象的成员方法
print(car1.speed)

​ 在Python中,函数和方法是有区别的。

方法一般指与特定实例绑定的函数



通过对象调用方法时,对象本身将被作为第一个参数传递过去

,普通函数并不具备这一特点。

import types

class Demo:
    pass
t=Demo()
def test(self,v):
    self.value=v
t.test=test
print(t.test) #<function test at 0x00000255135B3EB0>
t.test(t,3)
print(t.value)
3
t.test=types.MethodType(test,t)
#方法一般指与特定实例绑定的函数
print(t.test) #<bound method test of <__main__.Demo object at 0x00000255136B7250>>
t.test(5)
print(t.value)
5



6.4.私有成员与共有成员

​ Python并没有对私有成员提供严格的访问保护机制。在定义类的属性时,如果属性名以两个下划线”__”开头则表示是

私有属性



私有属性在类的外部不能直接访问,需要通过调用对象的公有成员方法来访问,或者通过Python支持的特殊方式来访问

​ Python提供了访问私有属性的特殊方式,可用于程序的测试和调试,对于成员方法也具有同样的性质。

​ 私有属性是

为了数据封装和保密而设的属性,一般只能在类的成员方法(类的内部)中使用访问

,虽然Python支持一种特殊的方式来从外部直接访问类的私有成员,但是并不推荐这样做。公有属性是可以公开使用的,既可以在类的内部进行访问,也可以在外部程序中使用。

class A:
    def __init__(self,value1=0,value2=0):
        self._value1=value1
        self.__value2=value2
    def setValue(self,value1,value2):
        self._value1=value1
        self.__value2=value2
    def show(self):
        print(self._value1)
        print(self.__value2)

a=A()
print(a._value1)
0
print(a._A__value2)     #在外部访问对象的私有数据成员
0

​ 在Python中,

以下划线开头的变量名和方法名有特殊含义

,尤其是在

类的定义中




用下划线作为变量名和方法名前缀和后缀来表示类的特殊成员


*

(1)_xxx:这样的对象叫做保护成员,不能用”from module import

“导入,只有类对象和子类对象能访问这些成员。


(2)_

xxx

_:系统定义的特殊成员。


(3)__xxx:类中的私有成员,

只有类对象自己能访问

,子类对象也不能访问到这个成员,但在外部可以通过”对象名._类名__xxx这样的特殊方式来访问。Python中不存在严格意义上的私有成员。

另外,在IDLE交互模式下,

一个下划线“_”表示解释器中最后一次显示的内容或最后一次语句正确执行的输出结果

。例如:

>>> 3+5
8
>>> _+2
10
>>> _*3
30
>>> _/5
6.0
>>> 1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>> _
6.0

下面的代码演示了

特殊成员定义和访问的方法

class Fruit:
    def __init__(self):
        self.__color='Red'
        self.price=1
apple=Fruit()
print(apple.price)
apple.price=2
print(apple.price,apple._Fruit__color)  #显示对象私有成员的值

apple._Fruit__color='Blue'
print(apple.price,apple._Fruit__color)
#print(apple.__color)    #不能直接访问对象的私有数据成员,出错

peach=Fruit()
print(peach.price,peach._Fruit__color)



6.5.方 法

​ 在类中定义的方法可以粗略分为四大类:

公有方法、私有方法、静态方法和类方法

。其中,

公有方法、私有方法都属于对象,

私有方法的名字以两个下划线“__”开始

,每个对象都有自己的公有方法和私有方法,在这两类方法中可以访问属于类和对象的成员




公有方法通过对象名直接调用,私有方法不能通过对象名直接调用



只能在属于对象的方法中通过

self调用



在外部通过Python支持的特殊方式来调用



​ 如果

通过类名来调用属于对象的公有方法,需要显式为该方法的self参数传递一个对象名,用来明确指定访问哪个对象的数据成员。静态方法和类方法都可以通过类名和对象名调用,但不能直接访问属于对象的成员,只能访问属于类的成员

。一般

将cls作为类方法的第一个参数名称,但也可以使用其他的名字作为参数,并且在调用类方法时不需要为该参数传递值

class Root:
    __total=0
    def __init__(self,v):
        self.__value=v
        Root.__total+=1

    def show(self):
        print('self.__value:',self.__value)
        print('Root.__total:',Root.__total)

    @classmethod
    def classShowTotal(cls):    #类方法
        print(cls.__total)

    @staticmethod
    def staticShowTotal():      #静态方法
        print(Root.__total)

r=Root(3)
r.classShowTotal()		#通过对象来调用类方法
r.staticShowTotal()		#通过对象来调用静态方法
r.show()
rr=Root(5)
Root.classShowTotal()  #通过类名调用类方法
Root.staticShowTotal()  #通过类名调用静态方法
#Root.show()     #试图用类名直接调用实例方法,失败
Root.show(r)
Root.show(rr)
rr.show()



6.6.属 性

在Python3.x中,

属性得到了较为完善的实现,支持更加全面的保护机制

例如下面的代码所示:如果

设置属性为只读,则无法修改其值,也无法为对象增加与属性同名的新成员,同时,也无法删除对象属性

class Test:
    def __init__(self,value):
        self.__value=value

    @property
    def value(self):        #只读,无法修改和删除
        return self.__value

t=Test(3)
print(t.value)
#t.value=5      #只读属性不允许修改值
t.v=5           #动态为对象新增新成员
print(t.v)
del t.v         #动态删除成员
#del t.value     #试图删除对象属性,失败
print(t.value)

下面的代码则把属性设置为可读、可修改,而不允许删除。

class Test:
    def __init__(self,value):
        self.__value=value

    def __get(self):
        return self.__value

    def __set(self,v):
        self.__value=v

    value=property(__get,__set)

    def show(self):
        print(self.__value)

t=Test(3)
print(t.value) #允许读取属性值
t.value=5
print(t.value) #允许修改属性值
t.show()        #属性对应的私有变量也得到了相应的修改
del t.value   #试图删除属性,失败

当然,也可以

将属性设置为可读、可修改、可删除

class Test:
    def __init__(self,value):
        self.__value=value

    def __get(self):
        return self.__value

    def __set(self,v):
        self.__value=v

    def __del(self):
        del self.__value

    value=property(__get,__set,__del)

    def show(self):
        print(self.__value)

t=Test(3)
t.show()
print(t.value)
t.value=5
t.show()
print(t.value)
del t.value
#print(t.value)
#t.show()
t.value=1   #为对象动态增加属性和对应的私有数据成员
t.show()
print(t.value)



6.7.继承机制



继承

是为代码复用和设计的,是

面向对象程序设计的重要特征之一

。当设计一个新类时,如果

可以继承一个已有的设计良好的类然后进行二次开发

,无疑会大幅度减少开发工作量。

在继承关系中,已有的、设计好的类称为

父类或基类

,新设计的类称为

子类或派生类




1、派生类可以继承父类的

公有成员

,但是不是继承其私有成员



2、如果需要在派生类中调用基类的方法,可以使用==内置函数super()==或者通过“

基类名 . 方法名()

”的方式来实现

​ Python支持

多继承

,如果父类中有相同的方法名,而在子类中使用时没有指定父类名,则Python解释器将从左向右按顺序搜索。

例:在派生类中调用基类方法。


首先设计Person类,然后以Person为基类派生Teacher类,分别创建Person类和Teacher类的对象,并在派生类对象中调用基类Person中方法

class Person(object):   #必须以object为基类
    def __init__(self,name=' ',age=20,sex='man'):
        self.setName(name)
        self.setAge(age)
        self.setSex(sex)

    def setName(self,name):
        if not isinstance(name,str):
            print('name must be string.')
            return
        self.__name=name

    def setAge(self,age):
        if not isinstance(age,int):
            print('age must be integer.')
            return
        self.__age=age

    def setSex(self,sex):
        if sex!='man' and sex!='woman':
            print('sex must be "man" or "woman".')
            return
        self.__sex=sex

    def show(self):
        print('Name:',self.__name)
        print('Age:',self.__age)
        print('Sex:',self.__sex)

class Teacher(Person):  #派生类
    def __init__(self,name=' ',age=30,sex='man',department='Computer'):
        super(Teacher,self).__init__(name,age,sex)
        ##or,use another method like below
        #Person.__init__(self,name,age,sex)
        self.setDepartment(department)

    def setDepartment(self,department):
        if not isinstance(department,str):
            print('department must be a string.')
            return
        self.__department=department

    def show(self):
        super(Teacher, self).show()
        print('Department:',self.__department)

if __name__=='__main__':
    zhangsan=Person('Zhang San',19,'man')
    zhangsan.show()
    lisi=Teacher('Li Si',32,'man','Math')
    lisi.show()
    lisi.setAge(40)
    lisi.show()

为了更好地理解Python类的继承机制,再来看看下面代码,并认真体会构造函数、私有方法和普通公开方法的继承原理。

class A(object):
    def __init__(self):
        self.__private()
        self.public()

    def __private(self):
        print('__private() method in A')

    def public(self):
        print('public() method in A')
class B(A):         #注意,类B没有定义构造函数
    def __private(self):
        print('__private() method in B')
    def public(self):
        print('public() method in B')

b=B()
print(dir(b))      #使用 dir()函数可以查看对象内的所有的属性和方法

class C(A):
    def __init__(self):     #显式定义构造函数
        self.__private()
        self.public()
    def __private(self):
        print('__private() method in C')
    def public(self):
        print('public() method in C')

c=C()
print(dir(c))



派生类只有定义了构造函数,派生类中的私有方法才能被调用,否则调用的是基类的私有方法。而公有方法无限制。



7.文件操作

按文件中数据的组织形式可以把文件分为

文本文件



二进制文件

两大类。


1、文本文件存储的是常规字符串,由若干文本行组成,通常每行以换行符’\n’结尾。常规字符串是指记事本或其他文本编辑器能正常显示,编辑并且人类能够直接阅读和理解的字符串,如英文字母、汉字、数字字符串。文本文件可以使用字处理软件,如gedit、记事本进行编辑。


2、二进制文件把对象内容以字节串(Bytes)进行存储,无法用记事本或其他普通文本处理软件直接进行编辑,通常也无法被人类直接阅读和理解,需要使用专门的软件进行解码后读取、显示、修改或执行。常见的如图形图像文件、音视频文件、可执行文件、资源文件、各种数据库文件、各类Office文档等都属于二进制文件。



7.1.文件对象

​ 无论是文本文件还是二进制文件,其操作流程基本都是一致的,即:

首先打开文件并创建文件对象,然后通过该文件对象对文件内容进行读取、写入、删除、修改等操作,最后关闭并保存文件内容

。Python内置了文件对象,通过

open()函数

即可以指定模式打开指定文件并创建文件对象,例如:

​ 文件对象名=open(文件名[,打开方式[,缓冲区]])

​ 其中,

文件名指定了被打开的文件名称,如果要打开的文件不在当前目录中,还需要指定完整路径

,为了减少完整路径中“\”符号的输入,可以使用原始字符串;

打开模式指定了打开文件后的处理方式,例如:只读、读写、追加等



缓冲区指定了读写文件的缓存模式,数值0表示不缓存,数值1表示缓存,如大于1则表示缓冲区的大小,默认值是缓存模式

。如果执行正常,

open()函数返回1个文件对象

,通过该文件对象可以对文件进行各种操作,如果指定文件不存在、访问权限不够、磁盘空间不够或其他原因导致创建文件对象失败则抛出异常。


例如

:下面代码分别以读、写方式打开了两个文件并创建了与之对应的文件对象:

f1=open('D:\\file1.txt','a+')
s='文本文件的读取方法\n 文本文件的写入方法\n'
f1.write(s)
f1.close()

对于上面的代码,建议写为如下形式:

s='文本文件的读取方法\n 文本文件的写入方法\n'
with open('D:\\file1.txt','a+') as f:
	f.write(s)

使用上下文管理关键字with可以自动管理资源,不论何种原因跳出with块,总能保证文件被正确关闭,并且可以在代码块执行完毕后自动还原进入该代码块时的现场。


例如

:读取并显示文本文件的前5个字节。

​ Python3.x对中文支持较好,对read()方法的解释是读取文件中指定数量的字符而不是字节,对中文和英文字母同等对待。

f1=open('D:\\file1.txt','r')
print(f1.read(5))
print(f1.read(7))
f1.seek(0)			#如果需要对文件指针进行定位,可以使用seek()方法。
print(f1.read(12))


例如

:读取并显示文本文件所有行。

f1=open('D:\\file1.txt','r')
while True:
    line=f1.readline()		#从文本文件中读取一行内容作为结果返回
    if line=='':
        break
    print(line,end=' ')
f1.close()

当然也可以写作:

f1=open('D:\\file1.txt','r')
li=f1.readlines()		#readlines():把文本文件中的每行文本作为一个字符串存入列表中,返回该列表
for line in li:
    print(line,end=' ')
f1.close()


例如

:移动文件指针。

s='中国湖北黄石SDIBT'
fp=open(r'D:\\file1.txt','w')
fp.write(s)
fp.close()
fp=open(r'D:\\file1.txt','r')
print(fp.read(3))
fp.seek(2)
print(fp.read(1))
fp.seek(13)
print(fp.read(1))
fp.seek(15)
print(fp.read(1))
fp.seek(3)
print(fp.read(1))


例如

:读取文本文件data.txt中所有整数,将其按升序排序后再写入文本文件data_asc.txt中。

with open('D:\\file1.txt','r') as fp:
    data=fp.readlines()
data=[int(line.strip()) for line in data]
data.sort()
data=[str(i)+'\n' for i in data]
with open('data_asc.txt','w') as fp:
    fp.writelines(data)



8.异常处理结构与程序调试、测试

异常处理是指因为程序执行过程中出错而在正常控制流之外采取的行为。严格来说,语法错误和逻辑错误不属于异常,但有些语法或逻辑错误往往会导致异常,例如:由于大小写拼写错误而试图访问不存在的对象,或者试图访问不存在的文件等等。



8.1.Python中的异常处理结构


try…except结构

​ 异常处理结构中最常见也最基本的是try…except…结构。其中try子句中的代码块包含可能出现异常的语句,而except子句用来捕捉相应的异常,except子句中的代码块用来处理异常。

​ 如果try中的代码块没有出现异常,则继续往下执行异常处理结构后面的代码;如果出现异常并且被except捕获,则继续往外层抛出;如果所有层都没有捕获并处理该异常,则程序终止并将该异常抛给最终用户。该结构语法如下:

try:
	try#被监控的语句,可能会引发异常
except Exception[as reason]:
	except#处理异常的代码

如果需要捕获所有类型的异常,可以使用BaseException,即Python异常类的基类,代码格式如下:

try:
	...
except BaseException as e:
	except#处理所有错误

​ 下面

演示了try…except…结构的用法

,代码运行后提示用户输入内容,如果输入的是数字,则循环结束,否则一直提示用户输入正确格式的内容。

while True:
    try:
        x=int(input("Please enter a number:"))
        break
    except ValueError:
        print("That was not a valid number. Try again......")

​ 在使用时,except子句可以在异常类名字后面指定一个变量,用来捕获异常的参数或更详细的信息。

try:
    raise Exception('spam','eggs')
except Exception as inst:
    print(type(inst))
    print(inst.args)
    print(inst)

    x,y=inst.args
    print('x=',x)
    print('y=',y)



8.2.try…except…else结构

​ 另外一种

常用的异常处理结构是try…except…else…语句

。正如前面章节中已经提到过,

带else子句的异常处理结构也是一种特殊形式的选择结构

。如果try中的代码抛出了异常,并且被某个except捕捉,则执行相应的异常处理代码,这种情况下不会执行else中的代码;如果try中的代码没有抛出任何异常,则执行else块中的代码。

a_list=['China','America','England','France']
while True:
    n=input("请输入字符串的序号:")
    try:
        n=int(n)
        print(a_list[n])
    except IndexError:
        print('列表元素的下标越界或格式不正确,请重新输入字符串的序号')
    else:
        break       #结束循环

###8.3.带有多个 except 的 try 结构

​ 在实际开发中,同一段代码可能会抛出多个异常,需要针对不同的异常类型进行相应的处理。

为了支持多个异常的捕捉和处理,Python提供了带有多个except的异常处理结构,类似于多分支选择结构



一旦某个except捕获了异常,则后面剩余的except子句将不会再执行

。该结构的语法为:

try:
	try#被监控的语句
except Exception1:
	except1		#处理异常1的语句
except Exception2:
	except2		#处理异常2的语句

下面的代码演示了该结构的用法:

try:
    x=input('请输入被除数:')
    y=input('请输入除数:')
    z=float(x)/float(y)
except ZeroDivisionError:
    print('除数不能为0')
except TypeError:
    print('被除数和除数应为数值类型')
except NameError:
    print('变量不存在')
else:
    print(x,'/',y,'=',z)

​ 将要捕获的异常写在一个元组中,可以使用一个except语句捕获多个异常,并且共用同一段异常处理代码,当然,除非确定要捕获的多个异常可以使用同一段代码来处理,否则并不建议这样做。

import sys
try:
	f=open('myfile.txt')
	s=f.readline()
	i=int(s.strip())
except (OSError,ValueError,RuntimeError,NameError):
	pass



8.4.try…except…finally结构

最后一种常用的异常处理结构是try…except…finally…结构。

在该结构中,finally子句中的语句块无论是否发生异常都会执行,常用来做一些清理工作以释放try子句中申请的资源

。语法如下:

try:
	...
finally:
	...		#无论如何都会执行的代码

例如下面的代码,无论是否发生异常,语句print(5)都会被执行。

try:
	3/0
except:
	print(3)
finally:
	print(5)
3
5

再如下面的代码,无论读取文件是否发生异常,总是能够保证正常关闭该文件。

try:
	f=open('test.txt','r')
	line=f.readline()
	print(line)
finally:
	f.close()

需要注意的一个问题是,如果try子句中的异常没有被捕捉和处理,或者except子句或else子句中的代码出现了异常,那么这些异常将会在finally子句执行完后再次抛出。

try:
	3/0
finally:
	print(5)
5
ZeroDivisionError:division by zero



9.GUI编程

使用wxPython创建GUI程序的三个主要步骤如下:


(1)导入wxPython包。


(2)建立框架类:框架类的父类为wx.Frame,在框架类的构造函数中调用父类的构造函数进行初始化,然后为frame类添加各种控件以及事件处理方法,如果需要在窗体上增加其他控件,可在构造函数中增加有关代码,如需处理相应事件,可增加框架类的成员函数,并将其与相应的控件绑定。


(3)建立主程序:通常需要做4件事,创建应用程序对象、创建框架类对象、显示框架、开始事件循环。执行frame.Show(True)后,框架才看得见,执行app.MainLoop()后,框架才能接收并处理事件。

wxPython提供了几乎所有常用的控件,例如按钮、静态文本标签、文本框、单选钮、复选框、对话框、菜单、列表框、树形控件等。



10.网络程序设计



10.1.UDP和TCP编程

UDP属于无连接协议,在UDP编程时不需要首先建立连接,而是直接向接收方发送信息。UDP编程经常用到的socket模块方法有3个。

(1)socket([family[,type[,proto]]]):创建一个Socket对象,其中family为socket,AF_INET表示IPV4,socket.AF_INET6表示IPV6;type为SOCK_STREAM表示TCP,SOCK_DGRAM表示UDP。

(2)sendto(string,address):把string指定的内容发送给address指定的地址,其中address是一个包含接收方主机IP地址和应用进程端口号的元组,格式为(IP地址,端口号)。

(3)recvfrom(bufsize[,flags]):接收数据。

下面通过一个示例简单了解下如何使用UDP进行网络通信。


UDP通信程序


发送端发送一个字符串,假设接收端在本机5000端口进行监听,并显示接收的内容,如果收到字符串byte(忽略大小写)则监听结束。

#网络编程
#接收端代码receive.py
import socket
#使用IPV4协议,使用UDP协议传输数据
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#绑定端口和端口号,空字符串表示本机任何可用IP地址
s.bind(('',5000))
while True:
    data,addr=s.recvfrom(1024)
    #显示接收到的内容
    print('received message:{0} from PORT {1} on {2}'.format(data.decode(),
                                                             addr[1],addr[0]))
    if data.decode().lower()=='bye':
        break
s.close()

发送端代码:

#发送端代码
import socket
import sys
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#假设192.168.145.1是接收端机器的IP地址
s.sendto(sys.argv[1].encode(),("192.168.56.1",5000))
s.close()

获取本机IP地址和网卡物理地址:

import socket
import uuid

ip=socket.gethostbyname(socket.gethostname())
node=uuid.getnode()
macHex=uuid.UUID(int=node).hex[-12:]
mac=[]
for i in range(len(macHex))[::2]:
    mac.append(macHex[i:i+2])
mac=':'.join(mac)
print('IP:',ip)
print('MAC:',mac)


TCP编程

TCP一般用于要求可靠数据传输的场合。编写TCP程序时经常需要用到的socket模块的方法主要有6个

(1)connect(address):连接远程计算机。

(2)send(byte[,flags]):发送数据。

(3)recv(bufsize[,flags]):接收数据。

(4)bind(address):绑定地址。

(5)listen(backlog):开始监听。

(6)accept():响应客户端的请求。

使用TCP协议进行通信需要先在客户端和服务端之间建立连接,并且要在通信结束后关闭连接以释放资源。



10.2.网络嗅探器与端口扫描器设计

###10.3.网页内容爬取与网页爬虫

​ Python3.x把这两个模块整合为

urllib

一个库,主要包含urllib.request、urllib.response、urllib.parse和urllib.error四个部分。

下面代码演示了如何读取并显示指定网页内容:

import urllib.request
fp=urllib.request.urlopen(r'http://www.python.org')
print(fp.read(100))
print(fp.read(100).decode())
fp.close()

如果需要更高级的网页抓取功能,请参考scrapy框架。



11.科学计算与可视化



11.1.NumPy简单应用

根据Python社区的习惯,可以使用下面的方式来导入NumPy模块:

>>> import numpy as np
>>> a=np.array((1,2,3,4,5))
>>> b=np.array(([1,2,3],[4,5,6],[7,8,9]))
>>> a*2
array([ 2,  4,  6,  8, 10])
>>> a/2
array([0.5, 1. , 1.5, 2. , 2.5])
>>> a**2
array([ 1,  4,  9, 16, 25])
>>> a=np.array((1,2,3))
>>> c=a*b
>>> print(c)
[[ 1  4  9]
 [ 4 10 18]
 [ 7 16 27]]

  1. a-zA-Z

    ↩︎

  2. a-zA-Z

    ↩︎



版权声明:本文为weixin_45804541原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。