Python基础 Release 2.x and 3.x

发布于:2023-01-04 ⋅ 阅读:(293) ⋅ 点赞:(0)

接前面一路过来牛刀小试,实操检验,也收集不少资料,来自csdn,python Documentation,c语言中文网,菜鸟教程,笨方法等等,集基础之大成,整合在一起,免去搜寻的麻烦,作为学习pythonr打基础之用!如有不足之处,共勉!!!本文虽然啰嗦,旨在不同人不同理解,但是总能找到能懂的地方!!!
生活苦短,我用python!!!修缮不足,共勉共进!!!

一、前言


Python简介

Python 是一种解释型语言: 这意味着开发过程中没有了编译这个环节。类似于PHP和Perl语言。

Python 是交互式语言: 这意味着,您可以在一个 Python 提示符 >>> 后直接执行代码。

Python 是面向对象语言: 这意味着Python支持面向对象的风格或代码封装在对象的编程技术。

Python 是初学者的语言:Python 对初级程序员而言,是一种伟大的语言,它支持广泛的应用程序开发,从简单的文字处理到 WWW 浏览器再到游戏。

1. python2和python3 的区别有哪些?

python2和python3分别是python的两个版本

【1】print方法
python2既可以使用小括号的方式,也可以使用一个空格来分隔打印内容,比如 print ‘hi’;

python3使用print必须要用小括号包含打印内容,比如print(“hi”)

【2】编码
python2中使用ASCII编码,需要更改更改字符集(添加coding:utf-8)才能正常支持中文

python3中使用utf-8,支持中文

【3】除法运算
python2中 / 除法规则是整除,结果为整数,把小数部分完全忽略掉,要想真除需要转为浮点数再除

​ //整数相除,与/相同,取整

python3 / 是真除,会得到小数

// 是地板除,取整

【4】数据类型
python2整型有长整形和整型

python3只有整型,范围是无限大

【5】python3中有f格式化,python2用%格式化
python2:

yourname = 'Hongyu'
hisname = 'lijiang'
print 'You are %s , he is %s .', %( yourname, hisname)

python3:

yourname = 'Hongyu'
hisname = 'lijiang'
print(f'You are {yourname}, he is {hisname}.')

【6】range方法
python3中没有xrange方法,只有range方法

python2中range(1,10)返回列表,python3返回range可迭代对象,节约内存

【7】市场差异
python2:官方通知python2 2020开始不再维护,但企业很多代码都是python2,python2有很大的用户基群故会出现历史遗留问题,需要很长时间的迁移过度到python3
python3:最新版本,但目前市场使用量不大

【8】字符串
python2中Unicode表示字符串序列,str表示字节序列

python3中str表示字符串序列,byte表示字节序列

【9】input
在Python2中raw_input()和input( ),两个函数都存在,其中区别为:
1)raw_input():将所有输入作为字符串看待,返回字符串类型
2)input():只能接收"数字"的输入,在对待纯数字输入时具有自己的特性,它返回所输入的数字的类型(int, float )
在Python3中raw_input()和input( )进行了整合,去除了raw_input(),仅保留了input()函数,其接收任意任性输入,将所有输入默认为字符串处理,并返回字符串类型。

【10】异常处理
1)Python2中捕获异常的语法为except exc, var,Python3中捕获异常的语法为except exc as var,使用语法except (exc1, exc2) as var可以同时捕获多种类别的异常。 Python 2.6已经支持这两种语法。

2)在Python2时代,所有类型的对象都是可以被直接抛出的,在Python3时代,只有继承自BaseException的对象才可以被抛出。

3)Python2中触发异常可以用raise IOError, "file error"或raise IOError(“file error”)两种方式,Python3中触发异常只能用raise IOError("file error”)。

4)异常StandardError 被Python3废弃,统一使用Exception

5)在Python2时代,异常在代码中除了表示程序错误,还经常做一些普通控制结构应该做的事情,在Python3中可以看出,设计者让异常变的更加专一,只有在错误发生的情况才能去用异常捕获语句来处理。

【11】比较符
Python2 中任意两个对象都可以比较,11 < 'test’返回True

Python3中只有同一数据类型的对象可以比较,11 < 'test’报错,需要调用正则判断

import re  
11 < int('test') if re.compile('^[0-9]+$').match('test') else 0 

【12】包的定义
Python2:文件夹中必须有 __init __.py文件
Python3:不需要有 __init __.py文件

【13】打开文件
Python2中使用file( … ) 或 open(…)
Python3中只能使用open(…)
【14】 Python3 对 Unicode 字符的原生支持。
Python2 中使用 ASCII 码作为默认编码方式导致 string 有两种类型 str 和 unicode,Python3 只支持 unicode 的 string。Python2 和 Python3 字节和字符对应关系为:

python2 python3 表现 转换 作用
str bytes 字节 encode 存储
unicode str 字符 decode 显示

【15】Python3 采用的是绝对路径的方式进行 import
Python2 中相对路径的 import 会导致标准库导入变得困难(想象一下,同一目录下有 file.py,如何同时导入这个文件和标准库 file)。Python3 中这一点将被修改,如果还需要导入同一目录的文件必须使用绝对路径,否则只能使用相关导入的方式来进行导入。
【16】 Python2 中存在老式类和新式类的区别,Python3 统一采用新式类。新式类声明要求继承 object,必须用新式类应用多重继承。
【17】 Python3 使用更加严格的缩进。Python2 的缩进机制中,1 个 tab 和 8 个 space 是等价的,所以在缩进中可以同时允许 tab 和 space 在代码中共存。这种等价机制会导致部分 IDE 使用存在问题。
Python3 中 1 个 tab 只能找另外一个 tab 替代,因此 tab 和 space 共存会导致报错:

TabError:
inconsistent use of tabs and spaces in indentation.

【18】第三方工具包差异
【19】工具安装问题

2. 八大应用领域:

  1. 人工智能
  2. 计算与数据分析
  3. 自动化运维
  4. 云计算
  5. 游戏开发
  6. 网络爬虫
  7. web开发
  8. 网络编程

3.Python 特点:

  1. 易于学习:Python有相对较少的关键字,结构简单,和一个明确定义的语法,学习起来更加简单。

  2. 易于阅读:Python代码定义的更清晰。

  3. 易于维护:Python的成功在于它的源代码是相当容易维护的。

  4. 一个广泛的标准库:Python的最大的优势之一是丰富的库,跨平台的,在UNIX,Windows和 Macintosh兼容很好。

  5. 互动模式:互动模式的支持,您可以从终端输入执行代码并获得结果的语言,互动的测试和调试代码片断。

  6. 可移植:基于其开放源代码的特性,Python已经被移植(也就是使其工作)到许多平台。

  7. 可扩展:如果你需要一段运行很快的关键代码,或者是想要编写一些不愿开放的算法,你可以使用C或C++完成那部分程序, 然后从你的Python程序中调用。

  8. 数据库:Python提供所有主要的商业数据库的接口。

  9. GUI编程:Python支持GUI可以创建和移植到许多系统调用。

  10. 可嵌入: 你可以将Python嵌入到C/C++程序,让你的程序的用户获得"脚本化"的能力。

4.Python缺点

  1. 运行速度,有速度要求的话,用 C++ 改写关键部分吧。

  2. 国内市场较小(国内以 Python 来做主要开发的,目前只有一些 web2.0 公司)。但时间推移,目前很多国内软件公司,尤其是游戏公司,也开始规模使用他。

  3. 中文资料匮乏(好的 Python 中文资料屈指可数,现在应该变多了)。托社区的福,有几本优秀的教材已经被翻译了,但入门级教材多,高级内容还是只能看英语版。

  4. 构架选择太多(没有像 C# 这样的官方 .net 构架,也没有像 ruby 由于历史较短,构架开发的相对集中。Ruby on Rails 构架开发中小型web程序天下无敌)。不过这也从另一个侧面 说明, python比较优秀,吸引的人才多,项目也多。

5. Python优点:

  1. 语法简洁,容易从类C语言转型,可以快速上手。
  2. 代码优雅,容易阅读。
  3. 使用方便,有大量的内置类型和模块,以及第三方模块。
  4. 思想和风格保持一致性,很多代码的思路都相通,很少出现格外突兀的诧异。

二、Python基础语法


1. 编程和执行python

Python中默认的编码格式是 ASCII 格式;python2.x 脚本加上 # -- coding: UTF-8 --或者 # coding=utf-8 Windows下,会出现乱码,解决办法:
,string.decode(“utf-8”).encode(“gbk”)即可;
Python3.X 源码文件默认使用utf-8编码,所以可以正常解析中文,无需指定 UTF-8 编码。

print语法:

print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)

参数

  1. objects – 复数,表示可以一次输出多个对象。输出多个对象时,需要用 , 分隔。

  2. sep – 用来间隔多个对象,默认值是一个空格。

  3. end – 用来设定以什么结尾。默认值是换行符 \n,我们可以换成其他字符串。

  4. file – 要写入的文件对象。

  5. flush – 输出是否被缓存通常决定于 file,但如果 flush 关键字参数为 True,流会被强制刷新。
    返回值无。

注意:
在Windows系统上,没有“可执行模式”的概念。 Python安装程序自动将 .py 文件与 python.exe相关联,这样双击Python文件就会将其作为脚本运行。 扩展也可以是 .pyw ,在这种情况下,会隐藏通常出现的控制台窗
口。

【1】 交互式编程

交互式编程不需要创建脚本文件,是通过 Python 解释器的交互模式进来编写代码。
用例:

>>> print("Hello, Python!")

【2】脚本式编程

所谓脚本,就是你写的 .py 程序。
通过脚本参数调用解释器开始执行脚本,直到脚本执行完毕。当脚本执行完成后,解释器不再有效。
新建脚本:test.py

print("Hello, Python!")

写好上面的代码,然后保存。
注意:
windows下,脚本必须建在可执行的根目录。否则,不可执行,解决办法:cd path path为脚本所在目录。比如说:我的在H:\test\test.py ,那么:

> H:
> cd test
>python

OK,可以了。执行下面的:

用例:

> python test.py

让我们尝试另一种方式来执行 Python 脚本。修改 test.py 文件,如下所示:

实例:

#!/usr/bin/env python

print ("Hello, Python!")

在BSD等类Unix系统上,Python脚本可以直接执行,就像shell脚本一样,第一行添加:
#!/usr/bin/env python3.10,(假设解释器位于用户的 PATH )脚本的开头,并将文件设置为可执行。 #! 必须是文件的前两个字符。在某些平台上,第一行必须以Unix样式的行结尾(‘\n’)结束,而不是以Windows(‘\r\n’)行结尾。请注意,散列或磅字符 ‘#’ 在Python中代表注释开始。
这里,假定您的Python解释器在/usr/bin目录中,可以使用 chmod 命令为脚本提供可执行模式或权限。

$ chmod +x test.py    

windows下,执行脚本:

  1. 编写bat (windows批处理脚本)记事本新建test.bat.内容:
@echo off

:start H:
::1脚本所在盘符,盘符后跟半角英文:               

cd test
::2脚本所在目录,可以看到脚本

call python test.py
::3在批处理执行过程中调用另一个批处理,启用python并打开test.py

pause

编写好后,右键单击打开即可。

  1. 将python脚本打包成.exe文件

首先安装PyInstaller,网址:http://www.pyinstaller.org/
在cmd窗口下使用指令 pip install pyinstaller进行安装。安装好之后,打开命令提示符/shell 窗口,导航到 .py 文件所在的目录,然后使用以下命令pyinstaller your_program.py构建应用程序:

pyinstaller H:\test\test.py  --distpath H:\dist

distpath意思是打包后的目录,在该目录中找到test.exe双击即可。

注意:
Python2.x 中使用 Python3.x 的 print 函数,
如果 Python2.x 版本想使用 Python3.x 的 print 函数,可以导入 __future__包,
该包禁用 Python2.x 的 print 语句,采用 Python3.x 的 print 函数:

实例:

>>> list =["a", "b", "c"]
>>> print list    # python2.x 的 print 语句
['a', 'b', 'c']
>>> from __future__ import print_function  # 导入 __future__ 包
>>> print list     # Python2.x 的 print 语句被禁用,使用报错
  File "<stdin>", line 1
    print list
             ^
SyntaxError: invalid syntax
>>> print (list)   # 使用 Python3.x 的 print 函数
['a', 'b', 'c']
>>>

Python3.x 与 Python2.x 的许多兼容性设计的功能可以通过 __future__这个包来导入。

2. Python 标识符

  1. 在 Python 里,标识符由字母、数字、下划线组成。

  2. 在 Python 中,所有标识符可以包括英文、数字以及 下划线(_),但不能以数字开头。

  3. Python 中的标识符是区分大小写的。

  4. 以下划线开头的标识符是有特殊意义的。以单下划线开头 _foo 的代表不能直接访问的类属性,需通过类提供的接口进行访问,不能用 from xxx import * 而导入。

  5. 以双下划线开头的 __foo 代表类的私有成员,以双下划线开头和结尾的 __foo__代表。
    Python 里特殊方法专用的标识,如 __init__() 代表类的构造函数。

  6. Python 可以同一行显示多条语句,方法是用分号 ; 分开。

3. Python 保留字符

and			exec		not
assert		finally		or
break		for			pass
class		from		print
continue	global		raise
def			if			return
del			import		try
elif		in			while
else		is			with
except		lambda		yield

4. 同一行显示多条语句

Python可以在同一行中使用多条语句,语句之间使用分号(;)分割。

5. 多行语句

Python语句中一般以新行作为语句的结束符。但是我们可以使用斜杠( \)将一行的语句分为多行显示。

6. 多个语句构成代码组

缩进相同的一组语句构成一个代码块,我们称之代码组。像if、while、def和class这样的复合语句,首行以关键字开始,以冒号( : )结束,该行之后的一行或多行代码构成代码组。
我们将首行及后面的代码组称为一个子句(clause)。

7. 行和缩进

Python 的代码块不使用大括号 {} 来控制类,函数以及其他逻辑判断。python 最具特色的就是用缩进来写模块。
缩进的空白数量是可变的,但是所有代码块语句必须包含相同的缩进空白数量,这个必须严格执行。
IndentationError: unindent does not match any outer indentation level错误表明,
你使用的缩进方式不一致,有的是 tab 键缩进,有的是空格缩进,改为一致即可。
如果是 IndentationError: unexpected indent 错误, 则 python 编译器是在告诉你"Hi,老兄,你的文件里格式不对了,可能是tab和空格没对齐的问题",所有 python 对格式要求非常严格。
因此,在 Python 的代码块中必须使用相同数目的行首缩进空格数。
建议你在每个缩进层次使用 单个制表符 或 两个空格 或 四个空格 , 切记不能混用。

8. Python 引号

Python 可以使用引号( ’ )、双引号( " )、三引号( ‘’’ 或 “”" ) 来表示字符串,引号的开始与结束必须是相同类型的。其中三引号可以由多行组成,编写多行文本的快捷语法,常用于文档字符串,在文件的特定地点,被当做注释。

9. Python注释

程序里的注释是很重要的。它们可以用自然语言告诉你某段代码的功能是什么。在你想要临时移除一段代码时,你还可以用注解的方式将这段代码临时禁用。python中单行注释采用 # 开头。
#处于字符串内部,所以它就是引号结束前的字符串中的一部分,这时它只是一个普通字符,而不代表 注解的意思。

10. Python空行

函数之间或类的方法之间用空行分隔,表示一段新的代码的开始。类和函数入口之间也用一行空行分隔,
以突出函数入口的开始。
空行与代码缩进不同,空行并不是Python语法的一部分。书写时不插入空行,Python解释器运行也不会出错。
但是空行的作用在于分隔两段不同功能或含义的代码,便于日后代码的维护或重构。
记住:空行也是程序代码的一部分。

11. 等待用户输入

python2.7----> raw_input(“你要说的内容,enter退出,其它键显示…”)
python3.x----> input(“你要说的内容,enter退出,其它键显示…”)

12. print 输出

print 默认输出是换行的,如果要实现不换行需要在变量末尾加上逗号 , 跟上。

13. 命令行参数

很多程序可以执行一些操作来查看一些基本信息,Python 可以使用 -h 参数查看各参数帮助信息。
例如:

  1. 在命令行运行 python test.py one two three
  2. argparse 模块提供了一种更复杂的机制来处理命令行参数。
  3. python top.py --lines=5 testa.txt testb.txt 在命令行运行时,该脚本会将 args.lines 设为 5 并将 args.filenames 设为 [‘testa.txt’, ‘testb.txt’]。

这里只作简单的了解,详细的慢慢探索吧…

三、 数字、字符串和文本

Python 内置数据类型

数值类型: int, float, complex
文本类型: str
序列类型: list, tuple, range
映射类型: dict
集合类型: set, frozenset
布尔类型: bool
二进制类型: bytes, bytearray, memoryview

1. 数值类型:数字(number)

每一种编程语言都包含处理数字和进行数学计算的方法。那么都有哪些数字类型呢?
Python 支持四种不同的数值类型:

  1. int 整型(Int) - 通常被称为是整型或整数,
    是正或负整数,不带小数点。
  2. long 长整型(long integers) - 无限大小的整数,
    整数最后是一个大写或小写的L。
  3. float 浮点型(floating point real values) -浮点型
    由整数部分与小数部分组成,浮点型也可以使用科学 计数法表示(2.5e2 = 2.5 x 102 = 250)
  4. complex 复数(complex numbers) - 复数
    由实数部分(real)和虚数部分(imag)构成,
    复数的虚部以j或者J作为后缀,具体格式为:
    a + bj,或者complex(a,b)表示,复数的实部a 和虚 部b都是浮点型。
  5. random 内置模块,可用于生成随机数。
|------|-----------------------|------------|-------------|
| int  |   long                |  float     | complex     |
|------|-----------------------|------------|-------------|
| 10   | 51924361L             |    0.0     |  3.14j      |
|------|-----------------------|------------|-------------|
|100   | -0x19323L             |  15.20     |  45.j       |
|------|-----------------------|------------|-------------|
|-786  | 0122L                 |  -21.9     |  9.322e-36j |
|------|-----------------------|------------|-------------|
|080   | 0xDEFABCECBDAECBFBAEl |  32.3+e18  |  .876j      |
|------|-----------------------|------------|-------------|
|-0490 | 535633629843          |  -90.      |  -.6545+0J  |
|------|-----------------------|------------|-------------|
|-0x260| -052318172735L        | -32.54e100 |  3e+26J     |
|------|-----------------------|------------|-------------|
|0x69  | -4721885298529L       |  70.2-E12  |  4.53e-7j   |
|------|-----------------------|------------|-------------|

长整型也可以使用小写"L",但是还是建议您使用大写"L",避免与数字"1"混淆。Python使用"L"来显示长整型。
Python 还支持其他数字类型,例如 Decimal 或 Fraction。Python 还内置支持 复数,后缀 j 或 J 用于表示虚数(例如 3+5j )。
Python支持复数,复数由实数部分和虚数部分构成,可以用a + bj,或者complex(a,b)表示,复数的实部a和虚部b都是浮点型。
Python 全面支持浮点数;混合类型运算数的运算会把整数转换为浮点数。

Python Number 			类型转换
int(x [,base ])         将x转换为一个整数  
long(x [,base ])        将x转换为一个长整数  
float(x )               将x转换到一个浮点数  
complex(real [,imag ])  创建一个复数  
str(x )                 将对象 x 转换为字符串  
repr(x )                将对象 x 转换为表达式字符串  
eval(str )              用来计算在字符串中的有效Python
                        表达式, 并返回一个对象  
tuple(s )               将序列 s 转换为一个元组  
list(s )                将序列 s 转换为一个列表  
chr(x )                 将一个整数转换为一个字符  
unichr(x )              将一个整数转换为Unicode字符  
ord(x )                 将一个字符转换为它的整数值  
hex(x )                 将一个整数转换为一个十六进制字符串  
oct(x )                 将一个整数转换为一个八进制字符串  

2. 文本类型:字符串(string)

字符串通常是指你想要展示给别人的、或者是你想要从程序里“导出”的一小段字符。字符串是Python 中最常用的数据类型。
字符串或串(String)是由数字、字母、下划线组成的一串字符。字符串是对数据的封装。

字符串有多种表现形式,我们可以使用双引号 ( ‘’ 或 “” ) 来识别和创建字符串。

在很多应用场景中,我们需要将字符串和数字拼接在一起,而 Python 不允许直接拼接数字和字符串,所以我们必须先将数字转换成字符串。可以借助 str() 和 repr() 函数将数字转换为字符串。

语法:

str(obj)
repr(obj)

二选一,其中obj 表示要转换的对象,它可以是数字、列表、元组、字典等多种类型的数据。

str() 和 repr() 函数虽然都可以将数字转换成字符串,但它们之间是有区别的:
1)str() 用于将数据转换成适合人类阅读的字符串形式。
2)repr() 用于将数据转换成适合解释器阅读的字符串形式(Python 表达式的形式),适合在开发和调试阶段使用;如果没有等价的语法,则会发生 SyntaxError 异常。

从本质上讲,字符串是由多个字符构成的,字符之间是有顺序的,这个顺序号就称为索引(index)。Python 允许通过索引来操作字符串中的单个或者多个字符,比如获取指定索引处的字符,返回指定字符的索引值等。

  1. 转义序列 (escape sequences)

在需要在字符中使用特殊字符时,python 用反斜杠 \ 转义字符。如下表:

转义字符		描述
\(在行尾时)	续行符
\\       反斜杠符号
\'       单引号
\"       双引号
\a	      响铃
\b       退格(Backspace)
\e	      转义
\f       换页
\n       换行
\N{}     Unicode数据库中的字符名,其中name是它的名字,仅Unicode适用
\ooo     值为8进制ooo的字符
\oyy     八进制数,y 代表 0~7 的字符,例如:\012 代表换行。		
\other   其它的字符以普通格式输出	
\r       回车
\t       横向制表符
\v       ASCⅡ垂直(纵向)制表符
\uxxxx   值为16位十六进制xxxx的字符
\Uxxxxxxxx  值为32位十六进制xxxx的字符
\xyy     十六进制数,以 \x 开头,yy代表的字符,例如:\x0a代表换行      
\000     空

如果不希望前置 \ 的字符转义成特殊字符,可以使用 原始字符串,在引号前添加 r 即可。

  1. 字符串格式化

Python2.6 开始,新增了一种格式化字符串的函数str.format(),它增强了字符串格式化的功能。还有在字符串前加前缀 f 或 F的 f’str’ 或者 f"str"手动格式化字符串:字符串对象的 str.rjust() 方法通过在左侧填充空格,对给定宽度字段中的字符串进行右对齐。
同类方法还有 str.ljust() 和 str.center() 。这些方法不写入任何内容,只返回一个新字符串,如果输入的字符串太长,它们不会截断字符串,而是原样返回;虽然这种方式会弄乱列布局,但也比另一种方法好,后者在显示值时可能不准确(如果真的想截断字符串,可以使用 x.ljust(n)[:n] 这样的切片操作 。)
另一种方法是 str.zfill() ,该方法在数字字符串左边填充零,且能识别正负号。
旧式字符串格式化方法:

    符   号	 描述
      %c	 格式化字符及其ASCII码
      %s	 格式化字符串
      %d	 格式化整数
      %u	 格式化无符号整型
      %o	 格式化无符号八进制数
      %x	 格式化无符号十六进制数
      %X	 格式化无符号十六进制数(大写)
      %f	 格式化浮点数字,可指定小数点后的精度
      %e	 用科学计数法格式化浮点数
      %E	 作用同%e,用科学计数法格式化浮点数
      %g	 %f和%e的简写
      %G	 %F 和 %E 的简写
      %p	 用十六进制数格式化变量的地址

格式化操作符辅助指令:

符号		功能
*		定义宽度或者小数点精度
-		用做左对齐
+		在正数前面显示加号( + )
<sp>	在正数前面显示空格
#		在八进制数前面显示零('0'),在十六进制前面显示'0x'	或者'0X'(取决于用的是'x'还是'X')
0		显示的数字前面填充'0'而不是默认的空格
%		'%%'输出一个单一的'%'
(var)	映射变量(字典参数)
m.n.	m 是显示的最小总宽度,n 是小数点后的位数(如果可用的话)
  1. 三引号 (triple-quotes)

字符串字面值可以包含多行。 一种实现方式是使用三重引号:“”“…”“” 或 ‘’‘…’‘’。 字符串中将自动包括行结束符,但也可以在换行的地方添加一个 \ 来避免此情况。
Python 中三引号可以将复杂的字符串进行赋值和注释。
Python 三引号允许一个字符串跨多行,字符串中可以包含换行符、制表符以及其他特殊字符。
三引号的语法是一对连续的单引号或者双引号(通常都是成对的用)。

  1. Unicode 字符串

Python 中定义一个 Unicode 字符串和定义一个普通字符串一样简单:
输入执行:

>>> u'Hello World !'

输出结果:

u'Hello World !'

引号前小写的"u"表示这里创建的是一个 Unicode 字符串。如果你想加入一个特殊字符,可以使用Python 的 Unicode-Escape 编码。如下例所示:
输入执行:

>>> u'Hello\u0020World !'

输出结果:

u'Hello World !'

被替换的 \u0020 标识表示在给定位置插入编码值为 0x0020 的 Unicode 字符(空格符)。

3. 文本类型:中文文本

@@ 如果要保持文本原样输出,我们只需要用引号"文本"或’文本’就可以了。
那么没引号呢?下面介绍:
计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。Unicode把所有语言都统一到一套编码里,这样就不会再有乱码问题了。Unicode标准也在不断发展,但最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。现代操作系统和大多数编程语言都直接支持Unicode。

ASCII编码和Unicode编码的区别:
ASCII编码是1个字节,而Unicode编码通常是2个字节,举例如下:

  字母 A 用ASCII编码是十进制的65,二进制的01000001;
  字符 0 用ASCII编码是十进制的48,二进制的00110000,注意字符 '0' 和整数 0 是不同的;  

汉字 中 已经超出了ASCII编码的范围,用Unicode编码是十进制的20013,二进制的01001110 00101101。
  如果把ASCII编码的 A 用Unicode编码,只需要在前面补0就可以,因此, A 的Unicode 编码是 00000000 01000001。
windows python2.7 的环境,中文编码默认使用 gbk: 许多模块返回的、或使用的字符串是 unicode,故而需要转码。

例如,xxx 模块 tmp_fun() 返回的字符串是 unicode 字符,而另一个模块 yyy 模块 fun_tmp() 返回的字符串是 gbk 字符,我们现在需要判断两个返回值是否相等。可以使用如下两种方式进行判断。

import xxx
import yyy
if tmp_fun()==fun_tmp().decode("gbk"):
    print "eq"
'''
  decode("gbk")作用为将gbk编码转为unicode编码
'''

或者

import xxx
import yyy
if tmp_fun().encode("gbk")==fun_tmp()
    print "eq"
'''
  encode("gbk")作用为将unicode编码转为gbk编码
'''

四、变量(variable)


变量只不过是用来指代某个东西的名字。程序员通过使用变量名可以让他们的程序读起来更像英语。
而且因为程序员的记性都不怎么地,变量名可以让他们更容易记住程序的内容。

1. 变量的命名(name)或赋值(assignment)

例子:

cars = 100
space_in_a_car = 4.0
drivers = 30
passengers = 90
cars_not_driven = cars - drivers
cars_driven = drivers
carpool_capacity = cars_driven * space_in_a_car
average_passengers_per_car = passengers / cars_driven


print("There are", cars, "cars available.")
print("There are only", drivers, "drivers available.")
print("There will be", cars_not_driven, "empty cars today.")
print("We can transport", carpool_capacity, "people today.")
print("We have", passengers, "to carpool today.")
print("We need to put about", average_passengers_per_car, "in each car.")

等号 = 用来给变量赋值。或者说命名也可以!!!每个变量都拥有独一无二的名字,通过变量的名字就能找到变量中的数据。

等号 = 运算符左边是一个变量名,等号 = 运算符右边是存储在变量中的值。
Python 中的变量赋值不需要类型声明。
每个变量在使用前首先都必须赋值,其次变量赋值以后该变量才会被创建。
每个变量在内存中创建,都包括变量的标识,名称和数据这些信息。
多个对象指定多个变量用逗号(comma),隔开,例如:

a, b, c = 36, 'car', "hello, world!"

变量是存放数据值的容器。

备注:给变量、常量、函数、语句块等起的名字被称之为“标识符”。
变量名命名规则:name = value

  1. 变量名通常由字母,数字,下划线组成,但不能以数字开头;

  2. 变量名不能包含空格,但可使用下划线来分隔其中的单词;

  3. 不能以python中的关键字命名;

  4. 变量名应既简短又具有描述性;

  5. 慎用易混淆的字符:小写字母l和大写字母O,因给他们可能被人错看成数字1和0,中英标点符号也易混淆;

  6. 变量名要区分大小写;

  7. 推荐使用驼峰型(GuessAge或guessAge)和下划线(guess_age)来命名;

  8. 常量通常使用大写来定义。

驼峰命名法详解:
当变量名是由二个或多个单词组成时,还可以利用驼峰命名法来命名。
小驼峰式命名法:
第一个单词以小写字母开始,后续单词的首字母大写。
例如:firstName、lastName
大驼峰式命名法:
每一个单词的首字母都采用大写字母。
例如:FirstName、LastName、CamelCase

2. 标准数据类型

变量是存储在内存中的值,这就意味着在创建变量时会在内存中开辟一个空间。
基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中。
变量是没有类型的。因此,变量可以指定(定义)不同的数据类型,这些变量可以存储数字或字符等。
在内存中存储的数据可以有多种类型。
这里介绍六个标准的数据类型,举例赋值:

   1. Numbers  (数字)    a = 1.05
   2. String   (字符串)  str = "我在学python。"
   3. List     (列表)    list = [258, 369, 'nomi', 'jion']
   4. Tuple    (元组)    tuple = (258, 369, 'nomi', 'jion')
   5. Dictionary(字典)   dic = {258, 369, 'nomi', 'jion'}
   6. set       (集合)     set = {258, 369, 'nomi', 'jion'}

这里明确了解变量是怎么样子,其它的我们简单了解就可以了,后续会有详细的介绍。

变量赋值简单粗暴不需要声明类型, 灵活多变,非常好用。

数字数据类是不可改变的数据类型,改变数字数据类型会分配一个新的对象。

字符串用 “” 或 ‘’ 标识。

列表用 “[ ]” 标识的数组。

元组用 “( )” 标识。内部元素用逗号隔开。但是元组不能二次赋值,相当于只读列表。

字典用 “{ }” 标识。字典由索引 key 和它对应的值 value 组成。

集合用 “{ }” 标识。集合并非字典,字面表达意思不一样,只是同样使用了{}而已。

关于类型不多作介绍,更多数据类型可参考:
https://docs.python.org/zh-cn/3/library/stdtypes.html#context-manager-types

3. 变量作用域

一个程序的所有的变量并不是在哪个位置都可以访问的。访问权限决定于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序你可以访问哪个特定的变量名称。两种最基本的变量作用域如下:

  1. 全局变量:定义在函数外的拥有全局作用域
  2. 局部变量:定义在函数内部的变量拥有一个局部作用域局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中。

全局变量想作用于函数内,需加 global:

  1. global—将变量定义为全局变量。可以通过定义为全局变量,实现在函数内部改变变量值。
  2. 一个global语句可以同时定义多个变量,如 global x, y, z。

用例:

global = 0 # 这是一个全局变量
# 可写函数说明
def sum( arg1, arg2 ):
    #返回2个参数的和."
    global = arg1 + arg2 # global在这里是局部变量.
    print("函数内是局部变量 : ", global)
    return global
 
#调用sum函数
sum( 10, 20 )
print "函数外是全局变量 : ", global

五、 运算符


Python语言支持以下类型的运算符:

1. 算术运算符
2. 比较(关系)运算符
3. 赋值运算符
4. 逻辑运算符
5. 位运算符
6. 成员运算符
7. 身份运算符
8. 三目运算符

各类运算符介绍:

1. 算术运算符

以下假设变量: a=10,b=20:

运算符 描述                                         实例
+     加 - 两个对象相加                             a + b 输出结果 30
-     减 - 得到负数或是一个数减去另一个数             a - b 输出结果 -10
*     乘 - 两个数相乘或是返回一个被重复若干次的字符串  a * b 输出结果 200
/     除 - x除以y                                   b / a 输出结果 2
%     取模 - 返回除法的余数                          b % a 输出结果 0
**    幂 - 返回x的y次幂                             a**b 为10的20次方,  输出结果 :100000000000000000000                             
//    取整除 - 返回商的整数部分(向下取整)           >>> 9//2
                                                   4
                                                   >>> -9//2
                                                   -5

注意:Python2.x 里,整数除整数,只能得出整数。如果要得到小数部分,把其中一个
数改成浮点数即可。

2. 比较运算符

以下假设变量a为10,变量b为20:

运算符 描述                                           实例
==    等于 - 比较对象是否相等                          (a == b) 返回 False。
!=    不等于 - 比较两个对象是否不相等                   (a != b) 返回 True。
<>    不等于 - 比较两个对象是否不相等。python3 已废弃。   (a <> b) 返回 True。这个运算符类似 != 。                                                     
>     大于 - 返回x是否大于y                            (a > b) 返回 False。
<     小于 - 返回x是否小于y。所有比较运算符返回1表示真,  
      返回0表示假。这分别与特殊的变量 True 和 False 等价。(a < b) 返回 True。
>=    大于等于 - 返回x是否大于等于y。                    (a >= b) 返回 False。
<=    小于等于 - 返回x是否小于等于y。                    (a <= b) 返回 True。

3. 赋值运算符

以下假设变量a为10,变量b为20:

运算符	描述			   实例
=	   简单的赋值运算符	c = a + b 将 a + b 的运算结果赋值为 c
+=	   加法赋值运算符	c += a 等效于 c = c + a
-=	   减法赋值运算符	c -= a 等效于 c = c - a
*=	   乘法赋值运算符	c *= a 等效于 c = c * a
/=	   除法赋值运算符	c /= a 等效于 c = c / a
%=	   取模赋值运算符	c %= a 等效于 c = c % a
**=	幂赋值运算符		    c **= a 等效于 c = c ** a
//=	取整除赋值运算符	    c //= a 等效于 c = c // a

4. 位运算符

按位运算符是把数字看作二进制来进行计算的。Python中的按位运算法则如下:

下表中变量 a 为 60,b 为 13,二进制格式如下:

a = 0011 1100

b = 0000 1101

-----------------

a&b = 0000 1100

a|b = 0011 1101

a^b = 0011 0001

~a  = 1100 0011
运算符  描述                                               实例
&      按位与运算符:参与运算的两个值,如果两个相应位都为1,   
则该位的结果为1,否则为0                                     (a & b) 输出结果 12 ,二进制解释: 0000 1100
       
                                                         
      

|  按位或运算符:只要对应的二个二进位有一个为1时,结果位就为1。 (a | b) 输出结果 61 , 二进制解释: 0011 1101
                                                         


^  按位异或运算符:当两对应的二进位相异时,结果为1             (a ^ b) 输出结果 49 , 二进制解释: 0011 0001
                                                          

~  按位取反运算符:对数据的每个二进制位取反,即把1变为0, 
把0变为1 。~x 类似于 -x-1                                    (~a ) 输出结果 -61 , 二进制解释: 1100 0011,在一个有符号二进制数的补码形式。

                                                           
                                  
<<  左移动运算符:运算数的各二进位全部左移若干位,由 << 右边的
       数字指定了移动的位数,高位丢弃,低位补0。                a << 2 输出结果 240 , 二进制解释: 1111 0000
                                                          
            

>> 右移动运算符:把">>"左边的运算数的各二进位全部右移若干位,
     >> 右边的数字指定了移动的位数                             a >> 2 输出结果 15 ,  二进制解释: 0000 1111

5. Python逻辑运算符

Python语言支持逻辑运算符,以下假设变量 a 为 10, b为 20:

运算符     逻辑表达式 描述                              实例

and       x and y   布尔"与" - 如果 x 为 False,
                    x and y 返回 False, 
                    否则它返回 y 的计算值。             (a and b) 返回 20。

or        x or y    布尔"或" - 如果 x 是非 0,
                    它返回 x 的计算值,否则它
                    返回 y 的计算值。                   (a or b) 返回 10。

not        not x    布尔"非" - 如果 x 为 True,
                    返回 False 。如果 x 为 False,
                    它返回 True。                      not(a and b) 返回 False

在 python 中我们会用到下面的术语(字符或者词汇)来定义事物的真(True)或者假(False)。计算机的逻辑就是在程序的某个位置检查这些字符或者变量组合在一起表达的结果是真是假。

• and 与 
• or 或 
• not 非 
• != (not equal) 不等于 
• == (equal) 等于 
• >= (greater-than-equal) 大于等于 
• <= (less-than-equal) 小于等于 
• True 真
• False 假
   布尔真值表
NOT True ?
not False True
not True False
OR True ?
True or False True
True or True True
False or True True
False or False False
AND True ?
True and False False
True and True True
False and True False
False and False False
NOT OR True ?
not (True or False) False
not (True or True) False
not (False or True) False
not (False or False) True
NOT ANDTrue ?
not (Trueand False) True
not (Trueand True) False
not (Falseand True) True
not (Falseand False) True
!= True ?
1 != 0 True
!= True ?
1 != 1 False
0 != 1 True
0 != 0 False
== True ?
1 == 0 False
1 == 1 True
0 == 1 False
0 == 0 True

布尔表达式练习:

1. True and True
2. False and True
3. 1 == 1 and 2 == 1
4. "test" == "test"
5. 1 == 1 or 2 != 1
6. True and 1 == 1
7. False and 0 != 0
8. True or 1 == 1
9. "test" == "testing"
10. 1 != 0 and 2 == 1
11. "test" != "testing"
12. "test" == 1
13. not (True and False)
14. not (1 == 1 and 0 != 1)
15. not (10 == 1 or 1000 == 1000)
16. not (1 != 10 or 3 == 4)
17. not ("testing" == "testing" and "Zed" == "Cool Guy")
18. 1 == 1 and not ("testing" == 1 or 1 == 0)
19. "chunky" == "bacon" and not (3 == 4 or 3 == 3)
20. 3 == 3 and not ("testing" == "testing" or "Python" == "Fun")
  • 看看自己做对了多少?

6. 成员运算符

除了以上的一些运算符之外,Python还支持成员运算符,测试实例中包含了一系列的成员,包括字符串,列表或元组。

运算符  描述                                               实例
in     如果在指定的序列中找到值返回 True,否则返回 False。     x 在 y 序列中 , 如果 x 在 y 序列中返回 True。
not in 如果在指定的序列中没有找到值返回 True,否则返回 False。  x 不在 y 序列中 ,如果 x 不在 y 序列中返回 True。
  1. 身份运算符
    身份运算符用于比较两个对象的存储单元。
运算符  描述                                     实例
is is  是判断两个标识符是不是引用自一个对象         x is y, 类似 id(x) == id(y) ,  如果引用的是同一个对象则 返回 True,否则返回 False

is not is not 是判断两个标识符是不是引用自不同对象  x is not y , 类似 id(a) != id(b)。如果引用的不是同一个对象则返回结果True,否则返回 False。

注: id() 函数用于获取对象内存地址。
脚本模式 is 和==结果是一样的。is 只是传递的指针,判断是否指向同一个地址块,这样 is 两边的参数指向内存中同个地址块。而==则是仅仅判断值相同。is 关键字判断两个小一点数,的确和==运算符一样,结果同为 true,但是这个小一点数取值到底小到多少,上限多少,经过测试,当数小于等于 256 时,两者结果相同,大于 256 后,is 判断结果就为 false 了。猜测:256 刚好为 8 为二进制数,是一个字节,所以可以归纳为当数可以用一个字节来表示时,is 和==结果相同,当数超过一个字节时, 按不同对象来对待,python 为不同数分配了不同内存,不同数为不同的对象,只是值相同而已,is 结果为 false,==依然为 true。python 创建了一个小型整型池来存放这些可以用一个字节表示的数,这样做避免了为小点数值重复分配内存,也即重复创建对象,而是直接引用已经存在的对象,
提高了语言运行性能。

8. 三目运算符

我们从一个具体的例子切入。假设现在有两个数字,我们希望获得其中较大的一个,那么可以使用if else 语句,例如:

if a>b:
    max = a;
else:
    max = b;

但是 Python 提供了一种更加简洁的写法,如下所示:

max = a if a>b else b

类似于其它编程语言中三目运算符 ?: 的写法。Python 是一种极简主义的编程语言,它没有引入?: 这个新的运算符,而是使用已有的 if else 关键字来实现相同的功能。

9. Python运算符优先级

从最高到最低优先级的所有运算符:

运算符		      描述
'expression,...'  字符串转换
         
{key:datum,...}   字典显示
         
[expression,...]  列表显示
         
(experession,...) 绑定或元组显示			
         			
f(arguments...)   函数调用
         
x[index:index]    寻址段
         
x[index]	      下标

x.attribute	     属性参考

**               指数 (最高优先级)
~ + -            按位翻转, 一元加号和减号 (最后两个的方法名为 +@ 和 -@)
* / % //         乘,除,取模和取整除
+ -              加法减法
>> <<            右移,左移运算符
&                位 'AND'与
^                位异或运算符
|                位或运算符
<= < > >=        比较运算符
<> == !=         等于运算符
= %= /=          赋值运算符
//= -= +=        赋值运算符
*= **=           赋值运算符
is is not        身份运算符
in not in        成员运算符
not              bool(布尔)非 逻辑运算符
and              bool(布尔)与 逻辑运算符
or               bool(布尔)或 逻辑运算符
lambda           Lambda 表达式(优先级最低)

六、数据的接收和输出


python2.x读取键盘输入raw_input([prompt])
python3.x读取键盘输入input([prompt]),但是可以接收表达式,并将运算结果返回。
一般软件做的事情主要就是下面几条:

  1. 接受人的输入。
  2. 改变输入。
  3. 打印出改变了的输入

python2.x:

print "How old are you?",
age = raw_input()
print "How tall are you?",
height = raw_input()
print "How much do you weigh?",
weight = raw_input()
print "So, you're %r old, %r tall and %r heavy." % (
 age, height, weight)

python3.x:

print("How old are you?", end=' ')
age = input()
print("How tall are you?", end=' ')
height = input()
print("How much do you weigh?", end=' ')
weight = input()
print(f"So, you're {age} old, {height} tall and {weight} heavy.")

键盘键入:

How old are you? 18
How tall are you? 165
How much do you weight? 50

打印出的输出结果:

So, you're 18 old, 165 tall and 50 heavy.

一次同时输入多个数据:

a, b = map(int,input('以逗号隔开: ').split(","))
print(type(a))
x = a + b
print(x)

要输入12和38这两个数据:

a =  12
b =  38
x =  50.0
以逗号隔开: 12,39
<class 'int'>
51

上面python3.x简化如下,达到提示别人的目的:

age = input("How old are you? ")
height = input("How tall are you? ")
weight = input("How much do you weigh? ")

print(f"So, you're {age} old, {height} tall and {weight} heavy.")

键盘键入,打印出的输出结果:

How old are you? 18
How tall are you? 165
How much do you weight? 50
So, you're 18 old, 165 tall and 50 heavy.

七、函数(function)


def fib(n):    # write Fibonacci series up to n
    """Print a Fibonacci series up to n."""
    a, b = 0, 1
    while a < n:
        print(a, end=' ')
        a, b = b, a+b
    print()

 # Now call the function we just defined:
fib(int(input("请输入你要键入的数字:>")))
请输入你要键入的数字:>2000

输出:

0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Python 提供了一个功能,即允许我们将常用的代码以固定的格式封装(包装)成一个独立的模块,
只要知道这个模块的名字就可以重复使用它,这个模块就叫做函数(Function)。
函数可以做三样事情:

  1. 它们给代码片段命名,就跟“变量”给字符串和数字命名一样。
  2. 它们可以接受参数。
  3. 通过使用 #1 和 #2,它们可以让你创建“微型脚本”或者“小命令”。
    函数是把组织好的,可重复使用的,用来实现单一,或相关联功能的代码段封装起来。提高应用的模块性,和代码的重复利用率。
    你已经知道Python提供了许多内置函数,比如print(),input()。但你也可以自己创建函数,这被叫做用户自定义函数。

python函数有两类:

  1. 内置函数
  2. 自定义函数:将实现特定功能的代码定义成一个函数。

自定义使用 def 新建函数。
将实现特定功能的代码定义成一个函数,每次当程序需要重复实现该功能时,只要执行(调用)该函数即可。

语法:

def functionname( *args ):
    "函数_文档字符串"
    function_suite
    return [expression]

定义 函数使用关键字 def,后跟函数名与括号内的形参列表。函数语句从下一行开始并且必须缩进。

函数内的第一条语句是字符串时,该字符串就是文档字符串,也称为 docstring,详见文档字符串。利用文档字符串可以自动生成在线文档或打印版文档,还可以让开发者在浏览代码时直接查阅文档;Python 开发者最好养成在代码中加入文档字符串的好习惯。
也就是注释或注解。函数注解 是可选的用户自定义函数类型的元数据完整信息。
详见4.8.7.文档字符串:
https://docs.python.org/zh-cn/3/tutorial/controlflow.html#documentation-strings

函数在 执行 时使用函数局部变量符号表,所有函数变量赋值都存在局部符号表中;引用变量时,首先,在局部符号表里查找变量,然后,是外层函数局部符号表,再是全局符号表,最后是内置名称符号表。因此,尽管可以引用全局变量和外层函数的变量,但最好不要在函数内直接赋值(除非是 global 语句定义的全局变量,或 nonlocal 语句定义的外层函数变量)。

在调用函数时会将实际参数(实参)引入到被调用函数的局部符号表中;因此,实参是使用 按值调用 来传递的(其中的 值 始终是对象的 引用 而不是对象的值)。 1 当一个函数调用另外一个函数时,会为该调用创建一个新的局部符号表。

函数定义在当前符号表中把函数名与函数对象关联在一起。解释器把函数名指向的对象作为用户自定义函数。还可以使用其他名称指向同一个函数对象,并访问访该函数。
一般来说,解释器不会输出单独的返回值 None ,如需查看该值,可以使用 print()

return [表达式] 语句返回函数的值。return 语句不带表达式参数时,返回 None。函数执行完毕退出也返回 None。
接下来我们分析演示:

  1. 首先我们告诉 Python 创建一个函数,我们使用到的命令是 def ,也就是“定义(define)”的意思。

  2. 紧接着 def 的是函数的名称。名字可以随便取也没关系。但最好函数的名称能够体现出函数的功能来。

  3. 然后我们告诉函数我们需要 *args (asterisk args),参数必须放 在圆括号 () 中才能正常工作。

  4. 接着我们用冒号 : 结束本行,然后开始下一行缩进。

  5. 冒号以下,使用 4 个空格缩进的行都是属于functionname的内容
    “函数_文档字符串”
    function_suite
    return [expression]

  6. 为了演示它的工作原理,我们把解包后的每个参数都打印出来,这和我们在之前脚本练习中所作的类似。
    如下:test.py

#this one is like your scripts with argv
def print_two(*args):
	arg1, arg2 = args
	print(f"arg1: {arg1}, arg2: {arg2}")
#空行	
#ok, that *args is actually pointless, we can just do this
def print_two_again(arg1, arg2):
	print(f"arg1: {arg1}, arg2: {arg2}")
#空行
#this just takes one argument
def print_one(arg1):
	print(f"arg1: {arg1}")
#空行
#this one takes no arguments
def print_none():
	print("I got nothin'.")
#空行
print_two("Zed","Shaw")
print_two_again("Zed","Shaw")
print_one("First!")
print_none()

1、参数、解包、变量

用例:test2.py

from sys import argv

script, first, second, third = argv

print("The script is called:", script)
print("Your first variable is:", first)
print("Your second variable is:", second)
print("Your third variable is:", third)

参数(argv)可以是接收的数据,数字,字符串,表达式,变量,也可以是组合,比如:数学表达式和变量的组合。
函数的方法名也可以作为另一个函数的参数。也可以叫 parameters都是参数( argument)的意思,argv是所谓的“参数变量(argument variable)”,是一个非常标准的编程术语。在其他的编程语言里你也可以看到它。这个变量包含了你传递给 Python 的参数。

调用函数时可使用的正式参数类型:

  1. 必备参数:以正确的顺序传入函数。调用时的数量必须和声明时的一样。例如:test2.py
  2. 关键字参数:函数调用使用关键字参数来确定传入的参数值。
    使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。
  3. 默认参数:调用函数时,默认参数的值如果没有传入,则被认为是默认值。
  4. 不定长参数:函数能处理比当初声明时更多的参数。声明时不会命名。例如:test.py

argv “解包(unpack)”,在执行时与其将所有参数放到同一个变量下面,我们将每个参数赋予
一个变量名: script, first, second, 以及 third。
那么执行:

python test2.py 1st 2nd 3rd

它的含义很简单:“把 argv 中的东西解包,将所有的参数依次赋予(=)左边的变量名”。
python命令后跟的都是参数。4个。
*args是将参数解包,这和脚本参数解包的原理差不多。

前面我们使用 import 让你的程序实现更多的功能,但实际上没人吧 import 称为“功能”。我希望你可以在没接触到正式术语的时候就弄懂它的功能。在继续下去之前, 你需要知道它们的真正名称:模组(modules)。
从现在开始我们将把这些我们导入(import)进来的功能称作模组。你将看到类似这样的说法:“你需 要把 sys 模组 import 进来。”也有人将它们称作“库(libraries)”,
不过我们还是叫 它们模组吧。
记住“模组(modules)”为你提供额外功能。多读几遍把这个词记住,因为我们后面还会用到它。
叫模块也可以,后面是这么叫的。

2、提示和传递

参数接收区别:

  1. argv是在执行命令时输入参数,也就是在键入命令时,脚本还没有开始运行。
  2. python2.0,使用raw_input是在脚本运行的过程中需要输入。
    python3.0,使用input是在脚本运行的过程中需要输入。
    我们可以使用:
prompt = '>'

作为提示符提示,可以改成不同的值也是可以的。我们将用户提示符设置为变量 prompt,这样我们就不需要在每次用到 raw_input 时重复输入提示用户的字符了。而且如果你要将提示符修改成别的字串,你只要改一个位置就可以了。非常顺手吧。
函数()里面的参数会依次传递给{}里的变量或者对应位置的变量并替换{}。

3、函数调用

函数的本质就是一段有特定功能、可以重复使用的代码,如果需要同样的功能,直接通过起好的名字就可以调用这段代码。
运行上面的脚本test.py会看到如下结果:
执行:> python text.py

arg1: 'Zed', arg2: 'Shaw'
arg1: 'Zed', arg2: 'Shaw'
arg1: 'First!'
I got nothin'.

可以看到:

print_two("Zed","Shaw")
print_two_again("Zed","Shaw")
print_one("First!")
print_none()

这几个函数functionname部已被调用。*args部参数被解包依次被传递。

为自己写一个函数注意事项以供后续参考。你可以写在一个索引卡片上随时阅读,直到你记住所有的要点为止。注意事项如下:

1. 函数定义是以 def 开始的吗? 
2. 函数名称是以字符和下划线 _ 组成的吗? 
3. 函数名称是不是紧跟着括号 ( ? 
4. 括号里是否包含参数?多个参数是否以逗号隔开? 
5. 参数名称是否有重复?(不能使用重复的参数名) 
6. 紧跟着参数的是不是括号和冒号 ): ? 
7. 紧跟着函数定义的代码是否使用了 4 个空格的缩进 (indent)? 
8. 函数结束的位置是否取消了缩进 (“dedent”)?

当你运行(或者说“使用 use”或者“调用
call”)一个函数时,记得检查下面的要点:

1. 调运函数时是否使用了函数的名称? 
2. 函数名称是否紧跟着 ( ? 
3. 括号后有无参数?多个参数是否以逗号隔开? 
4. 函数是否以 ) 结尾?

按照这两份检查表里的内容检查你的练习,直到你不需要检查表为止。
最后,将下面这句话阅读几遍: 

“‘运行函数(run)’、‘调用函数(call)’、和 ‘使用函数(use)’是同一个意思”
“‘运行函数(run)’、‘调用函数(call)’、和 ‘使用函数(use)’是同一个意思”
“‘运行函数(run)’、‘调用函数(call)’、和 ‘使用函数(use)’是同一个意思”

4. lambda匿名函数

在python中用【def + 函数名+参数】的方式定义函数,即:

>>> def function_name(parameters):        ...

这样的函数拥有函数名,调用函数只需要

>>> function_name(input_parameter)

在这里插入图片描述

暂且把def定义的函数叫作“有名函数”,

那什么是匿名函数呢?顾名思义,这类函数没有显示地定义函数名。
认识匿名函数
匿名函数不需要显示地定义函数名,使用【lambda + 参数 +表达式】的方式,
匿名函数语法:

lambda [arg1 [,arg2,.....argn]]:expression

lambda只是一个表达式,函数体比def简单很多。
lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
lambda函数拥有自己的命名空间,且不能访问自有参数列表之外或全局命名空间里的参数。
虽然lambda函数看起来只能写一行,却不等同于C或C++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。
也就是说,lambda用来表示匿名函数,可以传入多个参数,但只能有一个表达式。
下图可以看出匿名函数的独特之处
在这里插入图片描述

比方说,我要写一个函数用于两个数相乘

如果用def方式来写:

>>> def f(x,y):    
...     return x*y
>>> f(2,3)
6

用匿名函数来写:

>>> func = lambda x,y:x*y

可以看到,上面我们把匿名函数对象赋给一个变量,只要直接调用该对象就可以使用匿名函数:

>>> func(2,3)
6

你也可以给匿名函数传入一个参数:

>>> func_2 = lambda x:x^2 
>>> func_2(3)
9

以上对匿名函数作了解释,也举了一些例子用以说明。那么,匿名函数的优点是什么呢?
匿名函数优点:

1. 使用Python写一些脚本时,使用lambda可以省去定义函数的过程,让代码更加精简。
2.对于一些抽象的,不会被别的地方再重复使用的函数,有时候函数起个名字也是个难题,使用lambda不需要考虑命名的问题
3.使用lambda在某些时候然后代码更容易理解

不用取名称,因为给函数取名是比较头疼的一件事,特别是函数比较多的时候
可以直接在使用的地方定义,如果需要修改,直接找到修改即可,方便以后代码的维护工作。
语法结构简单,不用使用def 函数名(参数名):这种方式定义,直接使用lambda 参数:返回值 定义即可。
实例:

# 可写函数说明
sum = lambda arg1, arg2: arg1 + arg2;

# 调用sum函数
print ("相加后的值为 : ", sum( 10, 20 )) # 相加后的值为 :  30
print ("相加后的值为 : ", sum( 20, 20 )) # 相加后的值为 :  40

# 对字典排序
infors = [{"name":"wang","age":10},{"name":"xiaoming","age":20},{"name":"banzhang","age":10}]

infors.sort(key=lambda x:x['age']) #根据age对字典排序

print(infors)

# 排序结果  [{'name': 'wang', 'age': 10}, {'name': 'banzhang', 'age': 10}, {'name': 'xiaoming', 'age': 20}]

# 把lambda当一个变量def test(a,b,func):
    result = func(a,b)
    return result


num = test(11,22,lambda x,y:x+y)
print(num)

4、总结

  1. Python 内置函数
abs()            file()        map()          staticmethod()
all()            filter()      max()          str()    
any()            float()       memoryview()   sum()         
basestring()     format()      min()          super()          
bin()            frozenset()   next()         tuple()       
bool()           getattr()     object()       type()
breakpoint()     globals()     oct()          vars()
bytearray()      hasattr()     open()         zip()
bytes()          hash()        ord()          __import__()
callable()       help()        pow()        
chr()            hex()         print()             
classmethod()    id()          property()         
compile()        input()       range()               
complex()        int()         repr()            
delattr()        isinstance()  reversed()           
dict()           issubclass()  round()                 
dir()            iter()        set()            
divmod()         len()         setattr()          
enumerate()      list()        slice()  
eval()           locals()      sorted()      

内置函数 ,可以python help(),也可以上网查,了解它们的意思与用处。

  1. 定义函数格式:

(1)无参数、无返回值

def 函数名():
   代码

(2)无参数、有返回值

def 函数名():
   语句
   return 需要返回的数值

(3)有参数、无返回值

def 函数名(形参列表):
   语句

(4)有参数、有返回值

def 函数名(形参列表):
   语句
   return 需要返回的数值
  1. 1.全局变量和局部变量使用
def test1():
    a = 300 #局部变量
    print('test1-----------修改前:a = %d'%a)
    a = 100
    print('test1-----------修改后:a = %d'%a)
def test2():
    a = 500 #不同的函数可以定义相同的名字,彼此无关
    print('test2-----------a = %d'%a)

test1()
test2()
  1. 2.全局变量和局部变量相同名字
a = 100 #全局变量
def test1():
    a = 300 #局部变量优先使用
    print('test1-----------修改前:a = %d'%a)
    a = 200
    print('test1-----------修改后:a = %d'%a)
def test2():
    #a = 500 #不同的函数可以定义相同的名字,彼此无关
    print('test2-----------a = %d'%a)

test1()
test2()
  1. 3.全局变量和局部变量相同名字
    局部变量使用全局变量就要使用关键字global
a = 100 #全局变量
def test1():
    global  a 
    #声明全局变量在函数中的标识符,声明之后所有函数访问全局变量的值a
    print(f'test1 修改前:a = {a}')
    a = 200
    print(f'test1 修改后:a = {a}')
def test2():
    print(f'test2 : a = {a}')#没有局部变量,默认使用全局变量 
    #输出test2 : a = 200

test1()
test2()
  1. 函数使用
#函数的定义
def printinfo():
    print('--'*30)
    print('  人生苦短,我用python  ')
    print('--'*30)

#函数的调用
printinfo()
printinfo()

#带参数的函数
def add2Num(a,b):
    c = a + b
    print(c)
add2Num(11,22)

#带返回值的参数
def add2Num(a,b):
   return a + b
result = add2Num(11,22)
print(result)   #33
print(add2Num(11,22))   #33


# 返回多个值的函数
def divid(a,b):
    shang  = a//b
    yushu  = a%b
    return shang,yushu  #多个返回值用逗号分隔
sh,yu = divid(5,2)  #需要使用多个值来保存返回内容
print(f'商:{sh},余数:{yu}')

八、模块


1. 模块的定义

模块包含可执行语句及函数定义。这些语句用于初始化模块,且仅在 import 语句第一次 遇到模块名时执行。(文件作为脚本运行时,也会执行这些语句。
)可以理解为是对代码更高级的封装,即把能够实现某一特定功能的代码编写在同一个 .py文件中,并将其作为一个独立的模块,这样既可以方便其它程序或脚本导入并使用,同时还能有效避免函数名和变量名发生冲突。

模块分为内置模块和自定义模块。
自定义模块:创建模块,只需将所需代码保存在文件扩展名为 .py 的文件中。
我们知道,在定义函数可以为其添加说明文档,以方便用户清楚的知道该函数或者类的功能。
自定义模块也不例外。
为自定义模块添加说明文档,和函数或类的添加方法相同,即只需在模块开头的位置定义一个字符串即可。可以通过模板的__doc__属性,来访问模板的说明文档。

事实上,函数定义也是“执行”的“语句”;模块级函数定义的执行会将函数名称添加到模块的全局命名空间中。
Each module has its own private namespace, which is used as the global namespace by all functions defined in the module. Thus, the author of a module can use global variables in the module without worrying about accidental clashes with a user’s global variables. On the other hand, if you know what you are doing you can touch a module’s global variables with the same notation used to refer to its functions, modname.itemname.
译:[每个模块都有自己的私有命名空间,模块中定义的所有函数都将其用作全局命名空间。因此,模块的作者可以在模块中使用全局变量,而不必担心与用户的全局变量发生意外冲突。另一方面,如果您知道自己在做什么,则可以使用用于引用其函数的相同表示法(modname.itemname)来触摸模块的全局变量。]

Modules can import other modules. It is customary but not required to place all import statements at the beginning of a module (or script, for that matter). The imported module names, if placed atthe top level of a module (outside any functions or classes), are added to the module’s global namespace.

译:[模块可以导入其他模块。习惯上,但不是必须将所有 import 语句放在模块(或脚本,就此而言)的开头。导入的模块名称(如果放置在模块的顶层在任何函数或类外部)将添加到模块的全局命名空间中。]

模块到底指的是什么呢?模块,英文为 Modules,至于模块到底是什么,可以用一句话总结:模块就是 Python 程序。换句话说,任何 Python程序都可以作为模块,包括在前面章节中写的所有 Python 程序,都可以作为模块。
代码的可重用性体现在,当编写好一个模块后,只要编程过程中需要用到该模块中的某个功能(由变量、函数、类实现),无需做重复性的编写工作,直接在程序中导入该模块即可使用该功能。

模块(module)的一些属性:

  1. 模组是包含函数和变量的 Python 文件。(模块可以包含已经描述的函数,但也可以包含各种类型的变量(数组、字典、对象等))
  2. 你可以 import (导入)这个文件。
  3. 然后你可以使用 ‘.’ 操作符访问到模组中的函数和变量。

Python 模块(Module),是一个 Python 文件,以 .py 结尾,包含了 Python 对象定义和Python语句。(这就创建了模块!!!)

模块让你能够有逻辑地组织你的 Python 代码段。

把相关的代码分配到一个模块里能让你的代码更好用,更易懂。

模块能定义函数,类和变量,模块里也能包含可执行的代码。

2. 模块的导入

  1. import 语句
    模块定义好后,我们可以使用 import 语句来引入模块。

语法:

import module1[, module2[,... moduleN]]

一个模块只会被导入一次,不管你执行了多少次import。这样可以防止导入模块被一遍又一遍地执行。
注:相当于导入的是一个文件夹,是个相对路径。

  1. from…import 语句
    Python 的 from 语句让你从模块中导入一个指定的部分到当前命名空间中。

语法:

from modname import name1[, name2[, ... nameN]] 

这提供了一个简单的方法来导入一个模块中的所有项目。然而这种声明不该被过多地使用。
注:相当于导入的是一个文件夹中的文件,是个绝对路径。

例如我们想一次性引入 math 模块中所有的东西,语句如下:

from math import *
  1. from…import…as… 语句
    导入模块成员时,也可以用 as 为成员指定别名(alias)。

语法:

from modname import name1[, as alias1[, name2[, as alias2]] ...
  1. 区别
    import 与 from…import 的区别是当引用文件时是:
import   //模块.函数

from…import  // 直接使用函数名使用就可以了

from…import *:是把一个模块中所有函数都导入进来; 注:相当于:相当于导入的是一个文件夹中所有文件,所有函数都是绝对路径。
结论:
from…import *语句与import区别在于:
import 导入模块,每次使用模块中的函数都要指定是哪个模块。
from…import * 导入模块,每次使用模块中的函数,直接使用函数就可以了;注因为已经知道该函数是那个模块中的了。

  1. 调用模块属性的区别
import 模块名
模块名.xxx = 引用

from 模块名 import *
xxx = 拷贝  # 能修改属性值 

函数,类… : “import 模块名” 和 “from 模块名 import *” 都是引用。

  1. 私有属性两种导入的区别
# . 类中的私有属性
# 本质做了一个名字重整
class test()
    self.__name

__name 名字重整成 _test__name。

_littlethree : 模块的私有属性(数据)。

from 模块 import * : 导入模块时,会跳过私有属性;
import 模块 : 通过引用可以访问私有属性

通俗的理解 __name__== ‘__main__’:
假如你叫小明.py,在朋友眼中,你是小明(__name__== ‘小明’);在你自己眼中,你是你自己
(__name__== ‘__main__’)。
if __name__== '__main__‘的意思是:当 .py 文件被直接运行时,if __name__== '__main__
之下的代码块将被运行; 当 .py 文件以模块形式被导入时,if __name__== '__main__'之下的
代码块不被运行

3. 模块的执行(调用)

可以用以下方式运行 Python 模块:

python fibo.py <arguments>

这项操作将执行模块里的代码,和导入模块一样,但会把 __name__赋值为 “__main__”。 也就是
把下列代码添加到模块末尾:

if __name__ == "__main__":
    import sys
    fib(int(sys.argv[1]))

既可以把这个文件当脚本使用,也可以用作导入的模块, 因为,解析命令行的代码只有在模块以
“main” 文件执行时才会运行:

> python fibo.py 50

输出:
0 1 1 2 3 5 8 13 21 34

导入模块时,不运行这些代码:

>>> import fibo

这种操作常用于为模块提供便捷用户接口,或用于测试(把模块当作执行测试套件的脚本运行)。

调用模块时的区别:

对 module.xxx 的修改在重新 import 后仍然有效:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

print __name__
import math
print math.__name__
math.__name__ = "hello"
print math.__name__
import math
print math.__name__

输出:

__main__
math
hello
hello
from module import xxx

对 module.xxx 的修改在重新 import 后不会起效:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

print __name__
from math import __name__
print __name__
__name__ = "hello"
print __name__
from math import __name__
print __name__

输出:

__main__
math
hello
math

4. 模块搜索路径

当一个名为 spam 的模块被导入时,解释器首先搜索具有该名称的内置模块。这些模块的名字被列在 sys.builtin_module_names中。如果没有找到,它就在变量 sys.path 给出的目录列表中搜索一个名为 spam.py 的文件, sys.path 从这些位置初始化:

输入脚本的目录(或未指定文件时的当前目录)。

PYTHONPATH (目录列表,与 shell 变量 PATH 的语法一样)。

依赖于安装的默认值(按照惯例包括一个 site-packages 目录,由 site 模块处理)。

注解:在支持 symlink 的文件系统中,输入脚本目录是在追加 symlink 后计算出来的。
换句话说,包含 symlink 的目录并 没有 添加至模块搜索路径。
初始化后,Python 程序可以更改sys.path。运行脚本的目录在标准库路径之前,置于搜索路径的开头。即,加载的是该目录里的脚本,而不是标准库的同名模块。除非刻意替换,否则会报错。详见 标准模块。

1. PYTHONPATH 变量

作为环境变量,PYTHONPATH 由装在一个列表里的许多目录组成。PYTHONPATH 的语法和
shell 变量 PATH 的一样。

在 Windows 系统,典型的 PYTHONPATH 如下:

set PYTHONPATH=c:\python27\lib;

在 UNIX 系统,典型的 PYTHONPATH 如下:

set PYTHONPATH=/usr/local/lib/python

5. “已编译的” Python 文件

为了快速加载模块,Python 把模块的编译版缓存在 __pycache__目录中,文件名为 module.version.pyc,version对编译文件格式进行编码,
一般是 Python 的版本号。例如,CPython 的 3.3 发行版中,spam.py 的编译版本缓存为

__pycache__/spam.cpython-33.pyc

使用这种命名惯例,可以让不同 Python 发行版及不同版本的已编译模块共存。

Python 对比编译版本与源码的修改日期,查看它是否已过期,是否要重新编译,此过程完全自动化。
此外,编译模块与平台无关,因此,可在不同架构系统
之间共享相同的支持库。
Python 在两种情况下不检查缓存。
其一,从命令行直接载入模块,只重新编译,不存储编译结果;
其二,没有源模块,就不会检查缓存。为了支持无源文件(仅编译)发行版本, 编译模块必须在源目录下,并且绝不能有源模块。

给专业人士的一些小建议:
在 Python 命令中使用 -O 或 -OO 开关,可以减小编译模块的大小。-O 去除断言语句,
-OO 去除断言语句和 __doc__字符串。有些程序可能依赖于这些内容,因此,没有十足的把握,不要使用这两个选项。
“优化过的”模块带有 opt- 标签,并且文件通常会一小些。将来的发行版或许会改进优化的效果。
从 .pyc 文件读取的程序不比从 .py 读取的执行速度快,.pyc 文件只是加载速度更快。
compileall 模块可以为一个目录下的所有模块创建 .pyc 文件。

6. 标准模块

Python 自带一个标准模块的库,它在 Python 库参考(此处以下称为"库参考" )里另外描述。
一些模块是内嵌到编译器里面的, 它们给一些虽并非语言核心但却内嵌的操作提供接口,要么是为了效率,要么是给操作系统基础操作例如系统调入提供接口。 这些模块集是一个配置选项,
并且还依赖于底层的操作系统。例如,winreg 模块只在 Windows 系统上提供。
一个特别值得注意的模块 sys,它被内嵌到每一个 Python 编译器中。
sys.ps1 和 sys.ps2 变量定义了一些字符,它们可以用作主提示符和辅助提示符:

>>> import sys
>>> sys.ps1
'>>> '
>>> sys.ps2
'... '
>>> sys.ps1 = 'C> '
C> print('Yuck!')
Yuck!
C>

只有解释器用于交互模式时,才定义这两个变量。

变量 sys.path 是字符串列表,用于确定解释器的模块搜索路径。该变量以环境变量
PYTHONPATH 提取的默认路径进行初始化,如未设置 PYTHONPATH,则使用内置的
默认路径。可以用标准列表操作修改该变量:

>>> import sys
>>> sys.path.append('/ufs/guido/lib/python')

7.各类内置模块简介

  1. dir()函数
    内置函数 dir() 用于查找模块定义的名称。返回结果是经过排序的字符串列表。
    可以理解为:dir() 函数一个排好序的字符串列表,内容是一个模块里定义过的名字。
    返回的列表容纳了在一个模块里定义的所有模块,变量和函数。
  2. globals() 和 locals() 函数
    根据调用地方的不同,globals() 和 locals() 函数可被用来返回全局和局部命名空间里的名字。
    如果在函数内部调用 locals(),返回的是所有能在该函数里访问的命名。
    如果在函数内部调用 globals(),返回的是所有在该函数里能访问的全局名字。
    两个函数的返回类型都是字典。所以名字们能用 keys() 函数摘取。
  3. reload() 函数
    当一个模块被导入到一个脚本,模块顶层部分的代码只会被执行一次。
    因此,如果你想重新执行模块里顶层部分的代码,可以用 reload() 函数。该函数会
    重新导入之前导入过的模块。

语法:

reload(module_name)
  1. 系统相关的信息模块: import sys
sys.argv    是一个 list,包含所有的命令行参数.    

sys.stdout sys.stdin sys.stderr  分别表示标准输入输出,错误输出的文件对象.    

sys.stdin.readline()    从标准输入读一行 sys.stdout.write("a") 屏幕输出a    

sys.exit(exit_code)  退出程序    

sys.modules    是一个dictionary,表示系统中所有可用的module    

sys.platform   得到运行的操作系统环境    

sys.path    是一个list,指明所有查找module,package的路径.
  1. 操作系统相关的调用和操作: import os
os.environ 一个dictionary 包含环境变量的映射关系   

os.environ["HOME"] 可以得到环境变量HOME的值     

os.chdir(dir) 改变当前目录 os.chdir('d:\\outlook')   #注意windows下用到转义     

os.getcwd() 得到当前目录     

os.getegid() 得到有效组id os.getgid() 得到组id     

os.getuid() 得到用户id os.geteuid() 得到有效用户id     

os.setegid os.setegid() os.seteuid() os.setuid()     

os.getgruops() 得到用户组名称列表     

os.getlogin() 得到用户登录名称     

os.getenv 得到环境变量     

os.putenv 设置环境变量     

os.umask 设置umask     

os.system(cmd) 利用系统调用,运行cmd命令
  1. 内置模块(不用import就可以直接使用)常用内置函数:
help(obj) 在线帮助, obj可是任何类型    

callable(obj) 查看一个obj是不是可以像函数一样调用    

repr(obj) 得到obj的表示字符串,可以利用这个字符串eval重建该对象的一个拷贝    

eval_r(str) 表示合法的python表达式,返回这个表达式    

dir(obj) 查看obj的name space中可见的name    

hasattr(obj,name) 查看一个obj的name space中是否有name    

getattr(obj,name) 得到一个obj的name space中的一个name    

setattr(obj,name,value) 为一个obj的name   

space中的一个name指向vale这个object    

delattr(obj,name) 从obj的name space中删除一个name    

vars(obj) 返回一个object的name space。用dictionary表示    

locals() 返回一个局部name space,用dictionary表示    

globals() 返回一个全局name space,用dictionary表示    

type(obj) 查看一个obj的类型    

isinstance(obj,cls) 查看obj是不是cls的instance    

issubclass(subcls,supcls) 查看subcls是不是supcls的子类  
  1. 类型转换
chr(i) 把一个ASCII数值,变成字符    

ord(i) 把一个字符或者unicode字符,变成ASCII数值    

oct(x) 把整数x变成八进制表示的字符串    

hex(x) 把整数x变成十六进制表示的字符串    

str(obj) 得到obj的字符串描述    

list(seq) 把一个sequence转换成一个list    

tuple(seq) 把一个sequence转换成一个tuple    

dict(),dict(list) 转换成一个dictionary    

int(x) 转换成一个integer    

long(x) 转换成一个long interger    

float(x) 转换成一个浮点数    

complex(x) 转换成复数    

max(...) 求最大值    

min(...) 求最小值 

8. 包

包是一种用“点式模块名”构造 Python 模块命名空间的方法。例如,模块名 A.B 表示包 A 中名为 B的子模块。正如模块可以区分不同模块之间的全局变量名称一样,点式模块名可以区分 NumPy 或 Pillow 等不同多模块包之间的模块名称。

包的本质依然是模块,包可以包含包。Python 库:相比模块和包,库是一个更大的概念,例如在 Python 标准库中的每个库都有好多个包,而每个包中都有若干个模块。

包是一个分层次的文件目录结构,它定义了一个由模块及子包,和子包下的子包等组成的 Python 的应用环境。
简单来说,包就是文件夹,但该文件夹下必须存在 __init__.py 文件, 该文件的内容可以为空。__init__.py 用于标识当前文件夹是一个包。

通常情况下,当使用 import 语句导入模块后,Python 会按照以下顺序查找指定的模块文件:

  1. 在当前目录,即当前执行的程序文件所在目录下查找;
  2. 到 PYTHONPATH(环境变量)下的每个目录中查找;
  3. 到 Python 默认的安装目录下查找。

以上所有涉及到的目录,都保存在标准模块 sys 的 sys.path
变量中,通过此变量我们可以看到指定程序文件支持查找的所有目录。换句话说,如果要导入的模块没有存储在 sys.path显示的目录中,那么导入该模块并运行程序时,Python 解释器就会抛出 ModuleNotFoundError(未找到模块)异常。

解决“Python找不到指定模块”的方法有 3 种,分别是:

  1. 向 sys.path 中临时添加模块文件存储位置的完整路径;
  2. 将模块放在 sys.path 变量中已包含的模块加载路径中;
  3. 设置 path 系统环境变量。

假设要为统一处理声音文件与声音数据设计一个模块集(“包”)。声音文件的格式很多(通常以扩展名来识别,例如:.wav, .aiff, .au),因此,为了不同文件格式之
间的转换,需要创建和维护一个不断增长的模块集合。为了实现对声音数据的不同处理(例如,混声、添加回声、均衡器功能、创造人工立体声效果),还要编写无穷无尽的模块流。下面这个分级文件树展示了这个包的架构:

sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

导入包时,Python 搜索 sys.path 里的目录,查找包的子目录。

Python 只把含 __init__.py 文件的目录当成包。这样可以防止以 string 等通用名称
命名的目录,无意中屏蔽出现在后方模块搜索路径中的有效模块。 最简情况下,
__init__.py只是一个空文件,但该文件也可以执行包的初始化代码,或设置 __all__变量,
详见下文。

还可以从包中导入单个模块,例如:

import sound.effects.echo

这段代码加载子模块 sound.effects.echo ,但引用时必须使用子模块的全名:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4)

另一种导入子模块的方法是 :

from sound.effects import echo

这段代码还可以加载子模块 echo ,不加包前缀也可以使用。因此,可以按如下方式使用:

echo.echofilter(input, output, delay=0.7, atten=4)

Import 语句的另一种变体是直接导入所需的函数或变量:

from sound.effects.echo import echofilter

同样,这样也会加载子模块 echo,但可以直接使用函数 echofilter():

echofilter(input, output, delay=0.7, atten=4)

注意,使用 from package import item 时,item 可以是包的子模块(或子包),也可以是包中定义的函数、类或变量等其他名称。import 语句首先测试包中是否定义了 item;如果未在包中定义,则假定 item 是模块,并尝试加载。如果找不到 item,则触发 ImportError 异常。

相反,使用 import item.subitem.subsubitem 句法时,除最后一项外,每个 item 都必须是包;最后一项可以是模块或包,但不能是上一项中定义的类、函数或变量。

用例:
在 package_runoob 目录下的 runoob1.py、runoob2.py、__init__.py 文件,test.py
为测试调用包的代码,目录结构如下:

test.py
package_runoob
|-- __init__.py
|-- runoob1.py
|-- runoob2.py

源代码如下:

package_runoob/runoob1.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
def runoob1():
    print "I'm in runoob1"

package_runoob/runoob2.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
def runoob2():
    print "I'm in runoob2"

现在,在 package_runoob 目录下创建 __init__.py:

package_runoob/__init__.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
if __name__ == '__main__':
    print '作为主程序运行'
else:
    print 'package_runoob 初始化'

然后我们在 package_runoob 同级目录下创建 test.py 来调用 package_runoob 包

test.py

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
# 导入 Phone 包
from package_runoob.runoob1 import runoob1
from package_runoob.runoob2 import runoob2
 
runoob1()
runoob2()

以上实例输出结果:

package_runoob 初始化
I'm in runoob1
I'm in runoob2

1. 从包中导入 *

使用 from sound.effects import * 时会发生什么?理想情况下,该语句在文件系统查找
并导入包的所有子模块。这项操作花费的时间较长,并且导入子模块可能会产生不必要的副作用,这种副作用只有在显式导入子模块时才会发生。
唯一的解决方案是提供包的显式索引。import 语句使用如下惯例:如果包的 __init__.py 代码定义了列表 __all__,运行 from package import * 时,它就是用于导入的模块名列表。发布包的新版本时,包的作者应更新此列表。如果包的 作者认为没有必要在包中执行导入 * 操作, 也可以不提供此列表。
例如,sound/effects/__init__.py 文件包含以下代码:

__all__ = ["echo", "surround", "reverse"]

这将意味着将 from sound.effects import * 导入sound.effects 包的三个命名的子模块。
如果没有定义 __all__,from sound.effects import * 语句 不会 把包 sound.effects 中所有子模块都导入到当前命名空间;该语句只确保导入包 sound.effects (可能还会运行 __init__.py 中的初始化代码),然后,再导入包中定义的名称。这些名称包括 __init__.py中定义的任何名称(以及显式加载的子模块),还包括之前 import 语句显式加载的包里的子模块。
请看以下代码:

import sound.effects.echo
import sound.effects.surround
from sound.effects import *

本例中,执行 from…import 语句时,将把 echo 和 surround 模块导入至当前命名空间,
因为,它们是在 sound.effects 包里定义的。(该导入操作在定义了__all__时也有效。)
虽然,可以把模块设计为用 import * 时只导出遵循指定模式的名称,但仍不提倡在生产代码中使用这种做法。
记住,使用 from package import specific_submodule 没有任何问题! 实际上,除了导入模块使用不同包的同名子模块之外,这种方式是推荐用法。

2. 子包参考

包中含有多个子包时(与示例中的 sound 包一样),可以使用绝对导入引用兄弟包中的子模块。
例如,要在模块 sound.filters.vocoder 中使用sound.effects 包的 echo 模块时,可以用from sound.effects import echo 导入。
还可以用 import 语句的 from module import name 形式执行相对导入。这些导入语句使用前导句点表示相对导入中的当前包和父包。例如,相对于surround 模块,可以使用:

from . import echo
from .. import formats
from ..filters import equalizer

注意,相对导入基于当前模块名。因为主模块名是 “__main__” ,所以 Python 程序的主模块
必须始终使用绝对导入。

3. 多目录中的包

包支持一个更特殊的属性 __path__。在包的 :file:__init__.py 文件中的代码被执行前,
该属性被初始化为包含 :file:__init__.py
文件所在的目录名在内的列表。可以修改此变量;但这样做会影响在此包中搜索子模块和子包。

这个功能虽然不常用,但可用于扩展包中的模块集。

Python 模块索引:https://docs.python.org/zh-cn/3/py-modindex.html 可找到对应各模块的解释与用法。
模块变化可参考:https://docs.python.org/zh-cn/3/contents.html 新增去除对应目录可了解。

九、文件操作


关于文件,它有两个关键属性,分别是“文件名”和“路径”。其中,文件名指的是为每个文件设定的名称,而路径则用来指明文件在计算机上的位置。
文件的操作有很多种,常见的操作包括创建、删除、修改权限、读取、写入等,这些操作可大致分为以下 2 类:

  1. 删除、修改权限:作用于文件本身,属于系统级操作。
  2. 写入、读取:是文件最常用的操作,作用于文件的内容,属于应用级操作。

其中,对文件的系统级操作功能单一,比较容易实现,可以借助 Python中的专用模块(os、sys 等),并调用模块中的指定函数来实现。

文件的应用级操作可以分为以下 3 步,每一步都需要借助对应的函数实现:

  1. 打开文件:使用 open() 函数,该函数会返回一个文件对象;
  2. 对已打开文件做读/写操作:读取文件内容可使用 read()、readline()
    以及readlines() 函数;向文件中写入内容,可以使用 write() 函数。
  3. 关闭文件:完成对文件的读/写操作之后,最后需要关闭文件,可以使用close() 函数。

一个文件,必须在打开之后才能对其进行操作,并且在操作结束之后,还应该将其关闭,这 3 步的顺序不能打乱。

1.文件打开 open 函数

学过了 input 和 argv,这些是你开始学习读取文件的必备基础。你可能需要多多实验才能明白它的工作原理,所以你要细心做练习,并且仔细检查结果。处理文件需要非常仔细,如果不仔细的话,你可能会吧有用的文件弄坏或者清空。导致前功尽弃。

我们要做的是把文件用我们的脚本“打开 (open)”,现在请在终端命令行运行pydoc open 来读读它的说明。你可以看到它和你自己的脚本、或者input 命令类似,它会接受一个参数,并且返回一个值,你可以将这个值赋予一个变量。这就是你打开文件的过程。
我们会调用一个函数。从 open 获得的东西是一个 file (文件),文件本身也支持一些命令。它接受命令的方式是使用句点 . (英文称作 dot 或者period),紧跟着你的命令,然后是类似open和input一样的参数。不同点是: 当你说 f.read时,你的意思其实是:“嘿f!执行你的 read命令,无需任何参数!”
这里使用了“命令”这个词,不过实际上它们的名字是“函数(function)”和“方法method)”。

语法:

fileobject = open(name[, mode[, buffering[, encoding[,  errors[, newline[, closefd[, opener[,]]]]]]]])      
参数          描述
name:       name变量是一个包含了你要访问的文件名称的字符串值。
mode:       可选,文件打开模式
buffering:  设置缓冲
encoding:   一般使用 UTF-8
errors:     报错级别
newline:    区分换行符
closefd:    传入的file参数类型
opener: 设置自定义开启器,开启器的返回值必须是一个打开的文件描述符。

备注:
如果 buffing 参数的值为 0(或者False),则表示在打开指定文件时不使用缓冲区;如果 buffing参数值为大于 1的整数,该整数用于指定缓冲区的大小(单位是字节);如果 buffing参数的值为负数,则代表使用默认
的缓冲区大小。

文件打开模式 mode=‘模式’:
注意:不要忘了引号!

模式    描述

t     文本模式 (默认)。

x     写模式,新建一个文件,如果该文件已存在则会报错。

b     二进制模式。

+     打开一个文件进行更新(可读可写)。

U     通用换行模式(不推荐)。

r     以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式。

rb    以二进制格式打开一个文件用于只读。文件指针将会放在文件的开头。这是默认模式。一般用于非文本文件如图片等。

r+    打开一个文件用于读写。文件指针将会放在文件的开头。

rb+   以二进制格式打开一个文件用于读写。文件指针将会放在文件的开头。一般用于非文本文件如图片等。

w     打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。

wb    以二进制格式打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。

w+    打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。

wb+   以二进制格式打开一个文件用于读写。如果该文件已存在则打开文件,并从开头开始编辑,即原有内容会被删除。如果该文件不存在,创建新文件。一般用于非文本文件如图片等。

a     打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。如果该文件不存在,创建新文件进行写入。
   
ab    以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。也就是说,新的内容将会被写入到已有内容之后。件不如果该文存在,创建新文件进行写入。

a+    打开一个文件用于读写。如果该文件已存在,文件指针将会放在文件的结尾。文件打开时会是追加模式。如果该文件不存在,创建新文件用于读写。

ab+   以二进制格式打开一个文件用于追加。如果该文件已存在,文件指针将会放在文件的结尾。如果该文件不存在,创建新文件用于读写。

open()文件对象常用的属性
成功打开文件之后,可以调用文件对象本身拥有的属性获取当前文件的部分信息,其常见的属性为:

file.name:返回文件的名称;
file.mode:返回打开文件时,采用的文件打开模式;
file.encoding:返回打开文件时使用的编码格式;
file.closed:判断文件是否己经关闭。

2.文件读取 read函数

从一个打开的文件中逐个字节(或者逐个字符)读取文件中的内容。

语法:

fileObject.read([size])

用于读取文件内容,它会读取一些数据,并返回字符串(文本模式),或字节串对象(在二进制模式下)。 size 是可选的数值参数。省略 size 或 size 为负数时,读取并返回整个文件的内容;文件大小是内存的两倍时,会出现问题。size 取其他值时,读取并返回最多 size 个字符(文本模式)或 size个字节(二进制模式)。如已到达文件末尾,fileObject.read()返回空字符串(‘’)。被传递的参数是要从已打开文件中读取的字节计数。该方法从文件的开头开始读入,如果没有传入size,它会尝试尽可能多地读取更多的内容,很可能是直到文件的末尾。

语法:

fileObject.readline([size])

从文件中读取单行数据;字符串末尾保留换行符(\n),只有在文件不以换行符结尾时,文件的最后一行才会省略换行符。这种方式让返回值清晰明确;只要fileObject.readline()返回空字符串,就表示已经到达了文件末尾,空行用 '\n’表示,该字符串只包含一个换行符。size 为可选参数,用于指定读取每一行时,一次最多读取的字符(字节)数。

列表形式语法:list(fileObject)

fileObject.readlines([sizeint])

读取所有行并返回列表,若给定sizeint>0,则是设置一次读多少字节,这是为了减轻读取压力。

从文件中读取多行时,可以用循环遍历整个文件对象。这种操作能高效利用内存,快速,且代码简单:

>>> for line in fileObject:
...     print(line, end='')
...
This is the first line of the file.
Second line of the file

3.文件写入 write函数

可以向文件中写入指定内容。

语法:

fileObject.write([string])

fileObject表示已经打开的文件对象;string表示要写入文件的字符串(或字节串,仅适用写入二进制文件中)。把 string的内容写入文件,并返回写入的字符数。
写入其他类型的对象前,要先把它们转化为字符串(文本模式)或字节对象(二进制模式)。

注意,在使用 write() 向文件中写入数据,需保证使用 open() 函数是以r+、w、w+、a 或 a+ 的模式打开文件,否则执行 write() 函数会抛出 io.UnsupportedOperation 错误。

语法:

fileObject.writelines([stringlist])

向文件写入一个序列字符串列表。
需要注意的是,使用 writelines()函数向文件中写入多行数据时,不会自动给各行添加换行符。如果需要换行则要自己加入每行的换行符。

4.文件位置 指针函数

首先来了解一下什么是文件指针。
我们知道,使用 open() 函数打开文件并读取文件中的内容时,总是会从文件的第一个字符(字节)开始读起。那么,有没有办法可以自定指定读取的起始位置呢?答案是肯定,这就需要移动文件指针的位置。
Windows环境下,当我们以 ‘w’,‘w+’ 等开启文件写入模式的时候,由于是 ‘w’,所以文件会被清空,也就是文件为空,初始状态指针为 0 ,也就是初始即为 EOF 位置。也就是下图中Please的字母P的前面和句点的后面。
文件指针用于标明文件读写的起始位置。

在这里插入图片描述

可以看到,通过移动文件指针的位置,再借助 read() 和 write()函数,就可以轻松实现,读取文件中指定位置的数据或者向文件中的指定位置写入数据。

注意:当向文件中写入数据时,如果不是文件的尾部,写入位置的原有数据不会自行向后移动,新写入的数据会将文件中处于该位置的数据直接覆盖掉。

Python 是使用 fopen/fread/fwrite 这系列函数来读写文件的,经查阅 MSDN:
http://msdn.microsoft.com/en-us/library/yeby3zcb.aspx
经查阅, 里面有一句对写入后读取操作的一句表述:When you switch from writing to reading, you must use an intervening call to either fflush or to a file positioning function. 这句话给出了解决方法,也就是write后执行read方法,我们需要先执行flush()方法或指定一个文件读取为止。

改进以后的代码如下:

fo = open("foo.txt", "w+")
fo.write('Write content.')
fo.flush()
fo.seek(0)#指定指针为止为文件开头
print fo.read()

1. tell() 函数

语法:

fileObject.tell()

文件指针自动移动多少个位置。
当使用 open() 函数打开文件时,文件指针的起始位置为0,表示位于文件的开头处,当使用 read() 函数从文件中读取 n 个字符之后,文件指针同时向后移动了 n 个字符的位置。这就表明,当程序使用文件对象读写数据时,文件指针会自动向后移动:读写了多少个数据,文件指针就自动向后移动多少个位置。
在同一目录下,编写如下程序对 a.txt 文件做读取操作,a.txt 文件中内容为:

Nice to meet you!
Nice to meet you too!
Fine, thank you!

读取 a.txt 的代码如下:

f = open("a.txt",'r')
print(f.tell())
print(f.read(3))
print(f.tell())

2. seek()函数

语法:

fileObject.seek(offset[, whence])

将文件指针移动至指定位置。
whence:作为可选参数,用于指定文件指针要放置的位置,该参数的参数值有3 个选择:0 代表文件头(默认值)、1 代表当前位置、2 代表文件尾。
offset:表示相对于 whence 位置文件指针的偏移量,正数表示向后偏移,负数表示向前偏移。例如,当whence == 0 &&offset == 3(即 seek(3,0)),表示文件指针移动至距离文件开头处 3 个字符的位置;当whence == 1 &&offset == 5(即 seek(5,1)),表示文件指针向后移动,移动至距离当前位置 5 个字符处。
注意:由于程序中使用 seek() 时,使用了非 0的偏移量,因此文件的打开方式中必须包含 b,否则就会报io.UnsupportedOperation 错误。

f = open('a.txt', 'rb')
# 判断文件指针的位置
print(f.tell())
# 读取一个字节,文件指针自动后移1个数据
print(f.read(1))
print(f.tell())
# 将文件指针从文件开头,向后移动到 5 个字符的位置
f.seek(5)
print(f.tell())
print(f.read(1))
# 将文件指针从当前位置,向后移动到 5 个字符的位置
f.seek(5, 1)
print(f.tell())
print(f.read(1))
# 将文件指针从文件结尾,向前移动到距离 2 个字符的位置
f.seek(-1, 2)
print(f.tell())
print(f.read(1))

3. rewind()函数

倒回读取指针。俗称倒带。
用例:

from sys import argv

script, input_file = argv

def print_all(f):
 	print(f.read())
 	
def rewind(f):
	f.seek(0)

def print_a_line(line_count, f):
	print(line_count, f.readline())

current_file = open(input_file)

print("First let's print the whole file:\n")

print_all(current_file)

print("Now let's rewind, kind of like a tape.")

rewind(current_file)

print("Let's print three lines:")

current_line = 1
print_a_line(current_line, current_file)

current_line = current_line + 1
print_a_line(current_line, current_file)

current_line = current_line + 1
print_a_line(current_line, current_file)

5.文件保存和关闭 close函数

语法:

fileObject.close()

其中,file 表示已打开的文件对象。在open()文件开始,read(),write()等处理文件完之后,我们需要保存文件,只要执行close()即可。我们需要 保存文件,只要执行close()即可。不执行不保存。同时可以关闭访问的文件。

在 write 内容后,直接 read 文件输出会为空,是因为指针已经在内容末尾。
两种解决方式:
其一,先 close 文件,open 后再读取,
其二,可以设置指针回到文件最初后再 read

import os

document = open("test.txt", "w+")
print("文件名: ", document.name)
document.write("这是我创建的第一个测试文件!\nwelcome!")
print(document.tell())
#输出当前指针位置
document.seek(os.SEEK_SET)
#设置指针回到文件最初
context = document.read()
print(context)
document.close()

为了保证无论是否出错都能正确地关闭文件,我们可以使用 try … finally 来实现:

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

但是每次都这么写实在太繁琐,所以,Python 引入了 with 语句来自动帮我们调用
close() 方法:

with open('test.txt', 'r') as f:
    print(f.read())

这和前面的 try … finally 是一样的,但是代码更佳简洁,并且不必调用f.close() 方法。

语法:

wwith expression [as variable]:  
    with-block  

看这个结构我们可以获取至少两点信息 1. as可以省略 2. 有一个句块要执行

使用 with as 语句操作上下文管理器(context anager),它能够帮助我们自动分配并且释放资源。
简单的理解,同时包含 __enter__() 和 __exit__() 方法的对象就是上下文管理器。常见构建上下文管理器的方式有 2 种,分别是基于类实现和基于生成器实现。
例如,使用 with as 操作已经打开的文件对象(本身就是上下文管理器),无论期间是否抛出异常,都能保证 with as 语句执行完毕后自动关闭已经打开的文件。
用 [] 括起来的部分可以使用,也可以省略。其中,variable 参数用于指定一个变量,该语句会将 expression指定的结果保存到该变量中。
with as 语句中的代码块如果不想执行任何语句,可以直接使用 pass 语句代替。
使用 with as 语句,即便最终没有关闭文件,修改文件内容的操作也能成功。
with…as,就是个python控制流语句,像 if ,while一样。
with…as语句是简化版的 try except finally 语句。

详细了解:https://blog.csdn.net/qiqicos/article/details/79200089?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-79200089-blog-72911564.pc_relevant_vip_default&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2%7Edefault%7EBlogCommendFromBaidu%7ERate-1-79200089-blog-72911564.pc_relevant_vip_default&utm_relevant_index=1

6.文件删除 remove函数

如需删除文件,必须导入 OS 模块,并运行其 os.remove() 函数:

语法:

os.remove("[fileObject]")

为避免出现错误,您可能需要在尝试删除文件之前检查该文件是否存在:

检查文件是否存在,然后删除它:

import os
if os.path.exists("[fileObject]"):
    os.remove("[fileObject]")
else:
    print("The file does not exist")

如需删除整个文件夹,请使用 os.rmdir() 方法:

语法:

os.rmdir("[fileObject]")

提示:您只能删除空文件夹。

十、列表(list)、元组(tuple)、字典(dict)和集合(set)


Python 序列(Sequence)是指按特定顺序依次排列的一组数据,它们可以占用一块连续的内存,也可以分散到多块内存中。Python中的序列类型包括列表(list)、元组(tuple)和集合(set)。字典(dict)属于映射类型。

在 Python 编程中,我们既需要独立的变量来保存一份数据,也需要序列来保存大量数据。

列表(list)和元组(tuple)比较相似,它们都按顺序保存元素,所有的元素占用一块连续的内存,每个元素都有自己的索引,因此列表和元组的元素都可以通过索引(index)来访问。它们的区别在于:列表是可以修改的,而元组是不可修改的。

字典(dict)和集合(set)存储的数据都是无序的,每份元素占用不同的内存,其中字典元素以 key-value 的形式保存。

所谓序列,指的是一块可存放多个值的连续内存空间,这些值按一定顺序排列,可通过每个值所在位置的编号(称为索引)访问它们。
在 Python 中,序列类型包括字符串、列表、元组、集合和字典,这些序列支持以下几种通用的操作,但比较特殊的是,集合和字典不支持索引、切片、相加和相乘操作。
字符串也是一种常见的序列,它也可以直接通过索引访问字符串内的字符。

  1. 序列索引
    序列中,每个元素都有属于自己的编号(索引)。从起始元素开始,索引值从 0 开始递增:
    在这里插入图片描述

Python 还支持索引值是负数,此类索引是从右向左计数,换句话说,从最后一个元素开始计数,从索引值 -1 开始:

在这里插入图片描述

无论是采用正索引值,还是负索引值,都可以访问序列中的任何元素。无非就是正读倒读的区别,一个从0开始,一个从-1开始。

  1. 序列切片

切片操作是访问序列中元素的另一种方法,它可以访问一定范围内的元素,通过切片操作,
可以生成一个新的序列。

序列实现切片操作的语法:

sname[start : end : step]

其中,各个参数的含义分别是:
sname:表示序列的名称;
start:表示切片的开始索引位置(包括该位置),此参数也可以不指定,会默认为 0, 也就是从序列的开头进行切片;
end:表示切片的结束索引位置(不包括该位置),如果不指定,则默认为序列的长度;
step:表示在切片过程中,隔几个存储位置(包含当前位置)取一次元素,也就是说,如果step 的值大于1,则在进行切片去序列元素时,会“跳跃式”的取元素。如果省略设置step 的值,则最后一个冒号就可以省略。

注意:step默认情况下,并且从左向右切片读取索引,取出对应的元素;step为负值时,从右向左切片读取索引,取出对应的元素(负值换个思路:列表反转)。

  1. 序列相加

两种类型相同的序列使用“+”运算符做相加操作,它会将两个序列进行连接,但不会去除重复的元素。
这里所说的“类型相同”,指的是“+”运算符的两侧序列要么都是列表类型,要么都是元组类型,
要么都是字符串。

  1. 序列相乘
    使用数字 n 乘以一个序列会生成新的序列,其内容为原来序列被重复 n 次的结果。
    比较特殊的是,列表类型在进行乘法运算时,还可以实现初始化指定长度列表的功能。

  2. 检查元素是否包含在序列中 in 关键字用来检查某元素是否为序列的成员。

语法:

value in sequence

其中,value 表示要检查的元素,sequence 表示指定的序列。

  1. 和序列相关的内置函数

Python提供了几个内置函数,可用于实现与序列相关的一些常用操作。

函数        功能

len()       计算序列的长度,即返回序列中包含多少个元素。

max()       找出序列中的最大元素。注意,对序列使用sum()函数时,做加和操作的必须都是数字,不能是字符或字符串,否则该函数将抛出异常,因为解释器无法判定是要做连接操作(+ 运算符可以连接两个序列),还是做加和操作。

min()       找出序列中的最小元素。

list()      将序列转换为列表。

str()       将序列转换为字符串。

sum()       计算元素和。

sorted()    对元素进行排序。

reversed()  反向序列中的元素。

enumerate() 将序列组合为一个索引序列,多用在 for 循环中。

1.list列表详解

数组(Array),将一组(不只一个)数据存储起来。它就可以把多个数据挨个存储到一起,通过数组下标可以访问数组中的每个元素。
Python 中没有数组,但是加入了更加强大的列表。如果把数组看做是一个集装箱,那么 Python 的列表就是一个工厂的仓库。
大部分编程语言都支持数组,比如C语言、C++、Java、PHP、JavaScript 等。

从形式上看,列表会将所有元素都放在一对中括号[ ]里面,相邻元素之间用逗号,分隔。
列表是一个有序且可更改的集合。

语法:

[element1, element2, element3, ..., elementn]

element1 ~ elementn 表示列表中的元素,个数没有限制,只要是 Python 支持的数据类型就可以。
从内容上看,列表可以存储整数、小数、字符串、列表、元组等任何类型的数据,并且同一个列表中元素的类型也可以不同。

注意,在使用列表时,虽然可以将不同类型的数据放入到同一个列表中,但通常情况下不这么做,同一列表中只放入同一类型的数据,这样可以提高程序的可读性。

1. 创建列表

  1. 使用 [ ] 直接创建列表
    使用[ ]创建列表后,一般使用=将它赋值给某个变量:

语法:

listname = [element1, element2, element3, ..., elementn]

其中,listname 表示变量名,element1 ~ elementn 表示列表元素。

  1. 使用 list() 函数创建列表

Python 内置的函数 list(),可以将其它数据类型转换为列表类型。

#将字符串转换成列表
list1 = list("hello")
print(list1)

#将元组转换成列表
tuple1 = ('Python', 'Java', 'C++', 'JavaScript')
list2 = list(tuple1)
print(list2)

#将字典转换成列表
dict1 = {'a':100, 'b':42, 'c':9}
list3 = list(dict1)
print(list3)

#将区间转换成列表
range1 = range(1, 6)
list4 = list(range1)
print(list4)

#创建空列表
print(list())

运行结果:

['h', 'e', 'l', 'l', 'o']
['Python', 'Java', 'C++', 'JavaScript']
['a', 'b', 'c']
[1, 2, 3, 4, 5]
[]
  1. 双括号:list(()) 构造函数

实例
使用 list(()) 构造函数创建列表:

thislist = list(("apple", "banana", "cherry")) # 请注意双括号
print(thislist)

输出:

['apple', 'banana', 'cherry']

2. 访问列表元素

列表是 Python 序列的一种,我们可以使用索引(Index)访问列表中的某个元素(得到的是一个元素的值),也可以使用切片访问列表中的一组元素(得到的是一个新的子列表)。

  1. 使用索引访问列表元素

语法:

listname[i]

其中,listname 表示列表名字,i 表示索引值。列表的索引可以是正数,也可以是负数。

  1. 使用切片访问列表元素

语法:

listname[start : end : step]

其中,listname 表示列表名字,start 表示起始索引,end 表示结束索引,step 表示步长。

列表(list)切片操作详例

a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# 切取列表单个值
print(a[1])
print(a[-3])

输出:

1
7
# 切取列表完整对象
print(a[:])         # 从左往右
print(a[: :])       # 从左往右
print(a[: : -1])    # 从右往左

输出:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
# print("start_index和end_index都为正(+)索引:")
print(a[1:6])       
# step=1,从左往右取值,start_index=1到end_index=6同样表示从左往右取值。

输出:

[1, 2, 3, 4, 5]
# step=-1
print(a[1:5:-1])    
# step=-1,决定了从右往左取值,而start_index=1到end_index=5决定了从左往右取值,互相矛盾。

输出:

[]
# 切片左>右
print(a[5:1])       
# step=1,决定了从左往右取值,而start_index=5到end_index=1决定了从右往左取值,互相矛盾。

输出:

[]
# start未取值
print(a[:5] )       
# step=1,从左往右取值,从“起点”开始一直取到end_index=5。
print(a[:5:-1])     
# step=-1,从右往左取值,从“终点”开始一直取到end_index=5。

输出:

[0, 1, 2, 3, 4]
[9, 8, 7, 6]
# end 未取值
print(a[5:])       
# step=1,从左往右取值,从start_index=5开始,一直取到“终点”。
print(a[5::-1] )    
# step=-1,从右往左取值,从start_index=5开始,一直取到“起点”。

输出:

[5, 6, 7, 8, 9]
[5, 4, 3, 2, 1, 0]
# start_index和end_index都为正(-)索引
print(a[-1:-5])     
# step=1,从左往右取值,而start_index=-1到end_index=-5决定了从右往左取值,两者矛盾。
print(a[-1:-5:-1])  
# step=-1,从右往左取值,start_index=-1到end_index=-5同样是从右往左取值。
print(a[-6:-1])    
# step=1,从左往右取值,而start_index=-6到end_index=-1同样是从左往右取值。

输出:

[]
[9, 8, 7, 6]
[4, 5, 6, 7, 8]
# start未取值
print(a[:-6])      
# step=1,从左往右取值,从“起点”开始一直取到end_index=-6。
print(a[:-6:-1])   
# step=-1,从右往左取值,从“终点”开始一直取到end_index=-6。

输出:

[0, 1, 2, 3]
[9, 8, 7, 6, 5]
# end 未取值
print(a[-6:])     
# step=1,从左往右取值,从start_index=-6开始,一直取到“终点”。
print(a[6::-1])   
# step=-1,从右往左取值,从start_index=6开始,一直取到“起点”。

输出:

[4, 5, 6, 7, 8, 9]
[6, 5, 4, 3, 2, 1, 0]
# start_index和end_index正(+)负(-)混合索引
print(a[1:-6])    
# start_index=1在end_index=-6的左边,因此从左往右取值,而step=1同样决定了从左往右取值。
print(a[1:-6:-1])  
# start_index=1在end_index=-6的左边,因此从左往右取值,但step=-则决定了从右往左取值,两者矛盾 
print(a[-1:6])    
# start_index=-1在end_index=6的右边,因此从右往左取值,但step=1则决定了从左往右取值,两者矛盾。
print(a[-1:6:-1])   
# start_index=-1在end_index=6的右边,因此从右往左取值,而step=-1同样决定了从右往左取值。

输出:

[1, 2, 3]
[]
[]
[9, 8, 7]
# 连续切片,方向很重要!
print(a[:8][2:5][-1:])

输出:

[4]
# 三个参数(start_index、end_index、step)表达式计算
print(a[2+1:3*2:7%3])   # a[2+1:3*2:7%3] = a[3:6:1]

输出:

[3, 4, 5]
# 插入
a[3:3] = ['A','B','C']
print(a)

输出:

[0, 1, 2, ‘A’, ‘B’, ‘C’, 3, 4, 5, 6, 7, 8, 9]
# 修改
a[3] = ['A','B']
print(a)

输出:

[0, 1, 2, [‘A’, ‘B’], 4, 5, 6, 7, 8, 9]
# 替换
a[3:6] = ['A','B']
print(a)

输出:

[0, 1, 2, ‘A’, ‘B’, 6, 7, 8, 9]
# 复制
print(a[:])
#将上面替换后的输出复制

输出:

[0, 1, 2, ‘A’, ‘B’, 6, 7, 8, 9]

注意:
[:]和.copy()都属于“浅拷贝”,只拷贝最外层元素,内层嵌套元素则通过引用,而不是独立分配内存。浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。

# 取奇偶
# 1.取偶数位置
print(a[::2])
# 2.取奇数位置
print(a[1::2])

输出:

[0, 2, 4, 6, 8]
[1, 3, 5, 7, 9]

3. 删除列表

对于已经创建的列表,如果不再使用,可以使用del关键字将其删除。

实际开发中并不经常使用 del 来删除列表,因为 Python 自带的垃圾回收机制会自动销毁无用的列表,即使开发者不手动删除,Python 也会自动将其回收。

del 关键字的语法:

del listname

其中,listname 表示要删除列表的名称。

4. 添加元素

  • 更多的是用来拼接列表,而且执行效率并不高,如果想在列表中插入元素,应该使用下面几个专门的方法。
  1. append()方法添加元素
    append() 方法用于在列表的末尾追加元素。

语法:

listname.append(obj)

其中,listname 表示要添加元素的列表;obj 表示到添加到列表末尾的数据,它可以是单个元素,也可以是列表、元组等。

  1. extend()方法添加元素
    把它们包含的元素在列表末尾逐个添加到列表中。

语法:

listname.extend(obj)

其中,listname 指的是要添加元素的列表;obj 表示到添加到列表末尾的数据,它可以是单个元素,也可以是列表、元组等,但不能是单个的数字。

  1. insert()方法插入元素
    在列表中间某个位置插入元素。

语法:

listname.insert(index , obj)

其中,index 表示指定位置的索引值。insert() 会将 obj 插入到 listname 列表第 index 个元素的位置。

三者区别:
a. 当给 append() 方法传递列表或者元组时,此方法会将它们视为一个整体,作为一个元素添加到列表末尾。
b. extend() 不会把列表或者元祖视为一个整体,而是把它们包含的元素逐个添加到列表末尾。
c. 当插入列表或者元组时,insert() 也会将它们视为一个整体,作为一个元素插入到列表中指定位置。
提示,insert() 主要用来在列表的中间位置插入元素,如果你仅仅希望在列表的末尾追加元素,那我更建议使用 append() 和 extend()。

5. 删除元素

在 Python 列表中删除元素主要分为以下 3 种场景:

  1. 根据目标元素所在位置的索引进行删除,可以使用 del 关键字或者 pop() 方法;
    del 是 Python 中的关键字,专门用来执行删除操作,它不仅可以删除整个列表,还可以删除列表中的某些元素。

【1】 del 关键字的语法:

del listname[index]

其中,listname 表示列表名称,index 表示元素的索引值。

del 也可以删除中间一段连续的元素。

【2】del 关键字切片的语法:

del listname[start : end]

其中,start 表示起始索引,end 表示结束索引。del 会删除从索引 start 到 end 之间的元素,不包括 end 位置的元素。

Python pop() 方法用来删除列表中指定索引处的元素。

【3】pop 关键字的语法:

listname.pop(index)

其中,listname 表示列表名称,index 表示索引值。如果不写 index 参数,默认会删除列表中的最后一个元素,类似于数据结构中的“出栈”操作。

大部分编程语言都会提供和 pop() 相对应的方法,就是 push(),该方法用来将元素添加到列表的尾部,类似于数据结构中的“入栈”操作。
但是 Python 是个例外,Python 并没有提供 push() 方法,因为完全可以使用 append() 来代替 push() 的功能。

  1. 根据元素本身的值进行删除,可使用列表(list类型)提供的 remove() 方法;
    Python 还提供了 remove() 方法,该方法会根据元素本身的值来进行删除操作。

需要注意的是,remove() 方法只会删除第一个和指定值相同的元素,而且必须保证该元素是存在的,否则会引发 ValueError 错误,所以我们在使用 remove() 删除元素时最好提前判断一下。

  1. 将列表中所有元素全部删除,可使用列表(list类型)提供的 clear() 方法。
    Python clear() 用来删除列表的所有元素,也即清空列表。

6. 修改元素

可以每次修改单个元素,也可以每次修改一组元素(多个)。

  1. 修改单个元素
    修改单个元素非常简单,直接对元素赋值即可。请看下面的例子:
nums = [40, 36, 89, 2, 36, 100, 7]
nums[2] = -26  #使用正数索引
nums[-3] = -66.2  #使用负数索引
print(nums)

运行结果:

[40, 36, -26, 2, -66.2, 100, 7]

使用索引得到列表元素后,通过=赋值就改变了元素的值。

  1. 修改一组元素
    Python 支持通过切片语法给一组元素赋值。在进行这种操作时,如果不指定步长(step 参数),Python 就不要求新赋值的元素个数与原来的元素个数相同;这意味,该操作既可以为列表添加元素,也可以为列表删除元素。

下面的代码演示了如何修改一组元素的值:

nums = [40, 36, 89, 2, 36, 100, 7]
#修改第 1~4 个元素的值(不包括第4个元素)
nums[1: 4] = [45.25, -77, -52.5]
print(nums)

运行结果:

[40, 45.25, -77, -52.5, 36, 100, 7]
  1. 切片(slice)赋值
    但是如果使用字符串赋值,Python 会自动把字符串转换成序列,其中的每个字符都是一个元素,请看下面的代码:
s = list("Hello")
s[2:4] = "XYZ"
print(s)

运行结果:

['H', 'e', 'X', 'Y', 'Z', 'o']

使用切片语法时也可以指定步长(step 参数),但这个时候就要求所赋值的新元素的个数与原有元素的个数相同。

7. 复制列表

通过键入 list2 = list1 来复制列表,因为:list2 将只是对 list1 的引用,list1 中所做的更改也将自动在 list2 中进行。

有一些方法可以进行复制,一种方法是使用内置的 List 方法 copy()。

8. 查找元素

Python 列表(list)提供了 index() 和 count() 方法,它们都可以用来查找元素。

  1. index() 方法用来查找某个元素在列表中出现的位置(也就是索引),如果该元素不存在,则会导致 ValueError 错误,所以在查找之前最好使用 count() 方法判断一下。

index() 的语法:

listname.index(obj, start, end)

其中,listname 表示列表名称,obj 表示要查找的元素,start 表示起始位置,end 表示结束位置。

start 和 end 参数用来指定检索范围:
【1】 start 和 end 可以都不写,此时会检索整个列表;
【2】 如果只写 start 不写 end,那么表示检索从 start 到末尾的元素;
【3】 如果 start 和 end 都写,那么表示检索 start 和 end 之间的元素。

index() 方法会返回元素所在列表中的索引值。

  1. count() 方法用来统计某个元素在列表中出现的次数。

语法:

listname.count(obj)

其中,listname 代表列表名,obj 表示要统计的元素。

如果 count() 返回 0,就表示列表中不存在该元素,所以 count() 也可以用来判断列表中的某个元素是否存在。

9. 遍历(迭代)列表

python可以使用 for 循环遍历列表元素,逐个打印列表中的元素。如需确定列表中是否存在指定的项,请使用 in 关键字。
语法:

 for i in list: 
     print(list) 
cities = ["Guangzhou","Beijing","Shanghai","Nanjing"]

for i,city in enumerate(cities):

    print(i+1,city)

输出:

1 Guangzhou
2 Beijing
3 Shanghai
4 Nanjing

10. 列表长度

如需确定列表中有多少项,请使用 len() 方法。

list1 = [1, 2, 3, 4, 5]
print(len(list1))  # 5

array1 = np.array([1, 2, 3, 4, 5])
print(len(array1))  # 5

car_list = ['honda','toyota','suzuki','mazda','subaru']
list_length = len(car_list)
print(list_length)   # 5

多维列表:

list2 = [[1, 2, 3], [4, 5, 6]]
print(len(list2))   # 2

import numpy as np
array2 = np.array(([1, 2, 3], [4, 5, 6]))
print(len(array2))    # 2

还有很多:
使用列表方法实现堆栈非常容易,最后插入的最先取出(“后进先出”)。把元素添加到堆栈的顶端,使用 append() 。从堆栈顶部取出元素,使用 pop() ,不用指定索引。

列表也可以用作队列,最先加入的元素,最先取出(“先进先出”);然而,列表作为队列的效率很低。因为,在列表末尾添加和删除元素非常快,但在列表开头插入或移除元素却很慢(因为所有其他元素都必须移动一位)。
实现队列最好用 collections.deque,可以快速从两端添加或删除元素。

列表推导式创建列表的方式更简洁。常见的用法为,对序列或可迭代对象中的每个元素应用某种操作,用生成的结果创建新的列表;或用满足特定条件的元素创建子序列。
列表推导式的方括号内包含以下内容:一个表达式,后面为一个 for 子句,然后,是零个或多个 for 或 if 子句。结果是由表达式依据 for 和 if 子句求值计算而得出一个新列表。
实际应用中,最好用内置函数替代复杂的流程语句。此时,zip() 函数更好用。

  1. 总结:列表方法
    Python 有一组可以在列表上使用的内建方法。
方法       描述
append()    在列表的末尾添加一个元素

clear()     删除列表中的所有元素

copy()      返回列表的副本

count()     返回具有指定值的元素数量。

extend()    将列表元素(或任何可迭代的元素)添加到当前列表的末尾

index()     返回具有指定值的第一个元素的索引

insert()    在指定位置添加元素

pop()       删除指定位置的元素

remove()    删除具有指定值的元素

reverse()   颠倒列表的顺序

sort()      对列表进行排序

Python 表达式 结果 描述

L[2]           'Taobao'             读取列表中第三个元素
L[-2]          'Runoob'             读取列表中倒数第二个元素
L[1:]          ['Runoob', 'Taobao'] 从第二个元素开始截取列表

Python包含以下函数:

序号    函数             描述
1     cmp(list1, list2) 比较两个列表的元素
2     len(list)         列表元素个数
3     max(list)         返回列表元素最大值
4     min(list)         返回列表元素最小值
5     list(seq)         将元组转换为列表

Python包含以下方法:

序号 方法                   描述
1   list.append(obj)       在列表末尾添加新的对象

2  list.count(obj)         统计某个元素在列表中出现的次数

3  list.extend(seq)        在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)

4  list.index(obj)         从列表中找出某个值第一个匹配项的索引位置

5  list.insert(index, obj) 将对象插入列表

6  list.pop([index=-1])    移除列表中的一个元素(默认最后一个元素),并且返回该元素的值

7  list.remove(obj)        移除列表中某个值的第一个匹配项

8  list.reverse()          反向列表中元素

9  list.sort(cmp=None, key=None, reverse=False) 对原列表进行排序

针对列表无法正常输出汉字的解决方法:

#encoding=utf-8

import json

list_words = [ '你', '我', '他' ]
print( list_words )                                        # 无法正常显示汉字
print( str(list_words).decode( 'string_escape' ) )         # 正常显示汉字

list_words_result = json.dumps( list_words, encoding='UTF-8', ensure_ascii=False )
print( list_words_result )

输出结果为:

['\xe4\xbd\xa0', '\xe6\x88\x91', '\xe4\xbb\x96']
['你', '我', '他']
["你", "我", "他"]

python 创建二维列表,将需要的参数写入 cols 和 rows 即可

list_2d = [[0 for col in range(cols)] for row in range(rows)]
实例:

list_2d = [ [0 for i in range(5)] for i in range(5)]
print(list_2d[0].append(3))
print(list_2d[0].append(5))
print(list_2d[2].append(7))
print(list_2d)

输出:

None
None
None
[[0, 0, 0, 0, 0, 3, 5], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 7], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]]

实例总结:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

list01 = ['runoob', 786, 2.23, 'john', 70.2]
list02 = [123, 'john']

print list01
print list02

# 列表截取

print list01[0]
print list01[-1]
print list01[0:3]

# 列表重复

print list01 * 2

# 列表组合

print list01 + list02

# 获取列表长度

print len(list01)

# 删除列表元素

del list02[0]
print list02

# 元素是否存在于列表中

print 'john' in list02  # True

# 迭代

for i in list01:
    print i

# 比较两个列表的元素

print cmp(list01, list02)

# 列表最大/最小值

print max([0, 1, 2, 3, 4])
print min([0, 1])

# 将元组转换为列表

aTuple = (1,2,3,4)
list03 = list(aTuple)
print list03

# 在列表末尾添加新的元素

list03.append(5)
print list03

# 在列表末尾一次性追加另一个序列中的多个值(用新列表扩展原来的列表)

list03.extend(list01)
print list03

# 统计某个元素在列表中出现的次数

print list03.count(1)

# 从列表中找出某个值第一个匹配项的索引位置

print list03.index('john')

# 将对象插入列表

list03.insert(0, 'hello')
print list03

# 移除列表中的一个元素(默认最后一个元素),并且返回该元素的值

print list03.pop(0)
print list03

# 移除列表中某个值的第一个匹配项

list03.remove(1)
print list03

# 反向列表中元素

list03.reverse()
print list03

# 对原列表进行排序

list03.sort()
print list03

用例:

list4=[123,["das","aaa"],234]
list4
print("aaa" in list4)                  #in只能判断一个层次的元素

输出:

False

用例:

list4=[123,["das","aaa"],234]
list4
print("aaa" in list4[1])           #选中列表中的列表进行判断

输出:

True

用例:

list4=[123,["das","aaa"],234]
list4
print(list4[1][1])

输出:

aaa

遍历嵌套的列表:

num_list = [[1,2,3],[4,5,6]]
for i in num_list:
    for j in i:
        print(j)

输出结果:

1
2
3
4
5
6

列表里 a 与 a[:] 不同。

我们可以通过函数 id() 来查看:

a = [1, 2, 3]
print(a)
print(a[:])
print(id(a))
print(id(a[:]))
print(a is a[:])

输出结果:

[1, 2, 3]
[1, 2, 3]
2059997501952  # id每次都会不一样。
2060422795200  # id每次都会不一样。
False

简单来说,a[:] 是创建 a 的一个副本,这样我们在代码中对 a[:] 进行操作后,就不会
改变 a 的值了。而若直接对 a 进行操作,那么 a 的值会收到一些操作的影响,如 append() 等。

2.tuple元组详解

元组(tuple)是 Python 中另一个重要的序列结构,和列表类似,元组也是由一系列按特定顺序排序的元素组成。

元组和列表(list)的不同之处在于:

  1. 列表的元素是可以更改的,包括修改元素值,删除和插入元素,所以列表是可变序列;
  2. 元组一旦被创建,它的元素就不可更改了,所以元组是不可变序列。
  3. 元组使用小括号,列表使用方括号。
    元组也可以看做是不可变的列表,通常情况下,元组用于保存无需修改的内容。

从形式上看,元组的所有元素都放在一对小括号( )中,相邻元素之间用逗号,分隔,如下所示:

(element1, element2, ... , elementn)

其中 element1~elementn 表示元组中的各个元素,个数没有限制,只要是 Python 支持的数据类型就可以。
·
从存储内容上看,元组可以存储整数、实数、字符串、列表、元组等任何类型的数据,并且在同一个元组中,元素的类型可以不同,例如:

("c.biancheng.net", 1, [2,'a'], ("abc",3.0))

在这个元组中,有多种类型的数据,包括整形、字符串、列表、元组。

另外,我们都知道,列表的数据类型是 list,那么元组的数据类型是什么呢?我们不妨通过 type() 函数来查看一下:

python(type( ("c.biancheng.net",1,[2,'a'],("abc",3.0)) ))

输出结果:
<class ‘tuple’>
可以看到,元组是 tuple 类型,这也是很多教程中用 tuple 指代元组的原因。

1. 创建元组

  1. 使用 ( ) 直接创建

通过( )创建元组后,一般使用=将它赋值给某个变量,具体格式为:

tuplename = (element1, element2, ..., elementn)

其中,tuplename 表示变量名,element1 ~ elementn 表示元组的元素。

在 Python 中,元组通常都是使用一对小括号将所有元素包围起来的,但小括号不是必须的,只要将各元素用逗号隔开,Python 就会将其视为元组,需要注意的一点是,当创建的元组中只有一个字符串类型的元素时,该元素后面必须要加一个逗号 , 否则 Python 解释器会将它视为字符串。

  1. 创建空元组
tup1 = ()

元组中只包含一个元素时,需要在元素后面添加逗号。

tup1 = (50,)

元组与字符串类似,下标索引从0开始,可以进行截取,组合等。

  1. 使用tuple()函数创建元组
    除了使用( )创建元组外,Python 还提供了一个内置的函数 tuple(),用来将其它数据类型转换为元组类型。

tuple() 的语法:

tuple(data)

其中,data 表示可以转化为元组的数据,包括字符串、元组、range 对象等。

2. 访问元组元素

和列表一样,我们可以使用索引(Index)访问元组中的某个元素(得到的是一个元素的值),也可以使用切片访问元组中的一组元素(得到的是一个新的子元组)。

  1. 使用索引访问元组元素的格式为:
tuplename[i]

通过引用方括号内的索引号来访问元组元素,其中,tuplename 表示元组名字,i 表示索引值。
元组的索引可以是正数,也可以是负数。
负索引表示从末尾开始,-1 表示最后一个项目,-2 表示倒数第二个项目,依此类推。

  1. 使用切片访问元组元素的格式为:
tuplename[start : end : step]

其中,start 表示起始索引,end 表示结束索引,step 表示步长。

tup[n:n]的值为空。

3. 修改元组

前面我们已经说过,元组是不可变序列,元组中的元素不能被修改,所以我们只能创建一个新的元组去替代旧的元组。

  1. 对元组变量进行重新赋值。
  2. 还可以通过连接多个元组(使用+可以拼接元组)的方式向元组中添加新元素。
  3. 将元组转换为列表,更改列表,然后将列表转换回元组。
  4. 切片
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Python元组切片
元组(tuple)也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操
作的结果仍是tuple:

print((0, 1, 2, 3, 4, 5)[:3])

输出output : (0, 1, 2)

4. 删除元组

当创建的元组不再使用时,可以通过 del 关键字将其删除。元组中的元素值是不允许删除的。

5. 总结元组

与字符串一样,元组之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。

  1. 元组运算符:
Python 表达式                 结果                               描述
len((1, 2, 3))                 3                               计算元素个数
(1, 2, 3) + (4, 5, 6)         (1, 2, 3, 4, 5, 6)               连接
('Hi!',) * 4                  ('Hi!', 'Hi!', 'Hi!', 'Hi!')     复制
3 in (1, 2, 3)                True                             元素是否存在
for x in (1, 2, 3): print x,   1 2 3                            迭代
  1. 元组索引截取
    因为元组也是一个序列,所以我们可以访问元组中的指定位置的元素,也可以截取索引中的一段元素,如下所示:

元组:

T = ('spam', 'Spam', 'SPAM!')
Python表达式  结果              描述
T[2]        'SPAM!'           读取第三个元素
T[-2]       'Spam'            反向读取,读取倒数第二个元素
T[1:]       ('Spam', 'SPAM!') 截取元素

无关闭分隔符
任意无符号的对象,以逗号隔开,默认为元组,如下实例:

实例(Python 2.0+)

#!/usr/bin/python
 
print 'abc', -4.24e93, 18+6.6j, 'xyz'
x, y = 1, 2
print "Value of x , y : ", x,y

以上实例运行结果:

abc -4.24e+93 (18+6.6j) xyz
Value of x , y : 1 2

元组内置函数
Python元组包含了以下内置函数

序号 方法                描述
1  cmp(tuple1, tuple2)  比较两个元组元素。Python 2 中支持类似 c++ 中 cmp 的写法。Python 3 放弃了这一用法。               
2  len(tuple)           计算元组元素个数。
3  max(tuple)           返回元组中元素最大值。
4  min(tuple)           返回元组中元素最小值。
5  tuple(seq)           将列表转换为元组。

6. 时间元组

Python 程序能用很多方式处理日期和时间,转换日期格式是一个常见的功能。

Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间。

时间间隔是以秒为单位的浮点小数。

每个时间戳都以自从1970年1月1日午夜(历元)经过了多长时间来表示。

Python 的 time 模块下有很多函数可以转换常见日期格式。如函数time.time()用于获取当前时间戳。

时间戳单位最适于做日期运算。但是1970年之前的日期就无法以此表示了。太遥远的日期也不行,UNIX和Windows只支持到2038年

Python函数用一个元组装起来的9组数字处理时间:

序号 字段         值
0   4位数年       2008
1   月            1 到 12
2   日            1到31
3   小时          0到23
4   分钟          0到59
5   秒            0到61 (60或61 是闰秒)
6   一周的第几日   0到6 (0是周一)
7   一年的第几日   1到366 (儒略历)
8   夏令时        -1, 0, 1, -1是决定是否为夏令时的旗帜

上述也就是struct_time元组。这种结构具有如下属性:

序号 属性    值
0  tm_year  2008
1  tm_mon   1 到 12
2  tm_mday  1 到 31
3  tm_hour  0 到 23
4  tm_min   0 到 59
5  tm_sec   0 到 61 (60或61 是闰秒)
6  tm_wday  0到6 (0是周一)
7  tm_yday  1 到 366(儒略历)
8  tm_isdst -1, 0, 1, -1是决定是否为夏令时的旗帜

python中时间日期格式化符号:

%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00-59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身

ime 模块
Time 模块包含了以下内置函数,既有时间处理的,也有转换时间格式的:

序号 函数及描述
1  time.altzone
返回格林威治西部的夏令时地区的偏移秒数。如果该地区在格林威东部会返回负值(如西欧,包括英国)。对夏令时启用地区才能使用。
2  time.asctime([tupletime])
接受时间元组并返回一个可读的形式为"Tue Dec 11 18:07:142008"(2008年12月11日 周二18时07分14秒)的24个字符的字符串。
3  time.clock( )
用以浮点数计算的秒数返回当前的CPU时间。用来衡量不同程序的耗时,比time.time()更有用。
4  time.ctime([secs])
作用相当于asctime(localtime(secs)),未给参数相当于asctime()
5  time.gmtime([secs])
接收时间戳(1970纪元后经过的浮点秒数)并返回格林威治天文时间下的时间元组t。注:t.tm_isdst始终为0
6  time.localtime([secs])
接收时间戳(1970纪元后经过的浮点秒数)并返回当地时间下的时间元组t(t.tm_isdst可取0或1,取决于当地当时是不是夏令时)。
7  time.mktime(tupletime)
接受时间元组并返回时间戳(1970纪元后经过的浮点秒数)。
8  time.sleep(secs)
推迟调用线程的运行,secs指秒数。
9  time.strftime(fmt[,tupletime])
接收以时间元组,并返回以可读字符串表示的当地时间,格式由fmt决定。
10 time.strptime(str,fmt='%a %b %d %H:%M:%S %Y')
根据fmt的格式把一个时间字符串解析为时间元组。
11 time.time( )
返回当前时间的时间戳(1970纪元后经过的浮点秒数)。
12 time.tzset()

根据环境变量TZ重新初始化时间相关设置。
Time模块包含了以下2个非常重要的属性:

序号 属性及描述
1  time.timezone
属性 time.timezone 是当地时区(未启动夏令时)距离格林威治的偏移秒数(>0,美洲<=0大部分欧洲,亚洲,非洲)。
2  time.tzname
属性time.tzname包含一对根据情况的不同而不同的字符串,分别是带夏令时的本地时区名称,和不带的。

日历(Calendar)模块
此模块的函数都是日历相关的,例如打印某月的字符月历。

星期一是默认的每周第一天,星期天是默认的最后一天。更改设置需调用
calendar.setfirstweekday()函数。模块包含了以下内置函数:

序号 函数及描述
1  calendar.calendar(year,w=2,l=1,c=6)
返回一个多行字符串格式的year年年历,3个月一行,间隔距离为c。 每日宽度间隔为w字符。
每行长度为21* W+18+2* C。l是每星期行数。
2  calendar.firstweekday( )
返回当前每周起始日期的设置。默认情况下,首次载入 calendar 模块时返回 0,即星期一。
3  calendar.isleap(year)
是闰年返回 True,否则为 False。
>>> import calendar
>>> print(calendar.isleap(2000))
True
>>> print(calendar.isleap(1900))
False
4  calendar.leapdays(y1,y2)
返回在Y1,Y2两年之间的闰年总数。
5  calendar.month(year,month,w=2,l=1)
返回一个多行字符串格式的year年month月日历,两行标题,一周一行。每日宽度间隔为w字符。
每行的长度为7* w+6。l是每星期的行数。
6  calendar.monthcalendar(year,month)
返回一个整数的单层嵌套列表。每个子列表装载代表一个星期的整数。Year年month月外的日期都
设为0;范围内的日子都由该月第几日表示,从1开始。
7  calendar.monthrange(year,month)
返回两个整数。第一个是该月的星期几的日期码,第二个是该月的日期码。日从0(星期一)到6
(星期日);月从1到12。
8  calendar.prcal(year,w=2,l=1,c=6)
相当于 print calendar.calendar(year,w=2,l=1,c=6)。
9  calendar.prmonth(year,month,w=2,l=1)
相当于 print calendar.month(year,month,w=2,l=1) 。
10 calendar.setfirstweekday(weekday)
设置每周的起始日期码。0(星期一)到6(星期日)。
11 calendar.timegm(tupletime)
和time.gmtime相反:接受一个时间元组形式,返回该时刻的时间戳(1970纪元后经过的浮点秒数)。
12 calendar.weekday(year,month,day)
返回给定日期的日期码。0(星期一)到6(星期日)。月份为 1(一月) 到 12(12月)。

其他相关模块和函数
在Python中,其他处理日期和时间的模块还有:

datetime模块:
https://docs.python.org/3/library/datetime.html#module-datetime
pytz模块:
https://pypi.org/project/pytz/
https://www.cnblogs.com/sidianok/p/13756474.html
dateutil模块
http://labix.org/python-dateutil

用例:
实例(Python 2.0+)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import time  # 引入time模块
 
ticks = time.time()
print "当前时间戳为:", ticks

以上实例输出结果:

当前时间戳为: 1459994552.51

获取当前时间
从返回浮点数的时间戳方式向时间元组转换,只要将浮点数传递给如localtime之类的函数。

实例(Python 2.0+)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import time
 
localtime = time.localtime(time.time())
print "本地时间为 :", localtime

以上实例输出结果:

本地时间为 : time.struct_time(tm_year=2016, tm_mon=4, tm_mday=7, 
tm_hour=10, tm_min=3, tm_sec=27, tm_wday=3, tm_yday=98, tm_isdst=0)

获取格式化的时间
你可以根据需求选取各种格式,但是最简单的获取可读的时间模式的函数是asctime():

实例(Python 2.0+)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import time
 
localtime = time.asctime( time.localtime(time.time()) )
print "本地时间为 :", localtime

以上实例输出结果:

本地时间为 : Thu Apr  7 10:05:21 2016

格式化日期
我们可以使用 time 模块的 strftime 方法来格式化日期,:

time.strftime(format[, t])

实例演示:

实例(Python 2.0+)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import time
 
# 格式化成2016-03-20 11:45:39形式
print time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
 
# 格式化成Sat Mar 28 22:24:24 2016形式
print time.strftime("%a %b %d %H:%M:%S %Y", time.localtime()) 
  
# 将格式字符串转换为时间戳
a = "Sat Mar 28 22:24:24 2016"
print time.mktime(time.strptime(a,"%a %b %d %H:%M:%S %Y"))

以上实例输出结果:

2016-04-07 10:25:09
Thu Apr 07 10:25:09 2016
1459175064.0

获取某月日历
Calendar模块有很广泛的方法用来处理年历和月历,例如打印某月的月历:

实例(Python 2.0+)

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
import calendar
 
cal = calendar.month(2016, 1)
print "以下输出2016年1月份的日历:"
print cal

以上实例输出结果:

以下输出20161月份的日历:
    January 2016
Mo Tu We Th Fr Sa Su
             1  2  3
 4  5  6  7  8  9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31

使用datetime模块来获取当前的日期和时间

#!/usr/bin/python
# -*- coding: UTF-8 -*-

import datetime
i = datetime.datetime.now()
print ("当前的日期和时间是 %s" % i)
print ("ISO格式的日期和时间是 %s" % i.isoformat() )
print ("当前的年份是 %s" %i.year)
print ("当前的月份是 %s" %i.month)
print ("当前的日期是  %s" %i.day)
print ("dd/mm/yyyy 格式是  %s/%s/%s" % (i.day, i.month, i.year) )
print ("当前小时是 %s" %i.hour)
print ("当前分钟是 %s" %i.minute)
print ("当前秒是  %s" %i.second)
#!/usr/bin/python
# -*- coding: UTF-8 -*-

import time
import calendar


"""
    时间元组(年、月、日、时、分、秒、一周的第几日、一年的第几日、夏令时)
        一周的第几日: 0-6
        一年的第几日: 1-366
        夏令时: -1, 0, 1
"""

"""
    python中时间日期格式化符号:
    ------------------------------------
    %y 两位数的年份表示(00-99)
    %Y 四位数的年份表示(000-9999)
    %m 月份(01-12)
    %d 月内中的一天(0-31)
    %H 24小时制小时数(0-23)
    %I 12小时制小时数(01-12)
    %M 分钟数(00=59)
    %S 秒(00-59)
    %a 本地简化星期名称
    %A 本地完整星期名称
    %b 本地简化的月份名称
    %B 本地完整的月份名称
    %c 本地相应的日期表示和时间表示
    %j 年内的一天(001-366)
    %p 本地A.M.或P.M.的等价符
    %U 一年中的星期数(00-53)星期天为星期的开始
    %w 星期(0-6),星期天为星期的开始
    %W 一年中的星期数(00-53)星期一为星期的开始
    %x 本地相应的日期表示
    %X 本地相应的时间表示
    %Z 当前时区的名称  # 乱码
    %% %号本身
"""
# (1)当前时间戳
# 1538271871.226226
time.time()


# (2)时间戳 → 时间元组,默认为当前时间
# time.struct_time(tm_year=2018, tm_mon=9, tm_mday=3, tm_hour=9, tm_min=4, tm_sec=1, tm_wday=6, tm_yday=246, tm_isdst=0)
time.localtime()
time.localtime(1538271871.226226)


# (3)时间戳 → 可视化时间
# time.ctime(时间戳),默认为当前时间
time.ctime(1538271871.226226)


# (4)时间元组 → 时间戳
# 1538271871
time.mktime((2018, 9, 30, 9, 44, 31, 6, 273, 0))


# (5)时间元组 → 可视化时间
# time.asctime(时间元组),默认为当前时间
time.asctime()
time.asctime((2018, 9, 30, 9, 44, 31, 6, 273, 0))
time.asctime(time.localtime(1538271871.226226))


# (6)时间元组 → 可视化时间(定制)
# time.strftime(要转换成的格式,时间元组)
time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())


# (7)可视化时间(定制) → 时间元祖
# time.strptime(时间字符串,时间格式)
print(time.strptime('2018-9-30 11:32:23', '%Y-%m-%d %H:%M:%S'))


# (8)浮点数秒数,用于衡量不同程序的耗时,前后两次调用的时间差
time.clock()

时间日期处理实例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

from datetime import datetime
from datetime import date
from datetime import time as time_datetime
import time
from datetime import timedelta

# ------------------1、获得当前时间-------------#

# 得到当前时间戳
print(time.time())

# 时间戳转换为时间元组
print(time.localtime(time.time()))
print(time.gmtime(time.time()))

# 将时间元组格式化输出成字符串时间
print(time.strftime("%Y-%m-%d", time.localtime(time.time())))
print(time.strftime("%Y-%m-%d", time.gmtime(time.time())))

# 不带参数默认输出当前时间
print(time.strftime("%Y-%m-%d"))

# 通过datetime模块来实现
print(datetime.fromtimestamp(time.time()).strftime("%Y-%m-%d"))
print(datetime.now().strftime("%Y-%m-%d"))
print(datetime.today().strftime("%Y-%m-%d"))

# ------------------2、获取时间差,计算执行时间-------------#

# time 模块获取时间戳
start = time.time()
time.sleep(1)
print(time.time() - start)

# datetime模块
start = datetime.now()
time.sleep(1)
print((datetime.now() - start).seconds)

# 计算昨天的日期
print(datetime.now() - timedelta(days=1))

# 时间元组转化为时间戳
print(time.mktime(time.localtime()))  # localtime获取时间元组
print(time.mktime(time.gmtime()))  # gmtime获取时间元组,格林威治时间
print(time.mktime(datetime.now().timetuple()))  # datetime里获取时间元组

# 将时间字符串转换为时间元组
print(time.strptime("2019-07-14 11:23:33", "%Y-%m-%d %H:%M:%S"))

# 表示时间的两种方式:
# 1. 时间戳(相对于1970.1.1 00:00:00以秒计算的偏移量),时间戳是惟一的
# 2. 时间元组 即(struct_time),共有九个元素,分别表示,同一个时间戳的struct_time会因为时区不同而不同

# ------------------3、time时间模块-------------#
# time.clock方法
# 这个需要注意,在不同的系统上含义不同。在UNIX系统上,它返回的是“进程时间”,它是用秒表示的浮点数(时间
# 戳)。而在WINDOWS中,第一次调用,返回的是进程运行的实际时间。而第二次之后的调用是自第一次调用以后到现在的运行时间。
# (实际上是以WIN32 上QueryPerformanceCounter()
# 为基础,它比毫秒表示更为精确)

start = time.clock()
time.sleep(1)
print(time.clock() - start)

# 返回本地时间元组
print(time.localtime())
print(time.localtime(time.time()))

# 从时间元组按照格式进行格式化输出字符串
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))

# 从时间元组转换为时间戳
print(time.mktime(time.localtime()))
print(time.mktime(time.gmtime()))
print(time.mktime(datetime.now().timetuple()))

# ------------------4、datetime时间模块-------------#
# 1.datetime.date: 是指年月日构成的日期(相当于日历)
# 2.datetime.time: 是指时分秒微秒构成的一天24小时中的具体时间(相当于手表)
# 3.datetime.datetime: 上面两个合在一起,既包含时间又包含日期
# 4.datetime.timedelta: 时间间隔对象(timedelta)。一个时间点(datetime)加上一个时间间隔(timedelta)
# 可以得到一个新的时间点(datetime)。比如今天的上午3点加上5个小时得到今天的上午8点。同理,两个时间点相减会得到一个时间间隔。
# 获取当天日期
print(date.today())

# 构造函数构造日期
print(date(2018, 1, 1))

# 格式化日期输出
print(date.today().strftime("%Y%m%d"))

# 日期转换成时间元组,其中时分秒都是0
print(date.today().timetuple())

# 按照时间戳转换为日期
print(date.fromtimestamp(time.time()))

# datetime中的time模块,构造时间
t = time_datetime(8, 11, 11)
print(t)

# 格式化时间
print(t.strftime("%H-%M-%S"))

# 新建一个datetime对象,日期为今天,既可以直接调用datetime.datetime.today(),
# 也可以直接向datetime.datetime()传值,如下:
print(datetime.today())
print(datetime.now())
print(datetime(2014, 8, 15, 8, 12, 34, 790945))

# datetime.datetime.now([tz]) 当不指定时区时,和datetime.datetime.today()是一样的结果
# 格式化时间
print(datetime.now().strftime("%H-%M-%S"))

# 返回时间元组
print(datetime.now().timetuple())
print(time.mktime(datetime.now().timetuple()))

# 数据替换
d1 = datetime(2014, 8, 15, 8, 12, 34, 790945)
print(d1.replace(year=2000))

# ------------------5、timedelta-------------#
print(datetime.today())
print(datetime.today() - timedelta(days=1))

------------------------------------------------------时间元组 End

7.字符串(特殊:元组)

字符串是数组
像许多其他流行的编程语言一样,Python 中的字符串是表示 unicode 字符的字节数组。(元组)

但是,Python 没有字符数据类型,单个字符就是长度为 1 的字符串。

1. 创建字符串

用一对 ’ 或 " 引起来就可以创建字符串。
name = “李长江”
age = '10'
t = "This is a cat."

两对以及两对以上的多对引号引起来的字符串,就可以构建元组。(小括号可有可无)

用例:

T1 = "Strings enclosed in two pairs and more than two pairs of quotation marks 
can construct tuples.",
'Parentheses optional.'
print(type(T))
T2 = (T1)
print(type(T2))
print(T2 is T1)

输出:

<class 'tuple'>
<class 'tuple'>
True

2. 访问字符串

  1. 知道字符串名字以后,在方括号[ ]中使用索引即可访问对应的字符,具体的语法格式为:
strname[index]

strname 表示字符串名字,index 表示索引值。

Python 允许从字符串的两端使用索引:
当以字符串的左端(字符串的开头)为起点时,索引是从 0 开始计数的;字符串的第一个字符的索引为 0,第二个字符的索引为1,第三个字符串的索引为 2 ……
当以字符串的右端(字符串的末尾)为起点时,索引是从 -1 开始计数的;字符串的倒数第一个字符的索引为 -1,倒数第二个字符的索引为 -2,倒数第三个字符的索引为 -3 ……

  1. Python字符串切片可以指定一个范围来获取多个字符,也就是一个子串或者片段,具体格式为:
strname[start : end : step]

对各个部分的说明:
strname:要截取的字符串;
start:表示要截取的第一个字符所在的索引(截取时包含该字符)。如果不指定,默认为 0,也就是从字符串的开头截取;
end:表示要截取的最后一个字符所在的索引(截取时不包含该字符)。如果不指定,默认为字符串的长度;
step:指的是从 start 索引处的字符开始,每 step 个距离获取一个字符,直至 end 索引出的字符。
step 默认值为 1,当省略该值时,最后一个冒号也可以省略。
字符串(string) ‘aaa’ 或Unicode字符串 u’aaa’ 也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:

print('ABCDEFG'[:3])
print('ABCDEFG'[::2])   #步长step为2

输出output :

ABC
ACEG
#字符串分割
print('Hello'[1:3])

直接看输出output :

el
  1. 获取字符串长度或字节数
    Python 中,要想知道一个字符串有多少个字符(获得字符串长度),或者一个字符串占用多少个字节,可以使用 len 函数。

len 函数的基本语法格式为:

len(string)

其中 string 用于指定要进行长度统计的字符串。

  1. 字符串编码转换
    我们知道,最早的字符串编码是 ASCII 编码,它仅仅对 10 个数字、26 个大小写英文字母以及一些特殊字符进行了编码。ASCII 码最多只能表示 256 个符号,每个字符只需要占用 1 个字节。

随着信息技术的发展,各国的文字都需要进行编码,于是相继出现了 GBK、GB2312、UTF-8 编码等,其中 GBK 和 GB2312 是我国制定的中文编码标准,规定英文字符母占用 1 个字节,中文字符占用 2 个字节;而 UTF-8 是国际通过的编码格式,它包含了全世界所有国家需要用到的字符,其规定 英文字符占用 1 个字节,中文字符占用 3 个字节。

Python 3.x 默认采用 UTF-8 编码格式,有效地解决了中文乱码的问题。

在 Python 中,有 2 种常用的字符串类型,分别为 str 和 bytes 类型,其中 str 用来表示Unicode 字符,bytes 用来表示二进制数据。str 类型和 bytes 类型之间就需要使用 encode() 和 decode() 方法进行转换。

【1】 encode() 方法

encode() 方法为字符串类型(str)提供的方法,用于将 str 类型转换成 bytes 类型,这个过程也称为“编码”。

encode() 方法的语法格式如下:

str.encode([encoding="utf-8"][,errors="strict"])

注意,格式中用 [] 括起来的参数为可选参数,也就是说,在使用此方法时,可以使用 [] 中的参数,也可以不使用。

encode()参数及含义

参数                 含义

str                  表示要进行转换的字符串。

encoding = "utf-8"   指定进行编码时采用的字符编码,该选项默认采用 utf-8 编码。例如,如果想使用简体中文,可以设置 gb2312。当方法中只使用这一个参数时,可以省略前边的“encoding=”,直接写编码格式,例如 str.encode("UTF-8")。

errors = "strict"    指定错误处理方式,其可选择值可以是:
                     strict:遇到非法字符就抛出异常。
                     ignore:忽略非法字符。
                     replace:用“?”替换非法字符。
                     xmlcharrefreplace:使用 xml 的字符引用。
                     该参数的默认值为 strict。

注意,使用 encode() 方法对原字符串进行编码,不会直接修改原字符串,如果想修改原字符串,需要重新赋值。

在 Python 中,不同的字符所占的字节数不同,数字、英文字母、小数点、下划线以及空格,各占一个字节,而一个汉字可能占 2~4个字节,具体占多少个,取决于采用的编码方式。例如,汉字在 GBK/GB2312 编码中占用 2 个字节,而在 UTF-8 编码中一般占用 3 个字节。
encode() 方法,将字符串进行编码后再获取它的字节数。例如,采用 UTF-8 编码方式,计算“人生苦短,我用Python”的字节数,可以执行如下代码:

str1 = "人生苦短,我用Python"
print(len(str1.encode()))

输出:

27

因为汉字加中文标点符号共 7 个,占 21 个字节,而英文字母和英文的标点符号占 6 个字节,一共占用 27 个字节。
同理,如果要获取采用 GBK 编码的字符串的长度,可以执行如下代码:

str1 = "人生苦短,我用Python"
print(len(str1.encode('gbk')))

输出:

20

2)decode() 方法用于将 bytes 类型的二进制数据转换为 str 类型,这个过程也称为“解码”。

decode() 方法的语法格式如下:

bytes.decode([encoding="utf-8"][,errors="strict"])

decode()参数及含义

参数                含义
bytes             表示要进行转换的二进制数据。

encoding="utf-8"  指定解码时采用的字符编码,默认采用 utf-8 格式。当方法中只使用这一个参数时,可以省略“encoding=”,直接写编码方式即可。注意,对 bytes 类型数据解码,要选择和当初编码时一样的格式。

errors = "strict" 指定错误处理方式,其可选择值可以是:
                  strict:遇到非法字符就抛出异常。
                  ignore:忽略非法字符。
                  replace:用“?”替换非法字符。
                  xmlcharrefreplace:使用 xml 的字符引用。
                  该参数的默认值为 strict。

注意,如果编码时采用的不是默认的 UTF-8 编码,则解码时要选择和编码时一样的格式,否则会抛出异常。

3. 字符串拼接(包含字符串拼接数字)

在 Python 中拼接(连接)字符串很简单,可以直接将两个字符串紧挨着写在一起,具体格式为:

strname = "str1" "str2"

strname 表示拼接以后的字符串变量名,str1 和 str2 是要拼接的字符串内容。使用这种写法,Python 会自动将两个字符串拼接在一起。

需要注意的是,这种写法只能拼接字符串常量。

如果需要使用变量,就得借助+运算符来拼接,具体格式为:

strname = str1 + str2

当然,+ 运算符拼接或组合两个或多个字符串。+运算符也能拼接字符串常量。

在很多应用场景中,我们需要将字符串和数字拼接在一起,而 Python 不允许直接拼接数字和字符串,所以我们必须先将数字转换成字符串。
可以借助 str() 和 repr() 函数将数字转换为字符串,它们的使用格式为:

str(obj)
repr(obj)

obj 表示要转换的对象,它可以是数字、列表、元组、字典等多种类型的数据。
str() 和 repr() 的区别:
str() 和 repr() 函数虽然都可以将数字转换成字符串,但它们之间是有区别的:
str() 用于将数据转换成适合人类阅读的字符串形式。
repr() 用于将数据转换成适合解释器阅读的字符串形式(Python 表达式的形式),适合在开发和调试阶段使用;如果没有等价的语法,则会发生 SyntaxError 异常。

本身是一个字符串,但是我们依然使用 str() 和 repr() 对它进行了转换,str() 保留了字符串最原始的样子,而 repr() 使用引号将字符串包围起来,这就是 Python 字符串的表达式形式。
另外,在 Python 交互式编程环境中输入一个表达式(变量、加减乘除、逻辑运算等)时,Python 会自动使用 repr() 函数处理该表达式。

4. 分割字符串

split() 方法可以实现将一个字符串按照指定的分隔符切分成多个子串,这些子串会被保存到列表中(不包含分隔符),作为方法的返回值反馈回来。该方法的基本语法格式如下:

str.split(sep,maxsplit)

此方法中各部分参数的含义分别是:
str:表示要进行分割的字符串;
sep:用于指定分隔符,可以包含多个字符。此参数默认为 None,表示所有空字符,包括空格、换行符“\n”、制表符“\t”等。
maxsplit:可选参数,用于指定分割的次数,最后列表中子串的个数最多为 maxsplit+1。如果不指定或者指定为 -1,则表示分割次数没有限制。

需要注意的是,在未指定 sep 参数时,split() 方法默认采用空字符进行分割,但当字符串中有连续的空格或其他空字符时,都会被视为一个分隔符对字符串进行分割

5. 合并字符串

join() 方法是非常重要的字符串方法,它是 split() 方法的逆方法,用来将列表(或元组)中包含的多个字符串连接成一个字符串。

使用 join() 方法合并字符串时,它会将列表(或元组)中多个字符串采用固定的分隔符连接在一起。
例如,字符串“c.biancheng.net”就可以看做是通过分隔符“.”将 [‘c’,‘biancheng’,‘net’] 列表合并为一个字符串的结果。

join() 方法的语法格式如下:

newstr = str.join(iterable)

此方法中各参数的含义如下:
newstr:表示合并后生成的新字符串;
str:用于指定合并时的分隔符;
iterable:做合并操作的源字符串数据,允许以列表、元组等形式提供。

6. 统计字符串出现的次数

count 方法用于检索指定字符串在另一字符串中出现的次数,如果检索的字符串不存在,则返回 0,否则返回出现的次数。

count 方法的语法格式如下:

str.count(sub[,start[,end]])

此方法中,各参数的具体含义如下:
str:表示原字符串;
sub:表示要检索的字符串;
start:指定检索的起始位置,也就是从什么位置开始检测。如果不指定,默认从头开始检索;
end:指定检索的终止位置,如果不指定,则表示一直检索到结尾。

7. 检测字符串中是否包含某子串

find() 方法用于检索字符串中是否包含目标字符串,如果包含,则返回第一次出现该字符串的索引;反之,则返回 -1。

find() 方法的语法格式如下:

str.find(sub[,start[,end]])

此格式中各参数的含义如下:
str:表示原字符串;
sub:表示要检索的目标字符串;
start:表示开始检索的起始位置。如果不指定,则默认从头开始检索;
end:表示结束检索的结束位置。如果不指定,则默认一直检索到结尾。

注意,Python 还提供了 rfind() 方法,与 find() 方法最大的不同在于,rfind() 是从字符串右边开始检索。

8. 检测字符串中是否包含某子串

同 find() 方法类似,index() 方法也可以用于检索是否包含指定的字符串,不同之处在于,当指定的字符串不存在时,index() 方法会抛出异常。

index() 方法的语法格式如下:

str.index(sub[,start[,end]])

此格式中各参数的含义分别是:
str:表示原字符串;
sub:表示要检索的子字符串;
start:表示检索开始的起始位置,如果不指定,默认从头开始检索;
end:表示检索的结束位置,如果不指定,默认一直检索到结尾。

同 find() 和 rfind() 一样,字符串变量还具有 rindex() 方法,其作用和 index() 方法类似,不同之处在于它是从右边开始检索。

9. 字符串对齐

  1. ljust() 方法的功能是向指定字符串的右侧填充指定字符,从而达到左对齐文本的目的。

ljust() 方法的基本格式如下:

S.ljust(width[, fillchar])

其中各个参数的含义如下:
S:表示要进行填充的字符串;
width:表示包括 S 本身长度在内,字符串要占的总长度;
fillchar:作为可选参数,用来指定填充字符串时所用的字符,默认情况使用空格。

  1. rjust() 和 ljust() 方法类似,唯一的不同在于,rjust() 方法是向字符串的左侧填充
    指定字符,从而达到右对齐文本的目的。

rjust() 方法的基本格式如下:

S.rjust(width[, fillchar])

其中各个参数的含义如下:
S:表示要进行填充的字符串;
width:表示包括 S 本身长度在内,字符串要占的总长度;
fillchar:作为可选参数,用来指定填充字符串时所用的字符,默认情况使用空格。

  1. center() 字符串方法与 ljust() 和 rjust() 的用法类似,但它让文本居中,而不是左对齐或右对齐。

center() 方法的基本格式如下:

S.center(width[, fillchar])

其中各个参数的含义如下:
S:表示要进行填充的字符串;
width:表示包括 S 本身长度在内,字符串要占的总长度;
fillchar:作为可选参数,用来指定填充字符串时所用的字符,默认情况使用空格。

10. 检索方法

  1. startswith() 方法用于检索字符串是否以指定字符串开头,如果是返回 True;反之返回 False。此方法的语法格式如下:
str.startswith(sub[,start[,end]])

此格式中各个参数的具体含义如下:
str:表示原字符串;
sub:要检索的子串;
start:指定检索开始的起始位置索引,如果不指定,则默认从头开始检索;
end:指定检索的结束位置索引,如果不指定,则默认一直检索在结束。

  1. endswith() 方法用于检索字符串是否以指定字符串结尾,如果是则返回 True;反之则返
    回 False。该方法的语法格式如下:
str.endswith(sub[,start[,end]])

此格式中各参数的含义如下:
str:表示原字符串;
sub:表示要检索的字符串;
start:指定检索开始时的起始位置索引(字符串第一个字符对应的索引值为 0),如果不指定,默认从头开始检索。
end:指定检索的结束位置索引,如果不指定,默认一直检索到结束。

11. 字符串大小写转换

Python 中,为了方便对字符串中的字母进行大小写转换,字符串变量提供了 3 种方法,分别是 title()、lower() 和 upper()。

  1. title() 方法用于将字符串中每个单词的首字母转为大写,其他字母全部转为小写,转换完成后,此方法会返回转换得到的字符串。
    如果字符串中没有需要被转换的字符,此方法会将字符串原封不动地返回。

title() 方法的语法格式如下:

str.title()

其中,str 表示要进行转换的字符串。

  1. lower() 方法用于将字符串中的所有大写字母转换为小写字母,转换完成后,该方法会返回新得到的字符串。如果字符串中原本就都是小写字母,则该方法会返回原字符串。

lower() 方法的语法格式如下:

str.lower()

其中,str 表示要进行转换的字符串。

  1. upper() 的功能和 lower() 方法恰好相反,它用于将字符串中的所有小写字母转换为大写字母,和以上两种方法的返回方式相同,即如果转换成功,则返回新字符串;反之,则返回原字符串。

upper() 方法的语法格式如下:

str.upper()

其中,str 表示要进行转换的字符串。

需要注意的是,以上 3 个方法都仅限于将转换后的新字符串返回,而不会修改原字符串。

12. 去除字符串中空格(删除指定字符)

用户输入数据时,很有可能会无意中输入多余的空格,或者在一些场景中,字符串前后不允许出现空格和特殊字符,此时就需要去除字符串中的空格和特殊字符。
这里的特殊字符,指的是制表符(\t)、回车符(\r)、换行符(\n)等。

Python 中,字符串变量提供了 3 种方法来删除字符串中多余的空格和特殊字符,它们分别是:

strip():删除字符串前后(左右两侧)的空格或特殊字符。
lstrip():删除字符串前面(左边)的空格或特殊字符。
rstrip():删除字符串后面(右边)的空格或特殊字符。

注意,Python 的 str 是不可变的(不可变的意思是指,字符串一旦形成,它所包含的字符序列就不能发生任何改变),因此这三个方法只是返回字符串前面或后面空白被删除之后的副本,并不会改变字符串本身。

  1. strip() 方法用于删除字符串左右两个的空格和特殊字符,该方法的语法格式为:
str.strip([chars])

其中,str 表示原字符串,[chars] 用来指定要删除的字符,可以同时指定多个,如果不手动指定,则默认会删除空格以及制表符、回车符、换行符等特殊字符。

  1. lstrip() 方法用于去掉字符串左侧的空格和特殊字符。该方法的语法格式如下:
str.lstrip([chars])

其中,str 表示原字符串,[chars] 用来指定要删除的字符,可以同时指定多个,如果不手动指定,则默认会删除空格以及制表符、回车符、换行符等特殊字符。

  1. rstrip() 方法用于删除字符串右侧的空格和特殊字符,其语法格式为:
str.rstrip([chars])

其中,str 表示原字符串,[chars] 用来指定要删除的字符,可以同时指定多个,如果不手动指定,则默认会删除空格以及制表符、回车符、换行符等特殊字符。

13. 格式化输出

使用 % 操作符对各种类型的数据进行格式化输出,这是早期 Python 提供的方法。自 Python 2.6 版本开始,字符串类型(str)提供了 format() 方法对字符串进行格式化。

format() 方法的语法格式如下:

str.format(args)

此方法中,str 用于指定字符串的显示样式;args 用于指定要进行格式转换的项,如果有多项,之间有逗号进行分割。

学习 format() 方法的难点,在于搞清楚 str 显示样式的书写格式。在创建显示样式模板时,需要使用{}和:来指定占位符,其完整的语法格式为:

{ [index][ : [ [fill] align] [sign] [#] [width] [.precision] [type] ] }

注意,格式中用 [] 括起来的参数都是可选参数,即可以使用,也可以不使用。各个参数的含义如下:

index:指定:后边设置的格式要作用到 args 中第几个数据,数据的索引值从 0 开始。如果省略此选项,则会根据 args 中数据的先后顺序自动分配。
fill:指定空白处填充的字符。注意,当填充字符为逗号(,)且作用于整数或浮点数时,该整数(或浮点数)会以逗号分隔的形式输出,例如(1000000会输出 1,000,000)。
align:指定数据的对齐方式。

各参数具体的对齐方式如下:
align 参数及含义

align 含义

<     数据左对齐。

>     数据右对齐。

=     数据右对齐,同时将符号放置在填充内容的最左侧,该选项只对数字类型有效。

^     数据居中,此选项需和 width 参数一起使用。

sign:指定有无符号数。
sign 参数以含义

sign参数   含义

+        正数前加正号,负数前加负号。

-        正数前不加正号,负数前加负号。

空格     正数前加空格,负数前加负号。

#        对于二进制数、八进制数和十六进制数,使用此参数,各进制数前会分别显示0b、0o、0x前缀;反之则不显示前缀。

width:      指定输出数据时所占的宽度。
.precision: 指定保留的小数位数。

type:指定输出数据的具体类型。
type 占位符类型及含义

type类型值  含义

s           对字符串类型格式化。

d           十进制整数。

c           将十进制整数自动转换成对应的 Unicode 字符。

e  或者 E   转换成科学计数法后,再格式化输出。

g 或 G      自动在 e 和 f(或 E 和 F)中切换。

b           将十进制数自动转换成二进制表示,再格式化输出。

o           将十进制数自动转换成八进制表示,再格式化输出。

x 或者 X    将十进制数自动转换成十六进制表示,再格式化输出。

f 或者 F    转换为浮点数(默认小数点后保留 6 位),再格式化输出。

%           显示百分比(默认显示小数点后 6 位)。

在实际开发中,数值类型有多种显示需求,比如货币形式、百分比形式等,使用
format() 方法可以将数值格式化为不同的形式。

#以货币形式显示
print("货币形式:{:,d}".format(1000000))
#科学计数法表示
print("科学计数法:{:E}".format(1200.12))
#以十六进制表示
print("100的十六进制:{:#x}".format(100))
#输出百分比形式
print("0.01的百分比表示:{:.0%}".format(0.01))

输出结果为:

货币形式:1,000,000
科学计数法:1.200120E+03
100的十六进制:0x64
0.01的百分比表示:1%

14. 总结:字符串

字符串方法
strip() 方法删除开头和结尾的空白字符
lower() 返回小写的字符串
upper() 方法返回大写的字符串
replace() 用另一段字符串来替换字符串
split() 方法在找到分隔符的实例时将字符串拆分为子字符串
in 或 not in 关键字检查字符串中是否存在特定短语或字符。
format() 方法组合字符串和数字!format() 方法接受传递的参数,格式化它们,并将它们放在占位符 {} 所在的字符串中

注释:所有字符串方法都返回新值。它们不会更改原始字符串。

方法             描述
capitalize()   把首字符转换为大写。

casefold()     把字符串转换为小写。

center()       返回居中的字符串。

count()        返回指定值在字符串中出现的次数。

encode()       返回字符串的编码版本。

endswith()     如果字符串以指定值结尾,则返回 true。

expandtabs()   设置字符串的 tab 尺寸。

find()         在字符串中搜索指定的值并返回它被找到的位置。

format()       格式化字符串中的指定值。

format_map()   格式化字符串中的指定值。

index()        在字符串中搜索指定的值并返回它被找到的位置。

isalnum()      如果字符串中的所有字符都是字母数字,则返回 True。

isalpha()      如果字符串中的所有字符都在字母表中,则返回 True。

isdecimal()    如果字符串中的所有字符都是小数,则返回 True。

isdigit()      如果字符串中的所有字符都是数字,则返回 True。

isidentifier() 如果字符串是标识符,则返回 True。

islower()      如果字符串中的所有字符都是小写,则返回 True。

isnumeric()    如果字符串中的所有字符都是数,则返回 True。

isprintable()  如果字符串中的所有字符都是可打印的,则返回 True。

isspace()      如果字符串中的所有字符都是空白字符,则返回 True。

istitle()      如果字符串遵循标题规则,则返回 True。

isupper()      如果字符串中的所有字符都是大写,则返回 True。

join()         把可迭代对象的元素连接到字符串的末尾。

ljust()        返回字符串的左对齐版本。

lower()        把字符串转换为小写。

lstrip()       返回字符串的左修剪版本。

maketrans()    返回在转换中使用的转换表。

partition()    返回元组,其中的字符串被分为三部分。

replace()      返回字符串,其中指定的值被替换为指定的值。

rfind()        在字符串中搜索指定的值,并返回它被找到的最后位置。

rindex()       在字符串中搜索指定的值,并返回它被找到的最后位置。

rjust()        返回字符串的右对齐版本。

rpartition()   返回元组,其中字符串分为三部分。

rsplit()       在指定的分隔符处拆分字符串,并返回列表。

rstrip()       返回字符串的右边修剪版本。

split()        在指定的分隔符处拆分字符串,并返回列表。

splitlines()   在换行符处拆分字符串并返回列表。

startswith()   如果以指定值开头的字符串,则返回 true。

strip()        返回字符串的剪裁版本。

swapcase()     切换大小写,小写成为大写,反之亦然。

title()        把每个单词的首字符转换为大写。

translate()    返回被转换的字符串。

upper()        把字符串转换为大写。

zfill()        在字符串的开头填充指定数量的 0 值。

注释:所有字符串方法都返回新值。它们不会更改原始字符串。

Python 转义字符
在需要在字符中使用特殊字符时,python 用反斜杠 \ 转义字符。如下表:

转义字符     描述
\(在行尾时)  续行符
\\          反斜杠符号
\'          单引号
\"          双引号
\a          ASCII Bell(BEL) 响铃符
\b          ASCII Backspace(BS) 退格符
\e          转义
\f          ASCII Formfeed(FF) 进纸符,换页
\n          ASCII Linefeed(LF) 换行符
\N{name}    Unicode 数据 库中的字符名,其中 name 就是它的名字(Unicode only)
\oyy        八进制数,y 代表 0~7 的字符,例如:\012 代表换行。
\other      其它的字符以普通格式输出
\r          ASCII Carriage Return (CR) 回车符
\t          ASCIIHorizontal Tab (TAB) 水平制表符,横向制表符
\uxxxx      值为 16 位十六进制值 xxxx 的字符(Unicode only)
\Uxxxxxxxx  值为 32 位十六进制值 xxxx 的字符(Unicode only)
\v          ASCII Vertical Tab (VT) 垂直制表符,纵向制表符
\xyy        十六进制数,以 \x 开头,yy代表的字符,例如:\x0a代表换行
\ooo        值为八进制值 ooo 的字符
\000        空

Python字符串运算符
下表实例变量 a 值为字符串 “Hello”,b 变量值为 “Python”:

操作符   描述 
+        字符串连接 

*        重复输出字符串  

[]       通过索引获取字符串中字符   

[ : ]    截取字符串中的一部分  

in       成员运算符 - 如果字符串中包含给定的字符返回 True  

not in   成员运算符 - 如果字符串中不包含给定的字符返回 True 

r/R      原始字符串 - 原始字符串:所有的字符串都是直接按照字面的意思来使用,没有转义特殊或不能打印的字符。 原始字符串除在字符串的第一个引号前加上字母"r"(可以大小写)以外,与普通字符串有着几乎完全相同的语法。  

%       格式字符串 

实例(Python 2.0+):

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
a = "Hello"
b = "Python"
 
print "a + b 输出结果:", a + b 
print "a * 2 输出结果:", a * 2 
print "a[1] 输出结果:", a[1] 
print "a[1:4] 输出结果:", a[1:4] 
 
if( "H" in a) :
    print "H 在变量 a 中" 
else :
    print "H 不在变量 a 中" 
 
if( "M" not in a) :
    print "M 不在变量 a 中" 
else :
    print "M 在变量 a 中"
 
print r'\n'
print R'\n'

以上程序执行结果为:

a + b 输出结果: HelloPython
a * 2 输出结果: HelloHello
a[1] 输出结果: e
a[1:4] 输出结果: ell
H 在变量 a 中
M 不在变量 a 中
\n
\n

Python 字符串格式化
Python 支持格式化字符串的输出 。尽管这样可能会用到非常复杂的表达式,但最基本的用法是将一个值插入到一个有字符串格式符 %s 的字符串中。
在 Python 中,字符串格式化使用与 C 中 sprintf 函数一样的语法。

如下实例python2.0:

#!/usr/bin/python

print "My name is %s and weight is %d kg!" % ('Zara', 21) 

以上实例输出结果:

My name is Zara and weight is 21 kg!

python 字符串格式化符号:

    符   号   描述
      %c  格式化字符及其ASCII码
      %s  格式化字符串
      %d  格式化整数
      %u  格式化无符号整型
      %o  格式化无符号八进制数
      %x  格式化无符号十六进制数
      %X  格式化无符号十六进制数(大写)
      %f  格式化浮点数字,可指定小数点后的精度
      %e  用科学计数法格式化浮点数
      %E  作用同%e,用科学计数法格式化浮点数
      %g  %f和%e的简写
      %G  %F 和 %E 的简写
      %p  用十六进制数格式化变量的地址

格式化操作符辅助指令:

符号  功能
*     定义宽度或者小数点精度
-     用做左对齐
+     在正数前面显示加号( + )
<sp>  在正数前面显示空格
#     在八进制数前面显示零('0'),在十六进制前面显示'0x'或者'0X'(取决于用的是'x'还是'X')
0     显示的数字前面填充'0'而不是默认的空格
%     '%%'输出一个单一的'%'
(var)  映射变量(字典参数)
m.n.   m 是显示的最小总宽度,n 是小数点后的位数(如果可用的话)

Python2.6 开始,新增了一种格式化字符串的函数 str.format(),它增强了字符串格式化的功能。

Python 三引号
Python 中三引号可以将复杂的字符串进行赋值或注释。三引号允许一个字符串跨多,字符串中可以包含换行符、制表符以及其他特殊字符。三引号的语法是一对连续的单引号或者双引号(通常都是成对的用)。

三引号让程序员从引号和特殊字符串的泥潭里面解脱出来,自始至终保持一小块字符串的格式是所谓的WYSIWYG(所见即所得)格式的。

一个典型的用例是,当你需要一块HTML或者SQL时,这时当用三引号标记,使用传统的转义字符体系将十分费神。

 errHTML = '''
<HTML><HEAD><TITLE>
Friends CGI Demo</TITLE></HEAD>
<BODY><H3>ERROR</H3>
<B>%s</B><P>
<FORM><INPUT TYPE=button VALUE=Back
ONCLICK="window.history.back()"></FORM>
</BODY></HTML>
'''
cursor.execute('''
CREATE TABLE users (  
login VARCHAR(8), 
uid INTEGER,
prid INTEGER)
''')

Unicode 字符串
Python 中定义一个 Unicode 字符串和定义一个普通字符串一样简单:

>>> u'Hello World !'
u'Hello World !'

引号前小写的"u"表示这里创建的是一个 Unicode 字符串。如果你想加入一个特殊字符,可以使用 Python 的 Unicode-Escape 编码。
如下例所示:

>>> u'Hello\u0020World !'
u'Hello World !'

被替换的 \u0020 标识表示在给定位置插入编码值为 0x0020 的 Unicode 字符(空格符)。

python的字符串内建函数
字符串方法是从python1.6到2.0慢慢加进来的——它们也被加到了python中。

这些方法实现了string模块的大部分方法,如下表所示列出了目前字符串内建支持的方法,所有的方法都包含了对Unicode的支持,有一些甚至是专门用于Unicode的。

n)方法 
描   述

1)string.capitalize()
把字符串的第一个字符大写

2)string.center(width)
返回一个原字符串居中,并使用空格填充至长度 width 的新字符串

3)string.count(str, beg=0, end=len(string))
返回 str 在 string 里面出现的次数,如果 beg 或者 end 指定则返回指定范围内 str 出现的次数

4)string.decode(encoding='UTF-8', errors='strict')
以 encoding 指定的编码格式解码 string,如果出错默认报一个 ValueError 的 异 常 ,除非 errors 指 定 的 是 'ignore' 或 者'replace'

5)string.encode(encoding='UTF-8', errors='strict')
以 encoding 指定的编码格式编码 string,如果出错默认报一个ValueError 的异常,除非 errors 指定的是'ignore'或者'replace'

6)string.endswith(obj, beg=0, end=len(string))
检查字符串是否以 obj 结束,如果beg 或者 end 指定则检查指定的范围内是否以 obj 结束,如果是,返回 True,否则返回 False.

7)string.expandtabs(tabsize=8)
把字符串 string 中的 tab 符号转为空格,tab 符号默认的空格数是 8。

8)string.find(str, beg=0, end=len(string))
检测 str 是否包含在 string 中,如果 beg 和 end 指定范围,则检查是否包含在指定范围内,如果是返回开始的索引值,否则返回-1

9)string.format()
格式化字符串

10)string.index(str, beg=0, end=len(string))
跟find()方法一样,只不过如果str不在 string中会报一个异常.

11)string.isalnum()
如果 string 至少有一个字符并且所有字符都是字母或数字则返回 True,否则返回 False

12)string.isalpha()
如果 string 至少有一个字符并且所有字符都是字母则返回 True,否则返回 False

13)string.isdecimal()
如果 string 只包含十进制数字则返回 True 否则返回 False.

14)string.isdigit()
如果 string 只包含数字则返回 True 否则返回 False.

15)string.islower()
如果 string 中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是小写,则返回 True,否则返回 False

16)string.isnumeric()
如果 string 中只包含数字字符,则返回 True,否则返回 False

18)string.isspace()
如果 string 中只包含空格,则返回 True,否则返回 False.

19)string.istitle()
如果 string 是标题化的(见 title())则返回 True,否则返回 False

20)string.isupper()
如果 string 中包含至少一个区分大小写的字符,并且所有这些(区分大小写的)字符都是大写,则返回 True,否则返回 False

21)string.join(seq)
以 string 作为分隔符,将 seq 中所有的元素(的字符串表示)合并为一个新的字符串

22)string.ljust(width)
返回一个原字符串左对齐,并使用空格填充至长度 width 的新字符串

23)string.lower()
转换 string 中所有大写字符为小写.

24)string.lstrip()
截掉 string 左边的空格

25)string.maketrans(intab, outtab)
maketrans() 方法用于创建字符映射的转换表,对于接受两个参数的最简单的调用方式,第一个参数是字符串,表示需要转换的字符,第二个参数也是字符串表示转换的目标。

26)max(str)
返回字符串 str 中最大的字母。

27)min(str)
返回字符串 str 中最小的字母。

28)string.partition(str)
有点像 find()和 split()的结合体,从 str 出现的第一个位置起,把 字 符 串 string 分 成 一 个 3 元 素 的 元 组 (string_pre_str,str,string_post_str),如果 string 中不包含str 则 string_pre_str == string.

29)string.replace(str1, str2,  num=string.count(str1))
把 string 中的 str1 替换成 str2,如果 num 指定,则替换不超过 num 次.

30)string.rfind(str, beg=0,end=len(string) )
类似于 find() 函数,返回字符串最后一次出现的位置,如果没有匹配项则返回 -1。

31)string.rindex( str, beg=0,end=len(string))
类似于 index(),不过是返回最后一个匹配到的子字符串的索引号。

32)string.rjust(width)
返回一个原字符串右对齐,并使用空格填充至长度 width 的新字符串

33)string.rpartition(str)
类似于 partition()函数,不过是从右边开始查找

34)string.rstrip()
删除 string 字符串末尾的空格.

35)string.split(str="", num=string.count(str))
以 str 为分隔符切片 string,如果 num 有指定值,则仅分隔 num+1 个子字符串

36)string.splitlines([keepends])
按照行('\r', '\r\n', '\n')分隔,返回一个包含各行作为元素的列表,如果参数 keepends 为 False,不包含换行符,如果为 True,则保留换行符。

36)string.startswith(obj, beg=0,end=len(string))
检查字符串是否是以 obj 开头,是则返回 True,否则返回 False。如果beg 和 end 指定值,则在指定范围内检查.

38)string.strip([obj])
在 string 上执行 lstrip()和 rstrip()

39)string.swapcase()
翻转 string 中的大小写

40)string.title()
返回"标题化"的 string,就是说所有单词都是以大写开始,其余字母均为小写(见 istitle())

41)string.translate(str, del="")
根据 str 给出的表(包含 256 个字符)转换 string 的字符,要过滤掉的字符放到 del 参数中

42)string.upper()
转换 string 中的小写字母为大写

43)string.zfill(width)
返回长度为 width 的字符串,原字符串 string 右对齐,前面填充0

更多字符串操作可参考python文档:
https://docs.python.org/zh-cn/3/library/string.html

后记:字节(bytes)和字节串(bytearray)

可以参考python文档:
https://docs.python.org/zh-cn/3/library/stdtypes.html#binary-sequence-types-bytes-bytearray-memoryview

3.dict字典详解

Python 字典(dict)是一种无序的、可变的序列,它的元素以“键值对(key-value)”的形式存储。相对地,列表(list)和元组(tuple)都是有序的序列,它们的元素在底层是挨着存放的。有的语言里它的名称是 “hash”(哈希)。

字典类型是 Python 中唯一的映射类型。“映射”是数学中的术语,简单理解,它指的是元素之间相互对应的关系,即通过一个元素,可以唯一找到另一个元素。

      |             |        *         *      
      |   key1 -----|--------*-→ val1  *
      |             |        *         *                                                                           
      |      key2 --|--------*---→ val2*          
      |             |        *         * 
      |  key3-------|--------*-→ val3  *  
      |             |        *         *    
      |_____________|        ***********  

         key数据组 →→→→→→→→→→→→→val数据组         
                     映射关系

字典中,习惯将各元素对应的索引称为键(key),各个键对应的元素称为值(value),键及其关联的值称为“键值对”。

字典类型很像学生时代常用的新华字典。我们知道,通过新华字典中的音节表,可以快速找到想要查找的汉字。其中,字典里的音节表就相当于字典类型中的键,而键对应的汉字则相当于值。

总的来说,字典类型所具有的主要特征如如上图所示。

Python 字典特征

n)主要特征 
         解释
1) 通过键而不是通过索引来读取元素
      字典类型有时也称为关联数组或者散列表(hash)。它是通过键将一系列的值联系起来的, 这样就可以通过键从字典中获取指定项,但不能通过索引来获取。

2) 字典是任意数据类型的无序集合 
      和列表、元组不同,通常会将索引值 0 对应的元素称为第一个元素,而字典中的元素是无序的。字典是可变的,并且可以任意嵌套字典可以在原处增长或者缩短(无需生成一个副本),并且它支持任意深度的嵌套,即字典存储的值也可以是列表或其它的字典。

3) 字典中的键必须唯一   
      字典中,不支持同一个键出现多次,否则只会保留最后一个键值对。

4) 字典中的键必须不可变  
      字典中每个键值对的键是不可变的,只能使用数字、字符串或者元组,不能使用列表。

Python 中的字典类型相当于 Java 或者 C++ 中的 Map 对象。

和列表、元组一样,字典也有它自己的类型。Python 中,字典的数据类型为 dict,通过 type() 函数即可查看:

a = {'one': 1, 'two': 2, 'three': 3}  #a是一个字典类型
print(type(a))

输出:

<class 'dict'>

由于字典属于可变序列,所以我们可以任意操作字典中的键值对(key-value)。Python 中,常见的字典操作有以下几种:

  1. 向现有字典中添加新的键值对。
  2. 修改现有字典中的键值对。
  3. 从现有字典中删除指定的键值对。
  4. 判断现有字典中是否存在指定的键值对。

初学者要牢记,字典是由一个一个的 key-value 构成的,key 是找到数据的关键,Python 对字典的操作都是通过 key 来完成的。

1. 创建字典

  1. 使用 { } 创建字典
    由于字典中每个元素都包含两部分,分别是键(key)和值(value),因此在创建字典时,键和值之间使用冒号:分隔,相邻元素之间使用逗号,分隔,所有元素放在大括号{ }中。字典是另一种可变容器模型,且可存储任意类型对象。
    键一般是唯一的,如果重复最后的一个键值对会替换前面的,值不需要唯一。
    值可以取任何数据类型,但键必须是不可变的,如字符串,数字或元组。
    使用{ }创建字典的语法格式如下:
dictname = {'key':'value1', 'key2':'value2', ..., 'keyn':valuen}

其中 dictname 表示字典变量名,keyn : valuen 表示各个元素的键值对。需要注意的是,同一字典中的各个键必须唯一,不能重复。

字典的键可以是整数、字符串或者元组,只要符合唯一和不可变的特性就行;字典的值可以是 Python 支持的任意数据类型。

  1. 通过 fromkeys() 方法创建字典
    Python 中,还可以使用 dict 字典类型提供的 fromkeys() 方法创建带有默认值的字典,具体格式为:
dictname = dict.fromkeys(list,value=None)

其中,list 参数表示字典中所有键的列表(list);value 参数表示默认值,如果不写,则为空值 None。

  1. 通过 dict() 映射函数创建字典
    通过 dict() 函数创建字典的写法有多种,表 2 罗列出了常用的几种方式,它们创建的都是同一个字典 a。

dict() 函数创建字典

创建格式                                        注意事项
        
a = dict(str1=value1, str2=value2, str3=value3) str 表示字符串类型的键,value 表示键对应的值。使用此方式创建字典时,字符串不能带引号。

#方式1
demo = [('two',2), ('one',1), ('three',3)]
#方式2
demo = [['two',2], ['one',1], ['three',3]]
#方式3
demo = (('two',2), ('one',1), ('three',3))
#方式4
demo = (['two',2], ['one',1], ['three',3])
a = dict(demo)                                  向 dict() 函数传入列表或元组,而它们中的元素又各自是包含 2 个元素的列表或元组,其中第一个元素作为键,第二个元素作为值。
keys = ['one', 'two', 'three']                  还可以是字符串或元组
values = [1, 2, 3]                              还可以是字符串或元组
a = dict( zip(keys, values) )                   通过应用 dict() 函数和 zip() 函数,可将前两个列表转换为对应的字典。

注意,无论采用以上哪种方式创建字典,字典中各元素的键都只能是字符串、元组或数字,不能是列表。列表是可变的,不能作为键。
如果不为 dict() 函数传入任何参数,则代表创建一个空的字典。

字典键的特性
字典值可以没有限制地取任何 python 对象,既可以是标准的对象,也可以是用户定义的,但键不行。

两个重要的点需要记住:

  1. 不允许同一个键出现两次。创建时如果同一个键被赋值两次,后一个值会被记住
  2. 键必须不可变,所以可以用数字,字符串或元组充当,所以用列表就不行

2. 访问字典

列表和元组是通过下标来访问元素的,而字典不同,它通过键来访问对应的值。因为字典中的元素是无序的,每个元素的位置都不固定,所以字典也不能像列表和元组那样,采用切片的方式一次性访问多个元素。

  1. 键名 访问字典元素的具体格式为:
dictname[key]

其中,dictname 表示字典变量的名字,key 表示键名。注意,键必须是存在的,否则会抛出异常。
Python 更推荐使用 dict 类型提供的 get() 方法来获取指定键对应的值。当指定的键不存在时,get() 方法不会抛出异常。

  1. get() 方法的语法格式为:
dictname.get(key[,default])

其中,dictname 表示字典变量的名字;key 表示指定的键;default 用于指定要查询的键不存在时,此方法返回的默认值,如果不手动指定,会返回 None。

注意,当键不存在时,get() 返回空值 None,如果想明确地提示用户该键不存在,那么可以手动设置 get() 的第二个参数。

  1. 使用 for 循环遍历字典。

循环遍历字典时,返回值是字典的键,但也有返回值的方法。

  1. 使用 items() 函数遍历键和值

检查键是否存在
要确定字典中是否存在指定的键,请使用 in 关键字。

3. 添加键值对

为字典添加新的键值对很简单,直接给不存在的 key 赋值即可,具体语法格式如下:

dictname[key] = value

对各个部分的说明:
dictname 表示字典名称。
key 表示新的键。
value 表示新的值,只要是 Python 支持的数据类型都可以。

4. 修改键值对

Python 字典中键(key)的名字不能被修改,我们只能修改值(value)。

字典中各元素的键必须是唯一的,因此,如果新添加元素的键与已存在元素的键相同,那么键所对应的值就会被新的值替换掉,以此达到修改元素值的目的。

5. 删除键值对

如果要删除字典中的键值对,还是可以使用 del 语句。
判断字典中是否存在指定键值对如果要判断字典中是否存在指定键值对,首先应判断字典中是否有对应的键。判断字典是否包含指定键值对的键,可以使用 in 或 not in 运算符。
需要指出的是,对于 dict 而言,in 或 not in 运算符都是基于 key 来判断的。
通过 in(或 not in)运算符,我们可以很轻易地判断出现有字典中是否包含某个键,如果存在,由于通过键可以很轻易的获取对应的值,因此很容易就能判断出字典中是否有指定的键值对。

6. 删除字典

和删除列表、元组一样,手动删除字典也可以使用 del 关键字,Python 自带垃圾回收功能,会自动销毁不用的字典,所以一般不需要通过 del 来手动删除。

7. 字典方法

Python 字典的数据类型为 dict,我们可使用 dir(dict) 来查看该类型包含哪些方法,例如:

>>> dir(dict)
['clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 
'setdefault', 'update', 'values']

这些方法中,fromkeys() 和 get() 的用法已前面进行了介绍,这里不再赘述。
接下来介绍剩下的方法。

【1】keys()、values() 和 items() 方法
将这三个方法放在一起介绍,是因为它们都用来获取字典中的特定数据:

keys() 方法用于返回字典中的所有键(key);

values() 方法用于返回字典中所有键对应的值(value);

items() 用于返回字典中所有的键值对(key-value)。

需要注意的是,在 Python 2.x 中,上面三个方法的返回值都是列表(list)类型。但在 Python 3.x中,它们的返回值并不是 我们常见的列表或者元组类型,因为 Python 3.x不希望用户直接操作这几个方法的返回值。

在 Python 3.x 中如果想使用这三个方法返回的数据,一般有下面两种方案:

  1. 使用 list() 函数,将它们返回的数据转换成列表。
  2. 使用 for in 循环遍历它们的返回值。

【2】copy() 方法返回一个字典的拷贝,也即返回一个具有相同键值对的新字典
注意,copy() 方法所遵循的拷贝原理,既有深拷贝,也有浅拷贝。

【3】 update() 方法可以使用一个字典所包含的键值对来更新己有的字典。

在执行 update() 方法时,如果被更新的字典中己包含对应的键值对,那么原 value 会被覆盖;如果被更新的字典中不包含对应的键值对,则该键值对被添加进去。

【4】 pop() 和 popitem() 都用来删除字典中的键值对,不同的是,pop() 用来删除指定的键值对,而 popitem()用来随机删除一个键值对,它们的语法格式如下:

dictname.pop(key)
dictname.popitem()

其中,dictname 表示字典名称,key 表示键。

对 popitem() 的说明
其实,说 popitem() 随机删除字典中的一个键值对是不准确的,虽然字典是一种无须的列表,但键值对在底层也是有存储顺序的,popitem() 总是弹出底层中的最后一个 key-value,这和列表的 pop() 方法类似,都实现了数据结构中“出栈”的操作。

【5】 setdefault() 方法用来返回某个 key 对应的 value,其语法格式如下:

dictname.setdefault(key, defaultvalue)

说明,dictname 表示字典名称,key 表示键,defaultvalue 表示默认值(可以不写,不写的话是 None)。

当指定的 key 不存在时,setdefault() 会先为这个不存在的 key 设置一个默认的 defaultvalue,然后再返回 defaultvalue。

也就是说,setdefault() 方法总能返回指定 key 对应的 value:
1) 如果该 key 存在,那么直接返回该 key 对应的 value;
2) 如果该 key 不存在,那么先为该 key 设置默认的 defaultvalue,然后再返回该
key 对应的 defaultvalue。

【6】len() 方法确定字典有多少键值对 。(字典长度)

【7】 clear() 关键字清空字典
【8】 嵌套字典 词典也可以包含许多词典

8. 总结字典

Python 提供一组可以在字典上使用的内建方法。

方法          描述
clear()     删除字典中的所有元素
copy()      返回字典的副本
fromkeys()  返回拥有指定键和值的字典
get()       返回指定键的值
items()     返回包含每个键值对的元组的列表
keys()      返回包含字典键的列表
pop()       删除拥有指定键的元素
popitem()   删除最后插入的键值对
setdefault()   返回指定键的值。如果该键不存在,则插入具有指定值的键。
update()    使用指定的键值对字典进行更新
values()    返回字典中所有值的列表

字典内置函数&方法
Python字典包含了以下内置函数:

序号 函数              描述
1  cmp(dict1, dict2)  比较两个字典元素。

2  len(dict)          计算字典元素个数,即键的总数。

3  str(dict)          输出字典可打印的字符串表示。

4  type(variable)     返回输入的变量类型,如果变量是字典就返回字典类型。

Python字典包含了以下内置方法:

序号 函数                      描述
1  dict.clear()               删除字典内所有元素

2  dict.copy()                返回一个字典的浅复制

3  dict.fromkeys(seq[, val])   创建一个新字典,以序列 seq 中元素做字典的键,
                               val 为字典所有键对应的初始值

4  dict.get(key, default=None) 返回指定键的值,如果值不在字典中返回default值

5  dict.has_key(key)           如果键在字典dict里返回true,否则返回false

6  dict.items()               以列表返回可遍历的(键, 值) 元组数组

7  dict.keys()                以列表返回一个字典所有的键

8  dict.setdefault(key, default=None)  和get()类似, 但如果键不存在于字典中,
                                       将会添加键并将值设为default

9  dict.update(dict2)         把字典dict2的键/值对更新到dict里

10 dict.values()              以列表返回字典中的所有值

11 pop(key[,default])         删除字典给定键 key 所对应的值,返回值为被删除的值。 
                              key值必须给出。 否则,返回default值。

12 popitem()                  返回并删除字典中的最后一对键和值。

4.set集合详解

Python 中的集合,和数学中的集合概念一样,用来保存不重复的元素,即集合中的元素都是唯一的,互不相同。

从形式上看,和字典类似,Python 集合会将所有元素放在一对大括号 {} 中,相邻元素之间用“,”分隔,如下所示:

{element1,element2,...,elementn}

其中,elementn 表示集合中的元素,个数没有限制。

从内容上看,同一集合中,只能存储不可变的数据类型,包括整形、浮点型、字符串、元组,无法存储列表、字典、集合这些可变的数据类型,否则 Python 解释器会抛出 TypeError 错误。

并且需要注意的是,数据必须保证是唯一的,因为集合对于每种数据元素,只会保留一份。

由于 Python 中的 set 集合是无序的,所以每次输出时元素的排序顺序可能都不相同。
其实,Python 中有两种集合类型,一种是 set 类型的集合,另一种是 frozenset 类型的集合,它们唯一的区别是,set 类型集合可以做添加、 删除元素的操作,而 forzenset 类型集合不行。

1. 创建set集合

Python 提供了 2 种创建 set 集合的方法,分别是使用 {} 创建和使用 set() 函数将列表、元组等类型数据转换为集合。

  1. 使用 {} 创建
    在 Python 中,创建 set 集合可以像列表、元素和字典一样,直接将集合赋值给变量,从而实现创建集合的目的,其语法格式如下:
setname = {element1,element2,...,elementn}

其中,setname 表示集合的名称,起名时既要符合 Python 命名规范,也要避免与 Python 内置函数重名。

  1. set()函数创建集合
    set() 函数为 Python 的内置函数,其功能是将字符串、列表、元组、range 对象等可迭代对象转换成集合。该函数的语法格式如下:
setname = set(iteration)

其中,iteration 就表示字符串、列表、元组、range 对象等数据。

注意,如果要创建空集合,只能使用 set() 函数实现。因为直接使用一对 {},Python 解释器会将其视为一个空字典。

  1. 双括号 set(()) 构造函数
    使用 set(()) 构造函数来创建集合。

2. 访问set集合元素

由于集合中的元素是无序的,因此无法向列表那样使用下标访问元素。Python 中,访问集合元素最常用的方法是使用循环结构,将集合中的数据逐一读取出来。

3. 删除set集合

和其他序列类型一样,手动函数集合类型,也可以使用 del() 语句

4. 向 set 集合中添加元素

set 集合中添加元素,可以使用 set 类型提供的 add() 方法实现,该方法的语法格式为:

setname.add(element)

其中,setname 表示要添加元素的集合,element 表示要添加的元素内容。

需要注意的是,使用 add() 方法添加的元素,只能是数字、字符串、元组或者布尔类型(True 和 False)值,不能添加列表、字典、集合这类可变的数据,否则 Python 解释器会报 TypeError 错误。

5. 从set集合中删除元素

删除现有 set 集合中的指定元素,可以使用 remove() 方法,该方法的语法格式如下:

setname.remove(element)

使用此方法删除集合中元素,需要注意的是,如果被删除元素本就不包含在集合中,则此方法会抛出 KeyError 错误

如果我们不想在删除失败时令解释器提示 KeyError 错误,还可以使用 discard() 方法:

setname.discard(element)

此方法和 remove() 方法的用法完全相同,唯一的区别就是,当删除集合中元素失败时,此方法不会抛出任何错误。

6. 获取 Set 元素数量(长度)

len() 方法确定集合中有多少元素。

thisset = {"apple", "banana", "cherry"}

print(len(thisset))

输出:

5

7. Python set集合做交集、并集、差集运算

集合最常做的操作就是进行交集、并集、差集以及对称差集运算,首先有必要给大家普及一下各个运算的含义。

有 2 个集合,分别为 set1={1,2,3} 和 set2={3,4,5},它们既有相同的元素,也有不同的元素。
以这两个集合为例,分别做不同运算的结果。

Python set集合运算

运算操作  Python运算符   含义                            例子
交集        &          取两集合公共的元素               >>> set1 & set2
                                                      {3}

并集        |          取两集合全部的元素               >>> set1 | set2
                                                      {1,2,3,4,5}

差集        -          取一个集合中另一集合没有的元素   >>> set1 - set2
                                                      {1,2}
                                                      >>> set2 - set1
                                                      {4,5}

对称差集     ^        取集合 A 和 B 中不属于 A&B 的元素  >>> set1 ^ set2
                                                      {1,2,4,5}

8. frozenset集合

set 集合是可变序列,程序可以改变序列中的元素;frozenset 集合是不可变序列,程序不能改变序列中的元素。set两种情况下可以使用 fronzenset:当集合的元素不需要改变时,我们可以使用 fronzenset 替代 set,这样更加安全。
有时候程序要求必须是不可变对象,这个时候也要使用 fronzenset 替代 set。比如,字典(dict)的键(key)就要求是不可变对象。
集合中所有能改变集合本身的方法,比如 remove()、discard()、add() 等,frozenset 都不支持;set 集合中不改变集合本身的方法,fronzenset 都支持。

需要注意的是,set 集合本身的元素必须是不可变的, 所以 set 的元素不能是 set,只能是 frozenset,因为 frozenset 是不可变的。

9. 总结:

set集合方法:

方法名 语法格式 功能 实例
add() set1.add() 向 set1 集合中添加数字、字符串、元组或者布尔类型 >>> set1 = {1,2,3}
>>> set1.add((1,2))
>>> set1.add((1,2))
>>> set1
{(1, 2), 1, 2, 3}
clear() set1.clear() 清空 set1 集合中所有元素 >>> set1 = {1,2,3}
>>> set1.clear()
>>> set1
set()
set()才表示空集合,{}表示的是空字典
copy() set2 = set1.copy() 拷贝 set1 集合给 set2 >>> set1 = {1,2,3}
>>> set2 = set1.copy()
>>> set1.add(4)
>>> set1
{1, 2, 3, 4}
>>> set1
{1, 2, 3}
difference() set3 = set1.difference(set2) 将 set1 中有而 set2, 没有的元素给 set3 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set3 = set1.difference(set2)
>>> set3
{1, 2}
difference_update() set1.difference_update(set2) 从 set1 中删除与 set2,相同的元素 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set1.difference_update(set2)
>>> set1
{1, 2}
discard() set1.discard(elem) 删除 set1 中的 elem 元素 >>> set1 = {1,2,3}
>>> set1.discard(2)
>>> set1
{1, 3}
>>> set1.discard(4)
{1, 3}
intersection() set3 = set1.intersection(set2) 取 set1 和 set2 的交集给 set3 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set3 = set1.intersection(set2)
>>> set3
{3}
intersection_update() set1.intersection_update(set2) 取 set1和 set2 的交集,并更新给 set1 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set1.intersection_update(set2)
>>> set1
{3}
isdisjoint() set1.isdisjoint(set2) 判断 set1 和 set2 是否没有交集,有交集返回 False;没有交集返回 True >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set1.isdisjoint(set2)
False
issubset() set1.issubset(set2) 判断 set1 是否是 set2 的子集 >>> set1 = {1,2,3}
>>> set2 = {1,2}
>>> set1.issubset(set2)
False
issuperset() set1.issuperset(set2) 判断 set2 是否是 set1 的子集 >>> set1 = {1,2,3}
>>> set2 = {1,2}
>>> set1.issuperset(set2)
True
pop() a = set1.pop() 取 set1 中一个元素,并赋值给 a >>> set1 = {1,2,3}
>>> a = set1.pop()
>>> set1
{2,3}
>>> a
1
remove() set1.remove(elem) 移除 set1 中的 elem 元素 >>> set1 = {1,2,3}
>>> set1.remove(2)
>>> set1
{1, 3}
>>> set1.remove(4)
Traceback (most recent call last): File “<pyshell#90>”, line 1, in , set1.remove(4), KeyError: 4
symmetric_difference() set3 = set1.symmetric_difference(set2) 取 set1 和 set2 中互不相同 的元素,给 set3 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set3 = set1.symmetric_difference(set2)
>>> set3
{1, 2, 4}
symmetric_difference_update() set1.symmetric_difference_update(set2) 取 set1 和 set2 中互不相同的元素,并更新给 set1 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set1.symmetric_difference_update(set2)
{1, 2, 4}
union() set3 = set1.union(set2) 取 set1 和 set2 的并集,赋给 set3 >>> set1 = {1,2,3}
>>> set2 = {3,4}
>>> set3=set1.union(set2)
>>> set3
{1, 2, 3, 4}
update() set1.update(elem) 添加列表或集合中的元素到 set1 >>> set1 = {1,2,3}
>>> set1.update([3,4])
>>> set1
>>> set1
{1,2,3,4}
s.update( "字符串" ) 与 s.update( {"字符串"} ) 含义不同:
s.update( {"字符串"} ) 将字符串添加到集合中,有重复的会忽略。
s.update( "字符串" ) 将字符串拆分单个字符后,然后再一个个添加到集合中,有重复的会忽略。

十一、语句

简单的说语句实现了流程控制。
根据python文档介绍,语句包括:简单语句和复合语句。
简单语句说白了,前面介绍的大部分都是简单语句。
那么简单语句都有哪些?如下:

  1. 表达式语句
  2. 赋值语句
  3. assert 语句
  4. pass 语句
  5. del 语句
  6. return 语句
  7. yield 语句
  8. raise 语句
  9. break 语句
  10. continue 语句
  11. import 语句( future 语句)
  12. global 语句
  13. nonlocal 语句

可以参考python文档:
https://docs.python.org/zh-cn/3/reference/simple_stmts.html

复合语句如下:

  1. if 语句
  2. while 语句
  3. for 语句
  4. try 语句
  5. with 语句
  6. match 语句
  7. 函数def定义
  8. 类定义
  9. 协程(1. 协程函数定义,2. async for 语句,3. async with 语句)

可以参考python文档:
https://docs.python.org/zh-cn/3/reference/compound_stmts.html
这里着重介绍复合语句里的条件,循环,嵌套和简单语句中的assert 语句等。
10. pass 语句
11. del 语句
12. return 语句
13. yield 语句
14. raise 语句
15. break 语句
16. continue 语句
17. import 语句( future 语句)
18. global 语句

和其它编程语言一样,按照执行流程划分,Python 程序也可分为 3 大结构,即顺序结构、选择(分支)结构和循环结构:

Python 顺序结构就是让程序按照从头到尾的顺序依次执行每一条 Python 代码,不重复执行任何代码,也不跳过任何代码。
Python 选择结构也称分支结构,就是让程序“拐弯”,有选择性的执行代码;换句话说,可以跳过没用的代码,只执行有用的代码。
Python 循环结构就是让程序“杀个回马枪”,不断地重复执行同一段代码。

1. 条件语句

代码都是顺序执行的,也就是先执行第1条语句,然后是第2条、第3条……一直到最后一条语句,这称为顺序结构。
但是对于很多情况,顺序结构的代码是远远不够的,比如一个程序限制了只能成年人使用,儿童因为年龄不够,没有权限使用。这时候程序就需要做出判断,看用户是否是成年人,并给出提示。Python条件语句是通过一条或多条语句的执行结果(True或者False)来决定执行的代码块。

Python程序语言指定任何非0和非空(null)值为true,0 或者 null为false。

Python 编程中 if 语句用于控制程序的执行,基本形式为:

if 判断条件:
    执行语句……
else:
    执行语句……

其中"判断条件"成立时(非零),则执行后面的语句,而执行内容可以多行,以缩进来区分表示同一范围。
else 为可选语句,当需要在条件不成立时执行内容则可以执行相关语句。

在 Python 中,可以使用 if else 语句对条件进行判断,然后根据不同的结果执行不同的代码,这称为选择结构或者分支结构。
Python 中的 if else 语句可以细分为三种形式,分别是 if 语句、if else 语句和
if elif else 语句。

执行过程最简单的就是第一种形式——只有一个 if 部分。如果表达式成立(真),就执行后面的代码块;如果表达式不成立(假),就什么也不执行。

对于第二种形式if else 语句,如果表达式成立,就执行 if 后面紧跟的代码块;如果表达式不成立,就执行 else 后面紧跟的代码块。

对于第三种形式if elif else 语句,Python 会从上到下逐个判断表达式是否成立,一旦遇到某个成立的表达式,就执行后面紧跟的语句块;此时,剩下的代码就不再执行了,不管后面的表达式是否成立。如果所有的表达式都不成立,就执行 else 后面的代码块。
多重形式和第三种一样,只是多跑了几步。

if 语句的判断条件可以用 >(大于)、<(小于)、==(等于)、>=(大于等于)、<=(小于等于)来表示其关系。

当判断条件为多个值时,可以使用以下形式:

if 判断条件1:
    执行语句1……
elif 判断条件2:
    执行语句2……
elif 判断条件3:
    执行语句3……
else:
    执行语句4……

由于 python 并不支持 switch 语句,所以多个条件判断,只能用 elif 来实现,如果判断 需要多个条件需同时判断时,可以使用 or (或),表示两个条件有一个成立时判断条件成 功;使用 and (与)时,表示只有两个条件同时成立的情况下,判断条件才成功。
总起来说,不管有多少个分支,都只能执行一个分支,或者一个也不执行,不能同时执行多个分支。

需要强调的是,Python 是一门非常独特的编程语言,它通过缩进来识别代码块,具有相同缩进量的若干行代码属于同一个代码块,所以你不能胡乱缩进,这样很容易导致语法错误。Python 是以缩进来标记代码块的,代码块一定要有缩进,一般都是 4 格,没有缩进的不是代码块。
另外,同一个代码块的缩进量要相同,缩进量不同的不属于同一个代码块。if,elseif,else 在同一纵线(垂直线)对齐。一个代码块的所有语句都要缩进,而且缩进量必须相同。如果某个语句忘记缩进了,Python 解释器并不一定会报错,但是程序的运行逻辑往往会有问题。

if 和 elif 后面的“表达式”的形式是很自由的,只要表达式有一个结果,不管这个结果是什么类型,Python 都能判断它是“真”还是“假”。

布尔类型(bool)只有两个值,分别是 True 和 False,Python 会把 True 当做“真”,把 False 当做“假”。

对于数字,Python 会把 0 和 0.0 当做“假”,把其它值当做“真”。

对于其它类型,当对象为空或者为 None 时,Python 会把它们当做“假”,其它情况当做真。比如,下面的表达式都是不成立的:

""  #空字符串
[ ]  #空列表
( )  #空元组
{ }  #空字典
None  #空值

说明:对于没有 return 语句的函数,返回值为空,也即 None。

if 语句包含零个或多个 elif 子句及可选的 else 子句。关键字 ‘elif’ 是 ‘else if’ 的缩写,适用于避免过多的缩进。if … elif … elif … 序列可以当作其他语言中 switch 或 case 语句的替代品。
如果要把一个值与多个常量进行比较,或者检查特定类型或属性,match 语句更实用。

python 复合布尔表达式计算采用短路规则,即如果通过前面的部分已经计算出整个表达式的值,
则后面的部分不再计算。如下面的代码将正常执行不会报除零错误:test.py

a=0
b=1
if ( a > 0 ) and ( b / a > 2 ):
    print("yes")
else :
    print("no")

no

而下面的代码就会报错:test1.py

a=0
b=1
if ( a > 0 ) or ( b / a > 2 ):
    print("yes")
else :
    print("no")

输出:

Traceback (most recent call last):
  File "H:\临时文件夹\test1.py", line 3, in <module>
    if ( a > 0 ) or ( b / a > 2 ):
ZeroDivisionError: division by zero

2. 循环(loop)语句和简单语句 break、continue

程序在一般情况下是按顺序执行的。
python提供了各种控制结构,允许更复杂的执行路径。
循环语句允许我们执行一个语句或语句组多次。

Python 提供了 for 循环和 while 循环(在 Python 中没有 do…while 循环):

循环类型 描述

while 循环 在给定的判断条件为 true 时执行循环体,否则退出循环体。
for 循环   重复执行语句
嵌套循环  你可以在while循环体中嵌套for循环

1. for-loop 语句

for 循环,它常用于遍历字符串、列表、元组、字典、集合等序列类型,逐个获取序列中的各个元素。
for 循环的语法格式如下:

for 迭代变量 in 字符串|列表|元组|字典|集合:
    代码块

格式中,迭代变量用于存放从序列类型变量中读取出来的元素,所以一般不会在循环中对迭代变量手动赋值;代码块指的是具有相同缩进格式的多行代码(和 while 一样),由于和循环结构联用,因此代码块又称为循环体。

for i in range(0,10):
	print(i)

当用 for 循环遍历 list 列表或者 tuple 元组时,其迭代变量会先后被赋值为列表或元组中的每个元素并执行一次循环体。

在使用 for 循环遍历字典时,经常会用到和字典相关的 3 个方法,即 items()、keys() 以及 values(),它们各自的用法已经在前面章节中讲过,这里不再赘述。当然,如果使用 for 循环直接遍历字典,则迭代变量会被先后赋值为每个键值对中的键。

2. while-loop 语句

while 循环和 if 条件分支语句类似,即在条件(表达式)为真的情况下,会执行相应的代码块。
不同之处在于,只要条件为真,while 就会一直重复执行那段代码块。

while 语句的语法格式如下:

while 条件表达式:
    代码块

这里的代码块,指的是缩进格式相同的多行代码,不过在循环结构中,它又称为循环体。

i = 0
while i < 5:
    i += 1
    print(i)

while 语句执行的具体流程为:首先判断条件表达式的值,其值为真(True)时,则执行代码块中的语句,当执行完毕后,再回过头来重新判断条件表达式的值是否为真,若仍为真,则继续重新执行代码块…如此循环,直到条件表达式的值为假(False),才终止循环。

注意,在使用 while 循环时,一定要保证循环条件有变成假的时候,否则这个循环将成为一个死循环。所谓死循环,指的是无法结束循环的循环结构,除非我们强制关闭解释器(CTRL-C)。

while 循环还常用来遍历列表、元组和字符串,因为它们都支持通过下标索引获取指定位置的元素。

Python 中,无论是 while 循环还是 for 循环,其后都可以紧跟着一个 else 代码块,它的作用是当循环条件为 False 跳出循环时,程序会最先执行 else 代码块中的代码。

3. 嵌套循环(Nested-loops)

Python 不仅支持 if 语句相互嵌套,while 和 for 循环结构也支持嵌套。所谓嵌套(Nest),就是一条语句里面还有另一条语句,例如 for 里面还有 for,while 里面还有 while,甚至 while 中有 for 或者 for 中有 while 也都是允许的。允许在一个循环体里面嵌入另一个循环。

Python for 循环嵌套语法:

for iterating_var in sequence:
   for iterating_var in sequence:
      statements(s)
   statements(s)

Python while 循环嵌套语法:

while expression:
   while expression:
      statement(s)
   statement(s)

当 2 个(甚至多个)循环结构相互嵌套时,位于外层的循环结构常简称为外层循环或外循环,位于内层的循环结构常简称为内层循环或内循环。

循环嵌套结构的代码,Python 解释器执行的流程为:

  1. 当外层循环条件为 True 时,则执行外层循环结构中的循环体;
  2. 外层循环体中包含了普通程序和内循环,当内层循环的循环条件为 True 时会执行此循环中的循环体,直到内层循环条件为 False,跳出内循环;
  3. 如果此时外层循环的条件仍为 True,则返回第 2 步,继续执行外层循环体,直到外层循环的循环条件为 False;
  4. 当内层循环的循环条件为 False,且外层循环的循环条件也为 False,则整个嵌套循环才算执行完毕。

嵌套循环执行的总次数 = 外循环执行次数 * 内循环执行次数 if、while、for 之间完全支持多层( ≥3 )嵌套。例如:

if ...:
   while ...:
       for ...:
           if ...:
               ...

也就是说,只要场景需要,判断结构和循环结构之间完全可以相互嵌套,甚至可以多层嵌套。

用例:
使用循环嵌套来获取100以内的质数

#!/usr/bin/python
# -*- coding: UTF-8 -*-

num=[];
i=2
for i in range(2,100):
   j=2
   for j in range(2,i):
      if(i%j==0):
         break
   else:
      num.append(i)
print(num)

使用嵌套循环实现×字塔的实现

#!/usr/bin/python
# -*- coding: UTF-8 -*-

#*字塔
i=1
#j=1
while i<=9:
   if i<=5:
      print ("*"*i)

   elif i<=9 :
      j=i-2*(i-5)
      print("*"*j)
   i+=1
else :
   print("")

冒泡排序

#!/usr/bin/python
# -*- coding: UTF-8 -*-

array = [9,2,7,4,5,6,3,8,1,10]
L = len(array)
for i in range(L):
    for j in range(L-i):
        if array[L-j-1]<array[L-j-2]:
            array[L-j-1],array[L-j-2]=array[L-j-2],array[L-j-1]
for i in range(L):
    print array[i],

求区间[a,b]内的质数

#!/usr/bin/python
# -*- coding: UTF-8 -*-

a = 1000 #起始
b = 10000 #结束

E = []
for num in range(a,b+1):
   snum = int(num*0.5+1)
   for i in range(2,snum): 
      if num%i == 0: 
         break 
   else: 
      E.append(num)
print a,'到',b,'的质数有',E
print a,'到',b,'有',len(E),'个质数' 

选择排序:

array = [8,2,6,3,4,5,7,1,10,9]
L=len(array)
for i in range(1,L):
    temp = array[i]
    array.remove(array[i])
    for j in range(i):
        if array[j]>temp:
            array.insert(j,temp)
            break
    else:
        array.insert(i,temp)
print(array)

10以内遍历或迭代

i = 0
while i<10:
    for j in range(10):
        print("i=",i," j=",j)
    i=i+1
  • 乘法口诀表练习 有多少方法?
    自己研究,不要抄袭抄录复制之类起不到学习的目的 学习和锻炼

在执行 while 循环或者 for 循环时,只要循环条件满足,程序将会一直执行循环体,不停地转圈。但在某些场景,我们可能希望在循环结束前就强制结束循环,Python 提供了 2 种强制离开当前循环体的办法:
【1】 使用 continue 语句,可以跳过执行本次循环体中剩余的代码,转而执行下一次的循环。
continue 语句只会终止执行本次循环中剩下的代码,直接从下一次循环继续执行。
continue 语句用来告诉Python跳过当前循环的剩余语句,然后继续进行下一轮循环。
continue语句用在while和for循环中。

Python 语言 continue 语句语法格式如下:

句块
   continue

continue 语句是一个删除的效果,他的存在是为了删除满足循环条件下的某些不需要的成分:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

var = 10

while var > 0:
    var = var -1
    if var == 5 or var == 8:
        continue
    print '当前值 :', var
print "Good bye!"

这里效果是去掉5和8,执行效果如下:

当前值 : 9
当前值 : 7
当前值 : 6
当前值 : 4
当前值 : 3
当前值 : 2
当前值 : 1
当前值 : 0
Good bye!

我们想只打印0-10之间的奇数,可以用continue语句跳过某些循环:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

n = 0
while n < 10:
    n = n + 1
    if n % 2 == 0:      # 如果n是偶数,执行continue语句
        continue        # continue语句会直接继续下一轮循环,后续的print()语句不会执行
    print(n)

执行效果如下:

1
3
5
7
9

【2】 只用 break 语句,可以完全终止当前循环。
break 语句可以立即终止当前循环的执行,跳出当前所在的循环结构。无论是 while 循环还是 for 循环,只要执行 break语句,就会直接结束当前正在执行的循环体。
break 语句一般会结合 if 语句进行搭配使用,表示在某种条件下跳出循环体。
注意,通过前面的学习我们知道,for 循环后也可以配备一个 else 语句。这种情况下,如果使用 break 语句跳出循环体,不会执行 else 中包含的代码。
使用 break 跳出当前循环体之后,该循环后的 else 代码块也不会被执行。但是,如果将 else 代码块中的代码直接放在循环体的后面,则该部分代码将会被执行。
break语句用来终止循环语句,即循环条件没有False条件或者序列还没被完全递归完,也会停止执行循环语句。

break语句用在while和for循环中。

如果您使用嵌套循环,break语句将停止执行最深层的循环,并开始执行下一行代码。

Python语言 break 语句语法:

句块
   break

Python continue 语句跳出本次循环,而break跳出整个循环。
【3】 pass 是空语句,是为了保持程序结构的完整性。
pass 不做任何事情,一般用做占位语句。
Python 语言 pass 语句语法格式如下:

pass

pass 便是占据一个位置,因为如果定义一个空函数程序会报错,当你没有想好函数的内容是可以用 pass 填充,使程序可以正常运行。

【4】 range() 常用于遍历数字序列,该函数可以生成算术级数:

for i in range(5):
    print(i)

执行效果如下:

0
1
2
3
4

生成的序列不包含给定的终止数值;range(10) 生成 10 个值,这是一个长度为 10 的序列,其中的元素索引都是合法的。range 可以不从 0 开始,还可以按指定幅度递增(递增幅度称为 ‘步进’,支持负数):

print(list(range(5, 10)))

执行效果如下:

[5, 6, 7, 8, 9]
print(list(range(0, 10, 3)))

执行效果如下:

[0, 3, 6, 9]
print(list(range(-10, -100, -30)))

执行效果如下:

[-10, -40, -70]

range() 和 len() 组合在一起,可以按索引迭代序列:

a = ['Mary', 'had', 'a', 'little', 'lamb']
for i in range(len(a)):
    print(i, a[i])

执行效果如下:

0 Mary
1 had
2 a
3 little
4 lamb

不过,大多数情况下,enumerate() 函数更便捷,详见 循环的技巧 。

如果只输出 range,会出现意想不到的结果:

print(range(10))
print(range(0, 10))

执行效果如下:

range(0, 10)
range(0, 10)

range() 返回对象的操作和列表很像,但其实这两种对象不是一回事。迭代时,该对象基于所需序列返回连续项,并没有生成真正的列表,从而节省了空间。
这种对象称为可迭代对象 iterable,函数或程序结构可通过该对象获取连续项,直到所有元素全部迭代完毕。for 语句就是这样的架构,sum() 是一种把可迭代对象作为参数的函数:

print(sum(range(4)))  # 0 + 1 + 2 + 3)  # 0 + 1 + 2 + 3

执行效果如下:

6

【5】match 语句采用表达式,并将其值与作为一个或多个大小写块给出的连续模式进行比较。这在表面上类似于C,Java或JavaScript(以及许多其他语言)中的switch语句,但它更类似于Rust或Haskell等语言中的模式匹配。只有匹配的第一个模式才会被执行,并且它还可以将组件(序列元素或对象属性)从值中提取到变量中。

最简单的形式是将一个目标值与一个或多个字面值进行比较:

def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the internet"

注意最后一个代码块:“变量名” _ 被作为 通配符 并必定会匹配成功。 如果没有 case 语句匹配成功,则不会执行任何分支。

使用 | (“ or ”)在一个模式中可以组合多个字面值:

case 401 | 403 | 404:
    return "Not allowed"

模式的形式类似解包赋值,并可被用于绑定变量:

# point is an (x, y) tuple
match point:
    case (0, 0):
        print("Origin")
    case (0, y):
        print(f"Y={y}")
    case (x, 0):
        print(f"X={x}")
    case (x, y):
        print(f"X={x}, Y={y}")
    case _:
        raise ValueError("Not a point")

请仔细研究此代码! 第一个模式有两个字面值,可以看作是上面所示字面值模式的扩展。但接下来的两个模式结合了一个字面值和一个变量,而变量 绑定 了一个来自目标的值(point)。
第四个模式捕获了两个值,这使得它在概念上类似于解包赋值
(x, y) = point。
如果使用类实现数据结构,可在类名后加一个类似于构造器的参数列表,这样做可以把属性放到变量里:

class Point:
    x: int
    y: int

def where_is(point):
    match point:
        case Point(x=0, y=0):
            print("Origin")
        case Point(x=0, y=y):
            print(f"Y={y}")
        case Point(x=x, y=0):
            print(f"X={x}")
        case Point():
            print("Somewhere else")
        case _:
            print("Not a point")

可在 dataclass 等支持属性排序的内置类中使用位置参数。还可在类中设置 __match_args__ 特殊属性为模式的属性定义指定位置。
如果它被设为 (“x”, “y”),则以下模式均为等价的,并且都把 y 属性绑定到 var 变量:

Point(1, var)
Point(1, y=var)
Point(x=1, y=var)
Point(y=var, x=1)

读取模式的推荐方式是将它们看做是你会在赋值操作左侧放置的内容的扩展形式,以便理解各个变量将会被设置的值。 只有单独的名称(例如上面的 var)会被 match 语句所赋值。 带点号的名称 (例如 foo.bar)、属性名称(例如上面的 x= 和 y=)或类名称(通过其后的 “(…)” 来识别,例如上面的 Point)都绝不会被赋值。

模式可以任意地嵌套。例如,如果有一个由点组成的短列表,则可使用如下方式进行匹配:

match points:
    case []:
        print("No points")
    case [Point(0, 0)]:
        print("The origin")
    case [Point(x, y)]:
        print(f"Single point {x}, {y}")
    case [Point(0, y1), Point(0, y2)]:
        print(f"Two on the Y axis at {y1}, {y2}")
    case _:
        print("Something else")

为模式添加成为守护项的 if 子句。如果守护项的值为假,则 match 继续匹配下一个 case 语句块。
注意,值的捕获发生在守护项被求值之前:

match point:
    case Point(x, y) if x == y:
        print(f"Y=X at {x}")
    case Point(x, y):
        print(f"Not on the diagonal")

match 语句的其他特性:
与解包赋值类似,元组和列表模式具有完全相同的含义,并且实际上能匹配任意序列。 但它们不能匹配迭代器或字符串。
序列模式支持扩展解包操作:[x, y, [星号]rest] 和 (x, y, *rest) 的作用类似于解包赋值。 在 * 之后的名称也可以为 _,因此,(x, y, *) 可以匹配包含至少两个条目的序列,而不必绑定其余的条目。
映射模式:{“bandwidth”: b, “latency”: l} 从字典中捕获 “bandwidth” 和 “latency” 的值。
与序列模式不同,额外的键会被忽略。
**rest 等解包操作也支持。但 **
是冗余的,不允许使用。
使用 as 关键字可以捕获子模式:

case (Point(x1, y1), Point(x2, y2) as p2): ...

将把输入的第二个元素捕获为 p2 (只要输入是包含两个点的序列)大多数字面值是按相等性比较的,但是单例对象 True, False 和 None 则是按标识号比较的。
模式可以使用命名常量。 这些命名常量必须为带点号的名称以防止它们被解读为捕获变量:

from enum import Enum
class Color(Enum):
    RED = 'red'
    GREEN = 'green'
    BLUE = 'blue'

color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))

match color:
    case Color.RED:
        print("I see red!")
    case Color.GREEN:
        print("Grass is green")
    case Color.BLUE:
        print("I'm feeling the blues :(")

4. 设计和调试

【1】If 语句的规则

  1. 每一个“if 语句”必须包含一个 else.
  2. 如果这个 else 永远都不应该被执行到,因为它本身没有任何意义,那你必须在 else 语句后面使用一个叫做 die 的函数,让它打印出错误信息并且死给你看,这和上一节的习题类似,这样你可以找到很多的错误。
  3. “if 语句”的嵌套不要超过 2 层,最好尽量保持只有 1 层。 这意味着如果你在 if 里边又有了一个 if,那你就需要把第二个 if 移到另一个函数里面。
  4. 将“if 语句”当做段落来对待,其中的每一个 if, elif, else 组合就跟一个段落的句子组合一样。在这种组合的最前面和最后面留一个空行以作区分。
  5. 你的布尔测试应该很简单,如果它们很复杂的话,你需要将它们的运算事先放到一个变量里,并且为变量取一个好名字。
    如果你遵循上面的规则,你就会写出比大部分程序员都好的代码来。回到上一个练习中,看看我有没有遵循这些规则,如果没有的话,就将其改正过来。
    Warning在日常编程中不要成为这些规则的奴隶。在训练中,你需要通过这些规则的应用来巩固你学到的知识,而在实际编程中这些规则有时其实很蠢。如果你觉得哪个规则很蠢,就别使用它。

【2】循环的规则
1 只有在循环永不停止时使用“while 循环”,这意味着你可能永远都用不到。这条只有 Python中成立,其他的语言另当别论。
2. 其他类型的循环都使用“for 循环”,尤其是在循环的对象数量固定或者有限的情况下。

【3】调试(debug)的小技巧

  1. 不要使用 “debugger”。 Debugger 所作的相当于对病人的全身扫描。你并不会得到某方面的有用信息,而且你会发现它输出的信息态度,而且大部分没有用,或者只会让你更困惑。
  2. 最好的调试程序的方法是使用 print 在各个你想要检查的关键环节将关键变量打印出来,从而检查哪里是否有错。
  3. 让程序一部分一部分地运行起来。不要等一个很长的脚本写完后才去运行它。写一点,运行一点,再修改一点

十二、对象、类和各种关系


1. 初识对象和类

Python 是一种“面向对象编程语言(Object Oriented Programming Language)”。这个说法的意思是说,Python 里边有一种叫做 class 的结构,通过它你可以用一种特殊的方式构造你的软件。通过使用class(类),你可以让你的程序架构更为整齐,使用起来也会更为干净——至少理论上应该是这样的。
现在我要教你的是面向对象编程的起步知识,我会用你学过的知识向你介绍面向对象编程、类、以及对象。问题是变相对象编程(简称 OOP)本身就是个奇怪的东西,你只有努力去弄懂这一章的内容,好好写代码,然后到下一章节的习题,我就能把 OOP 像钉钉子一样钉到你脑子里了。
现在就开始吧。

  1. 模块和字典差不多
    你知道怎样创建和使用字典这个数据类型,这是一种将一种东西对应到另外一种的方式。这意味着如果你有一个字典,它里边有一个叫 ‘apple’ 的 key,而你要从中取值的话,你需要这样做:mystuff.py
mystuff = {'apple': "I AM APPLES!"}
print(mystuff['apple'])

记住这个“从 Y 获取 X”的概念,现在再来看看模块(module),你已经创建和使用过一些模块了,你已经了解了它们的一些属性:

【1】 模组是包含函数和变量的 Python 文件。
【2】 你可以 import 这个文件。
【3】然后你可以使用 ‘.’ 操作符访问到模组中的函数和变量。

假如说我有一个模块名字叫 mystuff.py 并且在里边放了个叫做 apple 的函数,就像这样:

# this goes in mystuff.py
def apple():
    print("I AM APPLES!")

接下来我就可以用 import 来调用这个模块,并且访问到 apple 函数:

import mystuff
mystuff.apple()

我还可以放一个叫做 tangerine 的变量到模块里边:

def apple():
 print("I AM APPLES!")
# this is just a variable
tangerine =("Living reflection of a dream")

一样的我还是可以访问到这个变量:

import mystuff
mystuff.apple()
print(mystuff.tangerine)

回到字典的概念,你会发现这和字典的使用方式有点相似,只不过语法不同而已,我们来比一比:

mystuff['apple'] # get apple from dict
mystuff.apple() # get apple from the module
mystuff.tangerine # same thing, it's just a variable

也就是说,Python 里边有这么一个通用的模式:

【1】 拿一个类似 key=value 风格的数据容器
【2】 通过 key 的名称获取其中的 value

对于字典来说,key 是一个字符串,获得值的语法是 [key] 。对于模块来说,key 是函数或者变量的名称,而语法是 .key 。除了这个,它们基本上就没什么区别了。

  1. 类和模块差不多
    模块还可以用一种方法去理解:你可以把它们当做一种特殊的字典,通过它们你可以储存一些 Python代码,而你可以通过 ‘.’操作符访问到这些代码。Python 还有另外一种代码结构用来实现类似的目的,那就是 类(class) ,通过类,你可以把一组函数和数据放到一个容器中,从而用 ‘.’操作符访问到它们。如果我要用创建 mystuff 模块的方法来创建一个类,那么方法大致是这样的:
class MyStuff(object):
 def __init__(self):
 self.tangerine = "And now a thousand years between"
 def apple(self):
 print("I AM CLASSY APPLES!")

这个和模块比起来有些复杂,确实,比起模块来,这里的确做了很多事情,不过你应该能大致看出来,这段代码差不多就是模拟了一个名字叫 MyStuff 的迷你模块,里边有一个叫做 apple() 的函数,难懂的 恐怕是__init__() 函数,还有就是设置 tangerine 变量时用了 self.tangerine 这样的语法。
使用类而非模块的原因如下:你可以拿着上面这个类,重复创建出很多出来,哪怕是一次一百万个,它们也不会互相干涉到。而对于模块来说,当你一次 import 之后,整个程序里就只有这么一份内容,只有鼓捣得很深才能弄点花样出来。
不过在弄懂这个之前,你要先理解“对象(object)”是什么东西,以及如何使用 MyStuff 达到类似import mystuff 实现的结果。

  1. 对象相当于迷你版的 import
    如果说类和迷你模块差不多,那么对于类来说,也必然有一个类似 import 的概念。这个概念名称就是“实例(instance)”。这只是一种故作高深的叫法而已,它的意思其实是“创建”。当你将一个类“实例 化”以后,你就得到了一个 对象(object) 。实现实例化的方法,就是像调用函数一样地调用一个类:
thing = MyStuff()
thing.apple()
print(thing.tangerine)

第一行代码就是“实例化”操作,这和调用函数很相似。然而,当你进行实例化操作时,Python 在背后
做了一系列的工作,这里我针对上面的代码详细解释一下:

【1】Python 看到了 MyStuff() 并且知道了它是你定义过的一个类。

【2】 Python 创建了一个空的对象,里边包含了你在类中用 def 创建的所有函数。

【3】然后 Python 回去检查你是不是在里边创建了一个 __init__ 魔法函数,如果你有创建,它就会调用这个函数,从而对你的空对象实现了初始化。

【4】 在 MyStuff 中的__init__ 函数里,我们有一个多余的函数叫做 self ,这就是 Python 为我们创建的空对象,而我可以对它进行类似模块、字典等的操作,为它设置一些变量进去。

【5】 在这里,我把 self.tangerine 设成了一段歌词,这样我就初始化了该对象。

【6】最后 Python 将这个新建的对象赋给一个叫 thing 的变量,以供后面使用。

这就是当你像调用函数一样调用类的时候, Python 完成这个“迷你 import”的过程。记住这不是拿来一个类就直接用,而是将类当做一个“蓝图”,然后用它创建和这个类有相同属性的拷贝。 提醒你一点,
我的解释和 Python 的实际原理还是有一点小小的出入,只不过在这里,基于你现有的关于模块的知识,我也暂时只能这么解释了。事实上类和对象和模组是完全不同的东西。如果我实实在在地跟你讲的话,我大概会说出下面的这些东西:

• 类就像一种蓝图、或者一种预定义的东西,通过它可以创建新的迷你模块。
• 实例化的过程相当于你创建了这么一个迷你模块,而且同时 import 了它。
• 结果生成的迷你模块就是一个对象,你可以将它赋予一个变量并进行后续操作。

而通过这一系列的操作,类和对象和模块已经很不同了,所以这里的内容只是为了让你理解类的概念而已。
从东西里获取东西现在我有三种方法可以从某个东西里获取它的内容:

# dict style
mystuff['apples']

# module style
mystuff.apples()
print(mystuff.tangerine)

# class style
thing = MyStuff()
thing.apples()

print(thing.tangerine)

第一个类的例子, 你应该开始注意到这三种 key=value 的容器类数据,而且有一些问题要问。先别问,下面一讲会让你了解面向对象编程的一些专有词汇。在这一节里,我只要求你写代码并让它运行起来,有了经验才能继续 前进。

class Song(object):
    def __init__(self, lyrics):
        self.lyrics = lyrics
    def sing_me_a_song(self):
        for line in self.lyrics:
            print line
happy_bday = Song(["Happy birthday to you",
                "I don't want to get sued",
                "So I'll stop right there"])
bulls_on_parade = Song(["They rally around the family",
                     "With pockets full of shells"])
happy_bday.sing_me_a_song()
bulls_on_parade.sing_me_a_song()

你应该看到的输出结果:

Happy birthday to you
I don't want to get sued
So I'll stop right there
They rally around the family
With pockets full of shells

(1) 使用这种方式写更多的歌进去,确定自己懂得了传入的歌词是一个字符串列表。
(2) 将歌词放到另一个的变量里边,然后再类里边使用这一个新定义的变量。
(3) 试着看能不能给它加些新功能,不知道怎么做也没关系,只要试着去做也行,弄坏了也没关系,反正它也不会疼。
(4) 在网上搜索一下“object oriented programming”(中文:面向对象编程),给自己洗洗脑。
弄不懂也没关系,其实里边有一半的东西对我来说也是没有意义的。

有一个重要的概念你需要弄明白,那就是“类(class)”和“对象(object)”的区别。问题在于,class 和 object 并没有真正的不同。它们其实是同样的东西,只是在不同的时间名字不同罢了。我用禅语来解释一下吧:
鱼和泥鳅有什么区别?
这个问题有没有让你有点晕呢?说真的,坐下来想一分钟。我的意思是说,鱼和泥鳅是不一样,不过它们其实也是一样的是不是?泥鳅是鱼的一种,所以说没什么不同,不过泥鳅又有些特别,它和别的种类的鱼的确不一样,比如泥鳅和黄鳝就不一样。所以泥鳅和鱼既相同又不同。怪了。
这个问题让人晕的原因是大部分人不会这样去思考问题,其实每个人都懂这一点,你无须去思考鱼和泥 鳅的区别,因为你知道它们之间的关系。你知道泥鳅是鱼的一种,而且鱼还有别的种类,根本就没必要去思考这类问题。
让我们更进一步,假设你有一只水桶,里边有三条泥鳅。假设你的好人卡多到没地方用,于是你给它们分别取名叫小方,小斌,小星。现在想想这个问题:
小方和泥鳅有什么区别?
这个问题一样的奇怪,但比起鱼和泥鳅的问题来还好点。你知道小方是一条泥鳅,所以他并没什么不同,
他只是泥鳅的一个“实例(instance)”。小斌和小星一样也是泥鳅的实例。我的意思是说,它们是由泥鳅创建出来的,而且代表着和泥鳅一样的属性。
所以我们的思维方式是(你可能会有点不习惯):鱼是一个“类(class)”,泥鳅是一个“类(class)”,而小方是一个“对象(object)”。仔细想想,然后我再一点一点慢慢解释给你。
鱼是一个“类”,表示它不是一个真正的东西,而是一个用来描述具有同类属性的实例的概括性词汇。
你有鳍?你有鳔?你住在水里?好吧那你就是一条鱼。 后来河蟹养殖专家路过,看到你的水桶,于是告诉你:“小伙子,你这些鱼是泥鳅。” 专家一出,真相即现。并且专家还定义了一个新的叫做“泥鳅”的“类”,
而这个“类”又有它特定的属性。细长条? 有胡须?爱钻泥巴?吃起来味道还可以?那你就是一条泥鳅。最后家庭煮父过来了,他跟河蟹专家说:“非也非也,你看到的是泥鳅,我看到的是小方,而且我要把 小方和剁椒配一起做一道小菜。”于是你就有了一只叫做小方的泥鳅的“实例(instance)”(泥鳅也是鱼
的一个“实例”),并且你使用了它(把它塞到你的胃里了),这样它就是一个“对象(object)”。
这会你应该了解了:小方是泥鳅的成员,而泥鳅又是鱼的成员。这里的关系式:对象属于某个类,而某个类又属于另一个类。

  1. 写成代码是什么样子
    这个概念有点绕人,不过实话说,你只要在创建和使用 class 的时候操心一下就可以了。我来给你两个区分 Class 和 Object 的小技巧。 首先针对类和对象,你需要学会两个说法,“is-a(是啥)”和“has-a(有啥)”。“是啥”要用在谈论“两者以类的关系互相关联”的时候,而“有啥”要用在“两者无共同点,仅是互为参照”的时候。 接下来,通读这段代码,将每一个注解为 ##?? 的位置标明他是“is-a”还是“has-a”的关系,并讲明白这个关系是什么。在代码的开始我还举了几个例子,所以你只要写剩下的就可以了。
    记住,“是啥”指的是鱼和泥鳅的关系,而“有啥”指的是泥鳅和鳃的关系。
    (译注:为了解释方便,译文使用了中文鱼名。原文使用的是“三文鱼(salmon)”和“大比目鱼(halibut)”,名字也是英文常用人名。)

  2. 关于 class Name(object)
    记得我曾经强迫让你使用 class Name(object) 却没告诉你为什么吧,现在你已经知道了“类” 和“对象”的区别,我就可以告诉你原因了。如果我早告诉你的话,你可能会晕掉,也学不会这门技术了。
    真正的原因是在 Python 早期,它对于 class 的定义在很多方面都是严重有问题的。当他们承认这一点的时候已经太迟了,所以逼不得已,他们需要支持这种有问题的 class。为了解决已有的问题,他们需要引入一种“新类”,这样的话“旧类”还能继续使用,而你也有一个新的正确的类可以使用了。
    这就用到了“类即是对象”的概念。他们决定用小写的“object”这个词作为一个类,让你在创建新类时从它继承下来。有点晕了吧?一个类从另一个类继承,而后者虽然是个类,但名字却叫“object”……不过在定义类的时候,别忘记要从 object 继承就好了。
    的确如此。一个词的不同就让这个概念变得更难理解,让我不得不现在才讲给你。现在你可以试着去理解“一个是对象的类”这个概念了,如果你感兴趣的话。
    不过我还是建议你别去理解了,干脆完全忘记旧格式和新格式类的区别吧,就假设 Python 的 class永远都要求你加上 (object) 好了,你的脑力要留着思考更重要的问题。

2. 面向对象

Python 语言在设计之初,就定位为一门面向对象的编程语言,“Python 中一切皆对象”就是对 Python 这门编程语言的完美诠释。

类和对象是 Python 的重要特征,相比其它面向对象语言,Python 很容易就可以创建出一个类和对象。
同时,Python 也支持面向对象的三大特征:封装、继承和多态。

面向对象编程是在面向过程编程的基础上发展来的,它比面向过程编程具有更强的灵活性和扩展性。
面向对象编程是程序员发展的分水岭,很多初学者会因无法理解面向对象而放弃学习编程。

面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。
比如说,将乱七八糟的数据扔进列表中,这就是一种简单的封装,是数据层面的封装;把常用的代码块打包成一个函数,这也是一种封装,是语句层面的封装。

代码封装,其实就是隐藏实现功能的具体代码,仅留给用户使用的接口,就好像使用计算机,用户只需要使用键盘、鼠标就可以实现一些功能,而根本不需要知道其内部是如何工作的。

打个比方,若在某游戏中设计一个乌龟的角色,应该如何来实现呢?使用面向对象的思想会更简单,可以分为如下两个方面进行描述:
从表面特征来描述,例如,绿色的、有 4 条腿、重 10 kg、有外壳等等。
从所具有的的行为来描述,例如,它会爬、会吃东西、会睡觉、会将头和四肢缩到壳里,等等。

如果将乌龟用代码来表示,则其表面特征可以用变量来表示,其行为特征可以通过建立各种函数来表示。参考代码如下所示:

class tortoise:
    bodyColor = "绿色"
    footNum = 4
    weight = 10
    hasShell = True
    #会爬
    def crawl(self):
        print("乌龟会爬")
    #会吃东西
    def eat(self):
        print("乌龟吃东西")
    #会睡觉
    def sleep(self):
        print("乌龟在睡觉")
    #会缩到壳里
    def protect(self):
        print("乌龟缩进了壳里")

注意,以上代码仅是为了演示面向对象的编程思想,具体细节后续会做详细介绍。

因此,从某种程序上,相比较只用变量或只用函数,使用面向对象的思想可以更好地模拟现实生活中的事物。

不仅如此,在 Python 中,所有的变量其实也都是对象,包括整形(int)、浮点型(float)、字符串(str)、列表(list)、元组(tuple)、字典(dict)和集合(set)。以字典(dict)为例,它包含多个函数供我们使用,例如使用 keys() 获取字典中所有的键,使用 values() 获取字典中所有的值,使用 item() 获取字典中所有的键值对,等等。

  1. 面向对象相关术语

在系统学习面向对象编程之前,初学者要了解有关面向对象的一些术语。当和其他人讨论代码的时候,或者尝试查找我们遇到的问题的解决方案时,知道正确的术语会很有帮助。

面向对象中,常用术语包括:

类:可以理解是一个模板,通过它可以创建出无数个具体实例。比如,前面编写的 tortoise 表示的只是乌龟这个物种,通过它可以创建出无数个实例来代表各种不同特征的乌龟(这一过程又称为类的实例化)。

对象:类并不能直接使用,通过类创建出的实例(又称对象)才能使用。这有点像汽车图纸和汽车的关系,图纸本身(类)并不能为人们使用,通过图纸创建出的一辆辆车(对象)才能使用。

属性:类中的所有变量称为属性。例如,tortoise 这个类中,bodyColor、footNum、weight、hasShell 都是这个类拥有的属性。

方法:类中的所有函数通常称为方法。不过,和函数所有不同的是,类方法至少要包含一个 self 参数(后续会做详细介绍)。例如,tortoise 类中,crawl()、eat()、sleep()、protect() 是这个类所拥有的方法,类方法无法单独使用,只能和类的对象一起使用。

  1. 面向对象技术简介:

类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。

数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。

方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

局部变量:定义在方法中的变量,只作用于当前实例的类。

实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。

继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自 Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。

实例化:创建一个类的实例,类的具体对象。

方法:类中定义的函数。

对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

3. 定义类(创建类)

Python 程序中类的使用顺序是这样的:

  1. 创建(定义)类,也就是制作图纸的过程;
  2. 创建类的实例对象(根据图纸造出实际的物品),通过实例对象实现特定的功能。

使用 class 语句来创建一个新类,class 之后为类的名称并以冒号结尾:

class ClassName:
   '类的帮助信息'   #类文档字符串
   class_suite  #类体

类的帮助信息可以通过ClassName.__doc__查看。
class_suite 由类成员,方法,数据属性组成。

和变量名一样,类名本质上就是一个标识符,因此我们在给类起名字时,必须让其符合 Python 的语法。
用 a、b、c 作为类的类名可以吗?从 Python 语法上讲,是完全没有问题的,但作为一名合格的程序员,我们必须还要考虑程序的可读性。

因此,在给类起名字时,最好使用能代表该类功能的单词,例如用“Student”作为学生类的类名;甚至如果必要,可以使用多个单词组合而成,例如初学者定义的第一个类的类名可以是“TheFirstDemo”。
注意,如果由单词构成类名,建议每个单词的首字母大写,其它字母小写。

给类起好名字之后,其后要跟有冒号(:),表示告诉 Python 解释器,下面要开始设计类的内部功能了,也就是编写类属性和类方法。

其实,类属性指的就是包含在类中的变量;而类方法指的是包含类中的函数。换句话说,类属性和类方法其实分别是包含类中的变量和函数的别称。需要注意的一点是,同属一个类的所有类属性和类方法,要保持统一的缩进格式,通常统一缩进 4 个空格。

Python 类是由类头(class 类名)和类体(统一缩进的变量和函数)构成。
实例
以下是一个简单的 Python 类的例子:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Employee:
   '所有员工的基类'
   empCount = 0
 
   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
       print "Total Employee %d" % Employee.empCount
 
   def displayEmployee(self):
       print "Name : ", self.name,  ", Salary: ", self.salary

1)empCount 变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部类使用 Employee.empCount 访问。
2)第一种方法__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。
3)self 代表类的实例,self 在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。

1. self用法详解

self代表类的实例,而非类。
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。

class Test:
    def prt(self):
        print(self)
        print(self.__class__)
 
t = Test()
t.prt()

以上实例执行结果为:

<__main__.Test instance at 0x10d066878>
__main__.Test

从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,
而 self.__class__ 则指向类。

self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的:

实例

class Test:
    def prt(runoob):
        print(runoob)
        print(runoob.__class__)
 
t = Test()
t.prt()

以上实例执行结果为:

<__main__.Test instance at 0x10d066878>
__main__.Test

在定义类的过程中,无论是显式创建类的构造方法,还是向类中添加实例方法,都要求将 self 参数作为方法的第一个参数。例如,定义一个 Person 类:

class Person:
    def __init__(self):
        print("正在执行构造方法")
    # 定义一个study()实例方法
    def study(self,name):
        print(name,"正在学Python")

那么,self 到底扮演着什么样的角色呢?
事实上,Python 只是规定,无论是构造方法还是实例方法,最少要包含一个参数,并没有规定该参数的具体名称。之所以将其命名为 self,只是程序员之间约定俗成的一种习惯,遵守这个约定,可以使我们编写的代码具有更好的可读性(大家一看到 self,就知道它的作用)。

那么,self 参数的具体作用是什么呢?打个比方,如果把类比作造房子的图纸,那么类实例化后的对象是真正可以住的房子。根据一张图纸(类),我们可以设计出成千上万的房子(类对象),每个房子长相都是类似的(都有相同的类变量和类方法),但它们都有各自的主人,那么如何对它们进行区分呢?

当然是通过 self 参数,它就相当于每个房子的门钥匙,可以保证每个房子的主人仅能进入自己的房子(每个类对象只能调用自己的类变量和类方法)。
如果你接触过其他面向对象的编程语言(例如 C++),其实 Python 类方法中的 self 参数就相当于 C++ 中的 this 指针。

也就是说,同一个类可以产生多个对象,当某个对象调用类方法时,该对象会把自身的引用作为第一个参数自动传给该方法,换句话说,Python 会自动绑定类方法的第一个参数指向调用该方法的对象。如此,Python解释器就能知道到底要操作哪个对象的方法了。

因此,程序在调用实例方法和构造方法时,不需要手动为第一个参数传值。例如,更改前面的 Person 类,如下所示:

class Person:
    def __init__(self):
        print("正在执行构造方法")
    # 定义一个study()实例方法
    def study(self):
        print(self,"正在学Python")
zhangsan = Person()
zhangsan.study()
lisi = Person()
lisi.study()

上面代码中,study() 中的 self 代表该方法的调用者,即谁调用该方法,那么 self 就代表谁。因此,该程序的运行结果为:

正在执行构造方法
<__main__.Person object at 0x0000021ADD7D21D0> 正在学Python
正在执行构造方法
<__main__.Person object at 0x0000021ADD7D2E48> 正在学Python

另外,对于构造函数中的 self 参数,其代表的是当前正在初始化的类对象。举个例子:

class Person:
    name = "xxx"
    def __init__(self,name):
        self.name=name
zhangsan = Person("zhangsan")
print(zhangsan.name)
lisi = Person("lisi")
print(lisi.name)

运行结果为:

zhangsan
lisi

可以看到,zhangsan 在进行初始化时,调用的构造函数中 self 代表的是 zhangsan;而 lisi 在进行初始化时,调用的构造函数中 self 代表的是 lisi。

值得一提的是,除了类对象可以直接调用类方法,还有一种函数调用的方式,例如:

class Person:
    def who(self):
        print(self)
zhangsan = Person()
#第一种方式
zhangsan.who()
#第二种方式
who = zhangsan.who
who()#通过 who 变量调用zhangsan对象中的 who() 方法

运行结果为:

<__main__.Person object at 0x0000025C26F021D0>
<__main__.Person object at 0x0000025C26F021D0>

显然,无论采用哪种方法,self 所表示的都是实际调用该方法的对象。
总之,无论是类中的构造函数还是普通的类方法,实际调用它们的谁,则第一个参数 self 就代表谁。

2.__init__()类构造方法

在创建类时,我们可以手动添加一个 __init__() 方法,该方法是一个特殊的类实例方法,称为构造方法(或构造函数)。

构造方法用于创建对象时使用,每当创建一个类的实例对象时,Python 解释器都会自动调用它。
Python 类中,手动添加构造方法的语法格式如下:

def __init__(self,...):
    代码块
class 类:
    def __init__(self, 参数):
        self.属性 = 参数     ...

对象 = 类(参数)

注意,此方法的方法名中,开头和结尾各有 2 个下划线,且中间不能有空格。Python 中很多这种以双下划线开头、双下划线结尾的方法,都具有特殊的意义,另外,__init__() 方法可以包含多个参数,但必须包含一个名为 self 的参数,且必须作为第一个参数。
也就是说,类的构造方法最少也要有一个 self 参数。

注意,即便不手动为类添加任何构造方法,Python 也会自动为类添加一个仅包含 self 参数的构造方法。
仅包含 self 参数的 __init__() 构造方法,又称为类的默认构造方法。
__init__() 构造方法中,除了 self 参数外,还可以自定义一些参数,参数之间使用逗号“,”进行分割。

用例:

class testClass(object):
    def __init__(self, name, gender):  # 构造函数或类的初始化,创建实例时会被调用
        self.Name = name
        self.Gender = gender
        print('hello')



testman = testClass('neo', 'male')
print(testman.Name)
print(testman.Gender)


class tetsClass2(object):
    def __init__(me, name, gender):  # 构造函数或类的初始化,创建实例时会被调用
        me.name = name
        me.gender = gender
        print("hello2")



testman2 = tetsClass2('neo', 'male')
print(testman2.name)
print(testman2.gender)

输出:

hello
neo
male
hello2
neo
male

用例解析:
class testClass:
def__init__(self, name, gender): //定义__init__方法,这里有三个参数, 这个self指的是一会创建类的实例的时候这个被创建的实例本身(例中的testman),你也可以写成其他的东西,比如写成me也是可以的,这样的话下面的self.Name就要写成me.Name。self.Name=name //通常会写成self.name =
name,这里为了区分前后两个是不同的东东,把前面那个大写了,等号左边的那个Name(或name)是实例的属性,后面那个是方法__init__的参数,两个是不同的)self.Gender=gender //通常会写成self.gender=gender
print(‘hello’) //这个print(‘hello’)是为了说明在创建类的实例的时候,__init__方法就立马被调用了。
testman = testClass('neo,‘male’) //这里创建了类testClass的一个实例 testman,
类中有__init__这个方法,在创建类的实例的时候,就必须要有和方法__init__匹配的参数了,由于self指的就是创建的实例本身,self是不用传入的,所以这里传入两个参数。这条语句一出来,实例testman的两个属性Name,Gender就被赋值初使化了,其中Name是 neo,Gender 是male。

4. 类对象的创建和使用

创建类对象的过程,又称为类的实例化。

1. 创建实例对象

对已定义好的类进行实例化,其语法格式如下:

对象 = 类名(参数)

实例化类其他编程语言中一般用关键字 new,但是在 Python 中并没有这个关键字,类的实例化类似函数调用方式。

以下使用类的名称 Employee 来实例化,并通过 init 方法接收参数。

"创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
"创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)

定义类时,如果没有手动添加 __init__() 构造方法,又或者添加的 __init__() 中仅有一个 self 参数,则创建类对象时的参数可以省略不写。

2. 访问属性

您可以使用点号 . 来访问对象的属性。使用如下类的名称访问类变量:

emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount

完整实例:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Employee:
   '所有员工的基类'
   empCount = 0
 
   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount
 
   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary
 
"创建 Employee 类的第一个对象"
emp1 = Employee("Zara", 2000)
"创建 Employee 类的第二个对象"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount

执行以上代码输出结果如下:

Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

你可以添加,删除,修改类的属性,如下所示:

emp1.age = 7  # 添加一个 'age' 属性
emp1.age = 8  # 修改 'age' 属性
del emp1.age  # 删除 'age' 属性

你也可以使用以下函数的方式来访问属性:

getattr(obj, name[, default]) : 访问对象的属性。

hasattr(obj,name) :             检查是否存在一个属性。

setattr(obj,name,value) :       设置一个属性。如果属性不存在,会创建一个新属性。

delattr(obj, name) :            删除属性。

hasattr(emp1, 'age')            如果存在 'age' 属性返回 True。

getattr(emp1, 'age')            返回 'age' 属性的值

setattr(emp1, 'age', 8)         添加属性 'age' 值为 8

delattr(emp1, 'age')            删除属性 'age'

3. 类变量和实例变量(类属性和实例属性)

无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。我们可以将类看做一个独立的空间,则类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。

在类体中,根据变量定义的位置不同,以及定义的方式不同,类属性又可细分为以下 3 种类型:

  1. 类体中、所有函数之外:此范围定义的变量,称为类属性或类变量;
  2. 类体中,所有函数内部:以“self.变量名”的方式定义的变量,称为实例属性或实例变量;
  3. 类体中,所有函数内部:以“变量名=变量值”的方式定义的变量,称为局部变量。

【1】 类变量(类属性)
类变量指的是在类中,但在各个类方法外定义的变量。举个例子:

class CLanguage :
    # 下面定义了2个类变量
    name = "Pthon"
    add = "基础入门"
    # 下面定义了一个say实例方法
    def say(self, content):
        print(content)

上面程序中,name 和 add 就属于类变量。

类变量的特点是,所有类的实例化对象都同时共享类变量,也就是说,类变量在所有实例化对象中是作为公用资源存在的。类方法的调用方式有 2 种,既可以使用类名直接调用,也可以使用类的实例化对象调用。

比如,在 CLanguage 类的外部,添加如下代码:

#使用类名直接调用
print(CLanguage.name)
print(CLanguage.add)
#修改类变量的值
CLanguage.name = "Python教程"
CLanguage.add = "基础入门和进阶"
print(CLanguage.name)
print(CLanguage.add)

程序运行结果为:

Pthon
基础入门
Python教程
基础入门和进阶

可以看到,通过类名不仅可以调用类变量,也可以修改它的值。

当然,也可以使用类对象来调用所属类中的类变量(此方式不推荐使用)。
例如,在 CLanguage 类的外部,添加如下代码:

clang = CLanguage()
print(clang.name)
print(clang.add)

运行程序,结果为:

Pthon
基础入门

注意,因为类变量为所有实例化对象共有,通过类名修改类变量的值,会影响所有的实例化对象。
例如,在 CLanguage 类体外部,添加如下代码:

print("修改前,各类对象中类变量的值:")
clang1 = CLanguage()
print(clang1.name)
print(clang1.add)
clang2 = CLanguage()
print(clang2.name)
print(clang2.add)
print("修改后,各类对象中类变量的值:")
CLanguage.name = "Python教程"
CLanguage.add = "基础入门和进阶"
print(clang1.name)
print(clang1.add)
print(clang2.name)
print(clang2.add)

程序运行结果为:
修改前,各类对象中类变量的值:

Pthon
基础入门
Pthon
基础入门

修改后,各类对象中类变量的值:

Python教程
基础入门和进阶
Python教程
基础入门和进阶

显然,通过类名修改类变量,会作用到所有的实例化对象(例如这里的 clang1 和 clang2)。
注意,通过类对象是无法修改类变量的。通过类对象对类变量赋值,其本质将不再是修改类变量的值,而是在给该对象定义新的实例变量(在讲实例变量时会进行详细介绍)。

值得一提的是,除了可以通过类名访问类变量之外,还可以动态地为类和对象添加类变量。
例如,在 CLanguage 类的基础上,添加以下代码:

clang = CLanguage()
CLanguage.catalog = 13
print(clang.catalog)

运行结果为:

13

【2】Python内置类属性:
__dict__ : 类的属性(包含一个字典,由类的数据属性组成)
__doc__: 类的文档字符串
__name__: 类名
__module__: 类定义所在的模块(类的全名是’__main__.className’,如果类位于一个导入
模块mymod中,那么className.__module__等于 mymod)
__bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)
__del__: 对象销毁(垃圾回收)

Python内置类属性调用实例如下:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Employee:
   '所有员工的基类'
   empCount = 0
 
   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount
 
   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary
 
print "Employee.__doc__:", Employee.__doc__
print "Employee.__name__:", Employee.__name__
print "Employee.__module__:", Employee.__module__
print "Employee.__bases__:", Employee.__bases__
print "Employee.__dict__:", Employee.__dict__

执行以上代码输出结果如下:

Employee.__doc__: 所有员工的基类
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: ()
Employee.__dict__: {'__module__': '__main__', 'displayCount': 
<function displayCount at 0x10a939c80>, 'empCount': 0, 
   'displayEmployee': <function displayEmployee at 0x10a93caa0>, 
      '__doc__': '\xe6\x89\x80\xe6\x9c\x89\xe5\x91\x98\xe5\xb7\
      xa5\xe7\x9a\x84\xe5\x9f\xba\xe7\xb1\xbb', '__init__': 
      <function __init__ at 0x10a939578>}

【3】 python对象销毁(垃圾回收)
Python 使用了引用计数这一简单技术来跟踪和回收垃圾。

在 Python 内部记录着所有使用中的对象各有多少引用。
一个内部跟踪变量,称为一个引用计数器。

当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说,这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。

a = 40      # 创建对象  <40>
b = a       # 增加引用, <40> 的计数
c = [b]     # 增加引用.  <40> 的计数

del a       # 减少引用 <40> 的计数
b = 100     # 减少引用 <40> 的计数
c[0] = -1   # 减少引用 <40> 的计数

垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(即未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。

实例
析构函数 __del____del__在对象销毁的时候被调用,当对象不再被使用时,
__del__方法运行:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print class_name, "销毁"
 
pt1 = Point()
pt2 = pt1
pt3 = pt1
print id(pt1), id(pt2), id(pt3) # 打印对象的id
del pt1
del pt2
del pt3

以上实例运行结果如下:

3083401324 3083401324 3083401324
Point 销毁

注意:通常你需要在单独的文件中定义一个类,

【4】 实例变量(实例属性)

实例变量指的是在任意类方法内部,以“self.变量名”的方式定义的变量,其特点是只作用于调用方法的对象。另外,实例变量只能通过对象名访问,无法通过类名访问。

举个例子:

class CLanguage:
    def __init__(self):
        self.name = "Python"
        self.add = '基础入门'

    # 下面定义了一个say实例方法
    def say(self):
        self.catalog = 13

此 CLanguage 类中,name、add 以及 catalog 都是实例变量。其中,由于 __init__()
函数在创建类对象时会自动调用,而 say() 方法需要类对象手动调用。因此,CLanguage 类的类对象都会包含 name 和 add 实例变量,而只有调用了 say() 方法的类对象,才包含 catalog 实例变量。

例如,在上面代码的基础上,添加如下语句:

clang = CLanguage()
print(clang.name)
print(clang.add)
#由于 clang 对象未调用 say() 方法,因此其没有 catalog 变量,下面这行代码会报错
#print(clang.catalog)
clang2 = CLanguage()
print(clang2.name)
print(clang2.add)
#只有调用 say(),才会拥有 catalog 实例变量
clang2.say()
print(clang2.catalog)

运行结果为:

Python
基础入门
Python
基础入门
13

前面讲过,通过类对象可以访问类变量,但无法修改类变量的值。这是因为,通过类对象修改类变量的值,不是在给“类变量赋值”,而是定义新的实例变量。例如,在 CLanguage 类体外,添加如下程序:

clang = CLanguage()
#clang访问类变量
print(clang.name)
print(clang.add)
clang.name = "Python教程"
clang.add = "基础入门和进阶"
#clang实例变量的值
print(clang.name)
print(clang.add)
#类变量的值
print(CLanguage.name)
print(CLanguage.add)

程序运行结果为:

Python
基础入门
Python教程
基础入门和进阶
    print(CLanguage.name)
AttributeError: type object 'CLanguage' has no attribute 'name'

其中,最后一行报错信息提示我们,调用 CLanguage 类时缺少name参数值。
这意味着,Python 并不会自动给 name 参数传值且无值可传。而且要求重新定义CLanguage.name和CLanguage.add。
self 参数需要的是方法的实际调用者(是类对象),而这里只提供了类名,当然无法自动传值。

显然,通过类对象是无法修改类变量的值的,本质其实是给 clang 对象新添加 name 和 add 这 2 个实例变量。
类中,实例变量和类变量可以同名,但这种情况下使用类对象将无法调用类变量,它会首选实例变量,这也是不推荐“类变量使用对象名调用”的原因。

另外,和类变量不同,通过某个对象修改实例变量的值,不会影响类的其它实例化对象,更不会影响同名的类变量。例如:

class CLanguage :
    name = "xxx"  #类变量
    add = "http://"  #类变量
    def __init__(self):
        self.name = "Python"   #实例变量
        self.add = "基础入门"   #实例变量
    # 下面定义了一个say实例方法
    def say(self):
        self.catalog = 13  #实例变量
clang = CLanguage()
#修改 clang 对象的实例变量
clang.name = "python教程"
clang.add = "基础入门和进阶"
print(clang.name)
print(clang.add)
clang2 = CLanguage()
print(clang2.name)
print(clang2.add)
#输出类变量的值
print(CLanguage.name)
print(CLanguage.add)

程序运行结果为:

python教程
基础入门和进阶
Python
基础入门
xxx
http://

不仅如此,Python 只支持为特定的对象添加实例变量。例如,在之前代码的基础上,为 clang 对象添加 money 实例变量,实现代码为:

clang.money = 30
print(clang.money)

【5】 局部变量
除了实例变量,类方法中还可以定义局部变量。和前者不同,局部变量直接以
“变量名=值”的方式进行定义,例如:

class CLanguage :
    # 下面定义了一个say实例方法
    def count(self,money):
        sale = 0.8*money
        print("优惠后的价格为:",sale)
clang = CLanguage()
clang.count(100)

通常情况下,定义局部变量是为了所在类方法功能的实现。需要注意的一点是,局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁。

5. 类方法详解(包含区别和用法)

和类属性一样,类方法也可以进行更细致的划分,具体可分为类方法、实例方法
和静态方法。
和类属性的分类不同,对于初学者来说,区分这 3 种类方法是非常简单的,即采用 @classmethod 修饰的方法为类方法;采用 @staticmethod 修饰的方法为静态方法;不用任何修改的方法为实例方法。
其中 @classmethod 和 @staticmethod 都是函数装饰器。

【1】类实例方法

通常情况下,在类中定义的方法默认都是实例方法。前面章节中,我们已经定义了不只一个实例方法。不仅如此,类的构造方法理论上也属于实例方法,只不过它比较特殊。

比如,下面的类中就用到了实例方法:

class CLanguage:
    #类构造方法,也属于实例方法
    def __init__(self):
        self.name = "python"
        self.add = "基础入门"
    # 下面定义了一个say实例方法
    def say(self):
        print("正在调用 say() 实例方法")

实例方法最大的特点就是,它最少也要包含一个 self 参数,用于绑定调用此
方法的实例对象(Python 会自动完成绑定)。实例方法通常会用类对象直接调
用,例如:

clang = CLanguage()
clang.say()

运行结果:

正在调用 say() 实例方法

当然,Python 也支持使用类名调用实例方法,但此方式需要手动给 self
参数传值。例如:

#类名调用实例方法,需手动给 self 参数传值
clang = CLanguage()
CLanguage.say(clang)

运行结果为:

正在调用 say() 实例方法

【2】 类方法

Python 类方法和实例方法相似,它最少也要包含一个参数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身绑定给 cls 参数(注意,绑定的不是
类对象)。也就是说,我们在调用类方法时,无需显式为 cls 参数传参。
和 self 一样,cls 参数的命名也不是规定的(可以随意命名),只是 Python
程序员约定俗称的习惯而已。

和实例方法最大的不同在于,类方法需要使用@classmethod修饰符进行修饰,
例如:

class CLanguage:
    #类构造方法,也属于实例方法
    def __init__(self):
        self.name = "python"
        self.add = "基础入门"
    #下面定义了一个类方法
    @classmethod
    def info(cls):
        print("正在调用类方法",cls)

注意,如果没有 @classmethod,则 Python 解释器会将 fly() 方法认定为
实例方法,而不是类方法。

类方法推荐使用类名直接调用,当然也可以使用实例对象来调用(不推荐)。
例如,在上面 CLanguage 类的基础上,在该类外部添加如下代码:

#使用类名直接调用类方法
CLanguage.info()
#使用类对象调用类方法
clang = CLanguage()
clang.info()

运行结果为:

正在调用类方法 <class '__main__.CLanguage'>
正在调用类方法 <class '__main__.CLanguage'>

【3】类静态方法

静态方法,其实就是我们学过的函数,和函数唯一的区别是,静态方法定义在
类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空
间)中。

静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对
它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调
用任何类属性和类方法。

静态方法需要使用@staticmethod修饰,例如:

class CLanguage:
    @staticmethod
    def info(name,add):
        print(name,add)

静态方法的调用,既可以使用类名,也可以使用类对象,例如:

#使用类名直接调用静态方法
CLanguage.info("Python","基础入门")
#使用类对象调用静态方法
clang = CLanguage()
clang.info("Python教程","基础入门和进阶")

运行结果为:

Python 基础入门
Python教程 基础入门和进阶

在实际编程中,几乎不会用到类方法和静态方法,因为我们完全可以使用函数代替它们实现想要的功能,但在一些特殊的场景中(例如工厂模式中),使用类方法和静态方法也是很不错的选择。

6. 类调用实例方法

实例方法的调用方式其实有 2 种,既可以采用类对象调用,也可以直接通过类名调用。
通常情况下,我们习惯使用类对象调用类中的实例方法。但如果想用类调用实例方法,不能像如下这样:

class CLanguage:
    def info(self):
        print("我正在学 Python")

#通过类名直接调用实例方法
CLanguage.info()

运行上面代码,程序会报出如下错误:

Traceback (most recent call last):
  File "H:\临时文件夹\test.py", line 5, in <module>
    CLanguage.info()
TypeError: CLanguage.info() missing 1 required positional argument: 'self'

其中,最后一行报错信息提示我们,调用 info() 类方式时缺少给 self参数传参。
这意味着,和使用类对象调用实例方法不同,通过类名直接调用实例方法时,
Python 并不会自动给 self 参数传值。
self 参数需要的是方法的实际调用者(是类对象),而这里只提供了类名,
当然无法自动传值。

因此,如果想通过类名直接调用实例方法,就必须手动为 self 参数传值。例如修改上面的代码为:

class CLanguage:
    def info(self):
        print("我正在学 Python")

clang = CLanguage()
#通过类名直接调用实例方法
CLanguage.info(clang)

再次运行程序,结果为:

我正在学 Python

可以看到,通过手动将 clang 这个类对象传给了 self 参数,使得程序得以正确执行。
实际上,这里调用实例方法的形式完全是等价于 clang.info()。

值得一提的是,上面的报错信息只是让我们手动为 self 参数传值,但并没有规定必须传一个该类的对象,其实完全可以任意传入一个参数,例如:

class CLanguage:
    def info(self):
        print(self,"正在学 Python")

#通过类名直接调用实例方法
CLanguage.info("zhangsan")

运行结果为:

zhangsan 正在学 Python

可以看到,“zhangsan” 这个字符串传给了 info() 方法的 self 参数。显然,无论是 info() 方法中使用 self 参数调用其它类方法,还是使用 self 参数定义新的实例变 量,胡乱的给 self 参数传参都将会导致程序运行崩溃。

总的来说,Python 中允许使用类名直接调用实例方法,但必须手动为该方法的第一个 self 参数传递参数,这种调用方法的方式被称为“非绑定方法”。
用类的实例对象访问类成员的方式称为绑定方法,而用类名调用类成员的方式称为非绑定方法。

7. 描述符详解

Python 中,通过使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。
本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类。
描述符是 Python 中复杂属性访问的基础,它在内部被用于实现 property、方法、类方法、静态方法和 super 类型。

描述符类基于以下 3 个特殊方法,换句话说,这 3 个方法组成了描述符协议:

__set__(self, obj, type=None):在设置属性时将调用这一方法(可用 setter 表示);
__get__(self, obj, value):在读取属性时将调用这一方法(可用 getter 表示);
__delete__(self, obj):对属性调用 del 时将调用这一方法。

其中,实现了 setter 和 getter 方法的描述符类被称为数据描述符;反之,如果只实现了 getter 方法,则称为非数据描述符。

实际上,在每次查找属性时,描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用(注意不要和 __getattr__() 弄混)。也就是说,每次使用类对象.属性(或者 getattr(类对象,属性值))的调用方式时,都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:

1) 验证该属性是否为类实例对象的数据描述符;
2) 如果不是,就查看该属性是否能在类实例对象的 __dict__ 中找到;
3) 最后,查看该属性是否为类实例对象的非数据描述符。

为了表达清楚,这里举个例子:

#描述符类
class revealAccess:
    def __init__(self, initval = None, name = 'var'):
        self.val = initval
        self.name = name
    def __get__(self, obj, objtype):
        print("Retrieving",self.name)
        return self.val
    def __set__(self, obj, val):
        print("updating",self.name)
        self.val = val
class myClass:
    x = revealAccess(10,'var "x"')
    y = 5
m = myClass()
print(m.x)
m.x = 20
print(m.x)
print(m.y)

运行结果为:

Retrieving var "x"
10
updating var "x"
Retrieving var "x"
20
5

从这个例子可以看到,如果一个类的某个属性有数据描述符,那么每次查找这个属性时,都会调用描述符的 __get__() 方法,并返回它的值;同样,每次在对该属性赋值时,也会调用__set__() 方法。

注意,虽然上面例子中没有使用__del__() 方法,但也很容易理解,当每次使用 del 类对象.属性(或者 delattr(类对象,属性))语句时,都会调用该方法。

除了使用描述符类自定义类属性被调用时做的操作外,还可以使用 property() 函数或者 @property 装饰器。这里不作介绍。

8. 封装机制及实现方法

大多数面向对象编程语言(诸如 C++、Java 等)都具备 3 个典型特征,即封装、继承和多态。

简单的理解封装(Encapsulation),即在设计类时,刻意地将一些属性和方法隐藏在类的内部,这样在使用此类时,将无法直接以“类对象.属性名”(或者“类对象.方法名(参数)”)的形式调用这些属性(或方法),而只能用未隐藏的类方法间接操作这些隐藏的属性和方法。

就好比使用电脑,我们只需要学会如何使用键盘和鼠标就可以了,不用关心内部是怎么实现的,因为那是生产和设计人员该操心的。
注意,封装绝不是将类中所有的方法都隐藏起来,一定要留一些像键盘、鼠标这样可供外界使用的类方法。

那么,类为什么要进行封装,这样做有什么好处呢?

首先,封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。

除此之外,对一个类实现良好的封装,用户只能借助暴露出来的类方法来访问数据,我们只需要在这些暴露的方法中加入适当的控制逻辑,即可轻松实现用户对类中属性或方法的不合理操作。

并且,对类进行良好的封装,还可以提高代码的复用性。

Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private)。
【1】类的私有属性
__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。
在类内部的方法中使用时 self.__private_attrs。

【2】类的方法
在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数。

【3】类的私有方法
__private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用 self.__private_methods

实例python2.0

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class JustCounter:
    __secretCount = 0  # 私有变量
    publicCount = 0    # 公开变量
 
    def count(self):
        self.__secretCount += 1
        self.publicCount += 1
        print self.__secretCount
 
counter = JustCounter()
counter.count()
counter.count()
print counter.publicCount
print counter.__secretCount  # 报错,实例不能访问私有变量

Python 通过改变名称来包含类名:

1
2
2
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    print counter.__secretCount  # 报错,实例不能访问私有变量
AttributeError: JustCounter instance has no attribute '__secretCount'

Python不允许实例化的类访问私有数据,但你可以使用 object._className__attrName( 对象名._类名__私有属性名 )访问属性,参考以下实例python2.0:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

class Runoob:
    __site = "www.runoob.com"

runoob = Runoob()
print runoob._Runoob__site

执行以上代码,执行结果如下:

www.runoob.com

单下划线、双下划线、头尾双下划线说明:
__foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 __init__() 之类的。

_foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *

__foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

Python 类中2 种属性的区别如下:

public:公有属性的类变量和类函数,在类的外部、类内部以及子类中,都可以正常访问;
private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。

但是,Python 并没有提供 public、private 这些修饰符。为了实现类的封装,Python 采取了下面的方法:
默认情况下,Python 类中的变量和方法都是公有(public)的,它们的名称前都没有下划线;
如果类中的变量和函数,其名称以双下划线开头,则该变量(函数)为私有变量(私有函数),其属性等同于 private。

除此之外,还可以定义以单下划线开头的类属性或者类方法(例如_name_display(self)),这种类属性和类方法通常被视为私有属性和私有方法,虽然它们也能通过类对象正常访问,但这是一种约定俗称的用法,初学者一定要遵守。
注意,Python 类中还有以双下划线开头和结尾的类方法(例如类的构造函数__init__(self)),这些都是 Python 内部定义的,用于 Python 内部调用。我们自己定义类属性或者类方法时,不要使用这种格式。

例如,如下程序示范了 Python 的封装机制:

class CLanguage :
    def setname(self, name):
        if len(name) < 3:
            raise ValueError('名称长度必须大于3!')
        self.__name = name
    def getname(self):
        return self.__name
    #为 name 配置 setter 和 getter 方法
    name = property(getname, setname)
    def setadd(self, add):
        if add.startswith("https://"):
            self.__add = add
        else:
            raise ValueError('地址必须以 https:// 开头') 
    def getadd(self):
        return self.__add
   
    #为 add 配置 setter 和 getter 方法
    add = property(getadd, setadd)
    #定义个私有方法
    def __display(self):
        print(self.__name,self.__add)
clang = CLanguage()
clang.name = "python教程"
clang.add = "https://docs.python.org/zh-cn/3/tutorial/index.html"
print(clang.name)
print(clang.add)

程序运行结果为:

python教程
https://docs.python.org/zh-cn/3/tutorial/index.html

上面程序中,CLanguage 将 name 和 add 属性都隐藏了起来,但同时也提供了可操作它们的“窗口”,也就是各自的 setter 和 getter 方法,这些方法都是公有(public)的。

不仅如此,以 add 属性的 setadd() 方法为例,通过在该方法内部添加控制逻辑,即通过调用 startswith() 方法,控制用户输入的地址必须以“http://”开头,否则程序将会执行 raise 语句抛出 ValueError 异常。
有关 raise 的具体用法,后续章节会做详细的讲解,这里可简单理解成,如果用户输入不规范,程序将会报错。

通过此程序的运行逻辑不难看出,通过对 CLanguage 类进行良好的封装,使得用户仅能通过暴露的 setter() 和 getter() 方法操作 name 和 add 属性,而通过对 setname() 和 setadd() 方法进行适当的设计,可以避免用户对类中属性的不合理操作,从而提高了类的可维护性和安全性。

细心的读者可能还发现,CLanguage 类中还有一个 __display() 方法,由于该类方法为私有(private)方法,且该类没有提供操作该私有方法的“窗口”,因此我们无法在类的外部使用它。
换句话说,如下调用 __display() 方法是不可行的:

#尝试调用私有的 display() 方法
clang.__display()

这会导致如下错误:

Traceback (most recent call last):
  File "H:\临时文件夹\test.py", line 33, in <module>
    clang.__display()
AttributeError: 'CLanguage' object has no attribute '__display'

那么,类似 __display() 这样的类方法,就没有办法调用了吗?并非如此,了解Python封装实现原理之后,就可以轻松搞定它。

【4】封装
隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处
1) 将变化隔离;
2) 便于使用;
3)提高复用性;
4)提高安全性;

封装原则
1) 将不需要对外提供的内容都隐藏起来;
2)把属性都隐藏,提供公共方法对其访问。

私有变量和私有方法

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)。

私有变量

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __N=0 
    #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性
    #设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print('from A')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,
#仅仅只是一种语法意义上的变形

这种自动变形的特点:

1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。
2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。
3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

这种变形需要注意的问题是:
1)这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2)变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形

私有方法

#正常情况
class A:
   def fa(self):
     print('from A')
   def test(self):
     self.fa()

class B(A):
   def fa(self):
     print('from B')

b=B()
b.test()

输出:

from B
#把fa定义成私有的,即__fa
class A:
   def __fa(self): #在定义时就变形为_A__fa
     print('from A')
   def test(self):
     self.__fa() #只会与自己所在的类为准,即调用_A__fa

class B(A):
   def __fa(self):
     print('from B')

b=B()
b.test()

输出:

from A

【5】封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        print(self.__width * self.__length)
#使用者
r1=Room('卧室','egon',20,20,20)
r1.tell_area() #使用者调用接口tell_area
r1.tell_volume() #没有这项功能会报错

输出:

400
程序报错
#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self):
         print(self.__width * self.__length)
    #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,
    #只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然
    #使用该方法,但是功能已经变了,扩展了
    def tell_volume(self):
        print(self.__width * self.__length * self.__high)
#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码
r1.tell_area()
r1.tell_volume()   #可以用上新功能

输出:

400
8000

9. property()函数:定义属性

前面章节中,我们一直在用“类对象.属性”的方式访问类中定义的属性,其实这种做法是欠妥的,因为它破坏了类的封装原则。正常情况下,类包含的属性应该是隐藏的,只允许通过类提供的方法来间接实现对类属性的访问和操作。

property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值

@property

@obj.setter

@obj.deleter

为什么要用property

将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

@property装饰器用例:

# class Peopel:
#     def __init__(self,name,age,sex,weight,height,permission=False):
#         self.__name=name
#         self.__age=age
#         self.__sex=sex
#         self.__height=height
#         self.__weight=weight
#         self.permission=permission
#     @property
#     def info(self):
#         print("""
#         -----%s info-----
#         name:%s
#         age:%s
#         sex:%s
#         weight:%s
#         height:%s
#         """%(self.__name,self.__name,self.__age,self.__sex,self.__height,self.__weight))
#     @property
#     def bmi(self):
#         res=self.__weight / (self.__height ** 2)
#         return res
#     @property
#     def name(self):
#         return self.__name
#     @name.setter
#     def name(self,val):
#         if not isinstance(val,str):
#             raise TypeError('must be str')
#         self.__name=val
#     @name.deleter
#     def name(self):
#         if not self.permission:
#             raise PermissionError('不让删')
#         del self.__name
# egon=Peopel('egon',18,'male',80,1.75)
# egon.info
# print(egon.bmi)
# print(egon.name)

因此,在不破坏类封装原则的基础上,为了能够有效操作类中的属性,类中应包含读(或写)类属性的多个 getter(或 setter)方法,这样就可以通过“类对象.方法(参数)”的方式操作属性,例如:

class CLanguage:
    #构造函数
    def __init__(self,name):
        self.name = name 
    #设置 name 属性值的函数 
    def setname(self,name):
        self.name = name
    #访问nema属性值的函数
    def getname(self):
        return self.name
    #删除name属性值的函数
    def delname(self):
        self.name="xxx"
clang = CLanguage("Python")
#获取name属性值
print(clang.getname())
#设置name属性值
clang.setname("Python教程")
print(clang.getname())
#删除name属性值
clang.delname()
print(clang.getname())

运行结果为:

Python
Python教程
xxx

可能有读者觉得,这种操作类属性的方式比较麻烦,更习惯使用“类对象.属性”这种方式。

庆幸的是,Python 中提供了 property() 函数,可以实现在不破坏类封装原则的前提下,让开发者依旧使用“类对象.属性”的方式操作类中的属性。

property() 函数的基本使用格式如下:

属性名=property(fget=None, fset=None, fdel=None, doc=None)

其中,fget 参数用于指定获取该属性值的类方法,fset 参数用于指定设置该属性值的方法,fdel 参数用于指定删除该属性值的方法,最后的 doc 是一个文档字符串,用于说明此函数的作用。
注意,在使用 property() 函数时,以上 4 个参数可以仅指定第 1 个、或者前 2 个、或者前 3 个,当前也可以全部指定。也就是说,property() 函数中参数的指定并不是完全随意的。

例如,修改上面的程序,为 name 属性配置 property() 函数:

class CLanguage:
    #构造函数
    def __init__(self,n):
        self.__name = n
    #设置 name 属性值的函数
    def setname(self,n):
        self.__name = n
    #访问nema属性值的函数
    def getname(self):
        return self.__name
    #删除name属性值的函数
    def delname(self):
        self.__name="xxx"
    #为name 属性配置 property() 函数
    name = property(getname, setname, delname, '指明出处')
#调取说明文档的 2 种方式
#print(CLanguage.name.__doc__)
help(CLanguage.name)
clang = CLanguage("Python")
#调用 getname() 方法
print(clang.name)
#调用 setname() 方法
clang.name="Python教程"
print(clang.name)
#调用 delname() 方法
del clang.name
print(clang.name)

运行结果为:

Help on property:

    指明出处

Python
Python教程
xxx

注意,在此程序中,由于 getname() 方法中需要返回 name 属性,如果使用 self.name 的话,其本身又被调用 getname(),这将会先入无限死循环。为了避免这种情况的出现,程序中的 name属性必须设置为私有属性,即使用__name(前面有 2 个下划线)。
有关类属性和类方法的属性设置(分为共有属性、保护属性、私有属性)。
当然,property() 函数也可以少传入几个参数。以上面的程序为例,我们可以修改 property() 函数如下所示:

name = property(getname, setname)

这意味着,name 是一个可读写的属性,但不能删除,因为 property() 函数中并没有为 name 配置用于函数该属性的方法。也就是说,即便 CLanguage 类中设计有 delname() 函数,这种情况下也不能用来删除 name 属性。
同理,还可以像如下这样使用 property() 函数:

name = property(getname)    # name 属性可读,不可写,也不能删除
name = property(getname, setname,delname)    
#name属性可读、可写、也可删除,就是没有说明文档

10. 继承机制及其使用

Python 类的封装、继承、多态 3 大特性,前面章节已经详细介绍了 Python 类的封装,本节继续讲解 Python 类的继承机制。

继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。也就是说,通过使用继承这种机制,可以轻松实现类的重复使用。

面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。

通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。

继承语法

class 派生类名(基类名)
    ...

在python中继承中的一些特点:

1、如果在子类中需要父类的构造方法就需要显式的调用父类的构造方法,或者不重写父类的构造方法。详细说明可查看: python 子类继承父类构造函数说明。
2、在调用基类的方法时,需要加上基类的类名前缀,且需要带上 self 参数变量。区别在于类中调用普通函数时并不需要带上 self 参数
3、Python 总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。
如果在继承元组中列了一个以上的类,那么它就被称作"多重继承" 。

语法:

派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

class SubClassName (ParentClass1[, ParentClass2, ...]):
    ...

实例python2.0

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Parent:        # 定义父类
   parentAttr = 100
   def __init__(self):
      print "调用父类构造函数"
 
   def parentMethod(self):
      print '调用父类方法'
 
   def setAttr(self, attr):
      Parent.parentAttr = attr
 
   def getAttr(self):
      print "父类属性 :", Parent.parentAttr
 
class Child(Parent): # 定义子类
   def __init__(self):
      print "调用子类构造方法"
 
   def childMethod(self):
      print '调用子类方法'
 
c = Child()          # 实例化子类
c.childMethod()      # 调用子类的方法
c.parentMethod()     # 调用父类方法
c.setAttr(200)       # 再次调用父类的方法 - 设置属性值
c.getAttr()          # 再次调用父类的方法 - 获取属性值

以上代码执行结果如下:

调用子类构造方法
调用子类方法
调用父类方法
父类属性 : 200

你可以继承多个类

class A:        # 定义类 A
.....

class B:         # 定义类 B
.....

class C(A, B):   # 继承类 A 和 B
.....

你可以使用issubclass()或者isinstance()方法来检测。

issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,
语法:

issubclass(sub,sup)

isinstance(obj, Class) 布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。

假设现有一个 Shape 类,该类的 draw() 方法可以在屏幕上画出指定的形状,现在需要创建一个 Form 类,要求此类不但可以在屏幕上画出指定的形状,还可以计算出所画形状的面积。要创建这样的类,笨方法是将 draw() 方法直接复制到新类中,并添加计算面积的方法。
实现代码如下所示:

class Shape:
    def draw(self,content):
        print("画",content)
class Form:
    def draw(self,content):
        print("画",content)
    def area(self):
        #....
        print("此图形的面积为...")

当然还有更简单的方法,就是使用类的继承机制。实现方法为:让 From 类继承 Shape 类,这样当 From 类对象调用 draw() 方法时,Python 解释器会先去 From 中找以 draw 为名的方法,如果找不到,它还会自动去 Shape 类中找。如此,我们只需在 From 类中添加计算面积的方法即可,示例代码如下:

class Shape:
    def draw(self,content):
        print("画",content)
class Form(Shape):
    def area(self):
        #....
        print("此图形的面积为...")

上面代码中,class From(Shape) 就表示 From 继承 Shape。

Python 中,实现继承的类称为子类,被继承的类称为父类(也可称为基类、超类)。因此在上面这个样例中,From 是子类,Shape 是父类。

子类继承父类时,只需在定义子类时,将父类(可以是多个)放在子类之后的圆括号里即可。

语法格式如下:

class 类名(父类1, 父类2, ...):
    #类定义部分

注意,如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类,即要么是直接父类,要么是间接父类)。另外,Python 的继承是多继承机制(和 C++ 一样),即一个子类可以同时拥有多个直接父类。

注意,有读者可能还听说过“派生”这个词汇,它和继承是一个意思,只是观察角度不同而已。
换句话话,继承是相对子类来说的,即子类继承自父类;而派生是相对于父类来说的,即父类派生出子类。

了解了继承机制的含义和语法之后,下面代码演示了继承机制的用法:

class People:
    def say(self):
        print("我是一个人,名字是:",self.name)
class Animal:
    def display(self):
        print("人也是高级动物")
#同时继承 People 和 Animal 类
#其同时拥有 name 属性、say() 和 display() 方法
class Person(People, Animal):
    pass
zhangsan = Person()
zhangsan.name = "张三"
zhangsan.say()
zhangsan.display()

运行结果,结果为:

我是一个人,名字是: 张三
人也是高级动物

可以看到,虽然 Person 类为空类,但由于其继承自 People 和 Animal 这 2 个类,因此实际上 Person 并不空,它同时拥有这 2 个类所有的属性和方法。
没错,子类拥有父类所有的属性和方法,即便该属性或方法是私有(private)的。

【1】关于Python的多继承
事实上,大部分面向对象的编程语言,都只支持单继承,即子类有且只能有一个父类。而 Python 却支持多继承(C++也支持多继承)。
和单继承相比,多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。
使用多继承经常需要面临的问题是,多个父类中包含同名的类方法。对于这种情况,Python的处置措施是:根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。

举个例子:

class People:
    def __init__(self):
        self.name = People
    def say(self):
        print("People类",self.name)
class Animal:
    def __init__(self):
        self.name = Animal
    def say(self):
        print("Animal类",self.name)
#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):
    pass
zhangsan = Person()
zhangsan.name = "张三"
zhangsan.say()

程序运行结果为:

People类 张三

可以看到,当 Person 同时继承 People 类和 Animal 类时,People 类在前,因此如果 People 和 Animal 拥有同名的类方法,实际调用的是 People 类中的。
虽然 Python 在语法上支持多继承,但逼不得已,建议大家不要使用多继承。
*建议大家不要使用多继承。
*建议大家不要使用多继承。
*建议大家不要使用多继承。

11.方法重写

在 Python 中,子类继承了父类,那么子类就拥有了父类所有的类属性和类方法。通常情况下,子类会在此基础上,扩展一些新的类属性和类方法。

但凡事都有例外,我们可能会遇到这样一种情况,即子类从父类继承得来的类方法中,大部分是适合子类使用的,但有个别的类方法,并不能直接照搬父类的,如果不对这部分类方法进行修改,子类对象无法使用。针对这种情况,我们就需要在子类中重复父类的方法。

举个例子,鸟通常是有翅膀的,也会飞,因此我们可以像如下这样定义个和鸟相关的类:

class Bird:
    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")
    #鸟会飞
    def fly(self):
        print("鸟会飞")

但是,对于鸵鸟来说,它虽然也属于鸟类,也有翅膀,但是它只会奔跑,并不会飞。针对这种情况,可以这样定义鸵鸟类:

class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟不会飞")

可以看到,因为 Ostrich 继承自 Bird,因此 Ostrich 类拥有 Bird 类的 isWing() 和 fly() 方法。其中,isWing() 方法同样适合 Ostrich,但 fly() 明显不适合,因此我们在 Ostrich 类中对 fly() 方法进行重写。
重写,有时又称覆盖,是一个意思,指的是对类中已有方法的内部实现进行修改。

在上面 2 段代码的基础上,添加如下代码并运行:

class Bird:
    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")
    #鸟会飞
    def fly(self):
        print("鸟会飞")
class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟不会飞")
# 创建Ostrich对象
ostrich = Ostrich()
#调用 Ostrich 类中重写的 fly() 类方法
ostrich.fly()

运行结果为:

鸵鸟不会飞

显然,ostrich 调用的是重写之后的 fly() 类方法。
如何调用被重写的方法?
事实上,如果我们在子类中重写了从父类继承来的类方法,那么当在类的外部通过子类对象调用该方法时,Python 总是会执行子类中重写的方法。

这就产生一个新的问题,即如果想调用父类中被重写的这个方法,该怎么办呢?

很简单,前面讲过,Python 中的类可以看做是一个独立空间,而类方法其实就是出于该空间中的一个函数。而如果想要全局空间中,调用类空间中的函数,只需要在调用该函数是备注类名即可。

举个例子:

class Bird:
    #鸟有翅膀
    def isWing(self):
        print("鸟有翅膀")
    #鸟会飞
    def fly(self):
        print("鸟会飞")
class Ostrich(Bird):
    # 重写Bird类的fly()方法
    def fly(self):
        print("鸵鸟不会飞")
# 创建Ostrich对象
ostrich = Ostrich()
#调用 Bird 类中的 fly() 方法
Bird.fly(ostrich)

程序运行结果为:

鸟会飞

此程序中,需要大家注意的一点是,使用类名调用其类方法,Python 不会为该方法的第一个 self参数自定绑定值,因此采用这种调用方法,需要手动为 self 参数赋值。
通过类名调用实例方法的这种方式,又被称为未绑定方法。

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法:

实例python2.0:

#!/usr/bin/python
# -*- coding: UTF-8 -*-
 
class Parent:        # 定义父类
   def myMethod(self):
      print '调用父类方法'
 
class Child(Parent): # 定义子类
   def myMethod(self):
      print '调用子类方法'
 
c = Child()          # 子类实例
c.myMethod()         # 子类调用重写方法

执行以上代码输出结果如下:

调用子类方法
基础重载方法

下表列出了一些通用的功能,你可以在自己的类重写:

序号 方法, 描述 & 简单的调用
1  __init__ ( self [,args...] )
构造函数
简单的调用方法: obj = className(args)

2  __del__( self )
析构方法, 删除一个对象
简单的调用方法 : del obj

3  __repr__( self )
转化为供解释器读取的形式
简单的调用方法 : repr(obj)

4  __str__( self )
用于将值转化为适于人阅读的形式
简单的调用方法 : str(obj)

5  __cmp__ ( self, x )
对象比较
简单的调用方法 : cmp(obj, x)

运算符重载
Python同样支持运算符重载,实例python2.0如下:

#!/usr/bin/python
 
class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b
 
   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)
 
v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2

以上代码执行结果如下所示:

Vector(7,8)

12. super()函数:调用父类的构造方法

Python 中子类会继承父类所有的类属性和类方法。严格来说,类的构造方法其实就是实例方法,因此毫无疑问,父类的构造方法,子类同样会继承。

但我们知道,Python 是一门支持多继承的面向对象编程语言,如果子类继承的多个父类中包含同名的类实例方法,则子类对象在调用该方法时,会优先选择排在最前面的父类中的实例方法。
显然,构造方法也是如此。

举个例子:

class People:
    def __init__(self,name):
        self.name = name
    def say(self):
        print("我是人,名字为:",self.name)
class Animal:
    def __init__(self,food):
        self.food = food
    def display(self):
        print("我是动物,我吃",self.food)
#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):
    pass
per = Person("zhangsan")
per.say()
#per.display()

运行结果,结果为:

我是人,名字为: zhangsan

上面程序中,Person 类同时继承 People 和 Animal,其中 People 在前。这意味着,在创建 per 对象时,其将会调用从 People 继承来的构造函数。因此我们看到,上面程序在创建 per 对象的同时,还要给 name 属性进行赋值。

但如果去掉最后一行的注释,运行此行代码,Python 解释器会报如下错误:

Traceback (most recent call last):
  File "H:/临时文件夹/tes.py", line 24, in <module>
    per.display()
  File "H:/临时文件夹/tes.py", line 14, in display
    print("我是动物,我吃",self.food)
AttributeError: 'Person' object has no attribute 'food'

这是因为,从 Animal 类中继承的 display() 方法中,需要用到 food 属性的值,但由于 People 类的构造方法“遮蔽”了Animal 类的构造方法,使得在创建 per 对象时,Animal 类的构造方法未得到执行,所以程序出错。

反过来也是如此,如果将第 18 行代码改为如下形式:

class Person(Animal, People)

则在创建 per 对象时,会给 food 属性传值。这意味着,per.display() 能顺序执行,
但 per.say() 将会报错:

Traceback (most recent call last):
  File "H:\临时文件夹\tes.py", line 23, in <module>
    per.say()
  File "H:\临时文件夹\tes.py", line 6, in say
    print("我是人,名字为:", self.name)
AttributeError: 'Person' object has no attribute 'name'

针对这种情况,正确的做法是定义 Person 类自己的构造方法(等同于重写第一个直接父类的构造方法)。但需要注意,如果在子类中定义构造方法,则必须在该方法中调用父类的构造方法。

在子类中的构造方法中,调用父类构造方法的方式有 2 种,分别是:
类可以看做一个独立空间,在类的外部调用其中的实例方法,可以向调用普通函数那样,只不过需要额外备注类名(此方式又称为未绑定方法);
使用 super() 函数。但如果涉及多继承,该函数只能调用第一个直接父类的构造方法。
也就是说,涉及到多继承时,在子类构造函数中,调用第一个父类构造方法的方式有以上 2 种,而调用其它父类构造方法的方式只能使用未绑定方法。

值得一提的是,Python 2.x 中,super() 函数的使用语法格式如下:

super(Class, obj).__init__(self,...)

其中,Class 值得是子类的类名,obj 通常指的就是 self。

但在 Python 3.x 中,super() 函数有一种更简单的语法格式,
推荐大家使用这种格式:

super().__init__(self,...)

在掌握 super() 函数用法的基础上,我们可以尝试修改上面的程序:

class People:
    def __init__(self,name):
        self.name = name
    def say(self):
        print("我是人,名字为:",self.name)
class Animal:
    def __init__(self,food):
        self.food = food
    def display(self):
        print("我是动物,我吃",self.food)
class Person(People, Animal):
    #自定义构造方法
    def __init__(self,name,food):
        #调用 People 类的构造方法
        super().__init__(name)
        #super(Person,self).__init__(name) #执行效果和上一行相同
        #People.__init__(self,name)#使用未绑定方法调用 People 类构造方法
        #调用其它父类的构造方法,需手动给 self 传值
        Animal.__init__(self,food)    
per = Person("zhangsan","熟食")
per.say()
per.display()

运行结果为:

我是人,名字为: zhangsan
我是动物,我吃 熟食

可以看到,Person 类自定义的构造方法中,调用 People 类构造方法,可以使用
super() 函数,也可以使用未绑定方法。但是调用 Animal 类的构造方法,只能
使用未绑定方法。

切记,super 只能在新式类中使用!!!
切记,super 只能在新式类中使用!!!
切记,super 只能在新式类中使用!!!

十三、复习

Keywords(关键字)
• and
• del
• from
• not
• while
• as
• elif
• global
• or
• with
• assert
• else
• if
• pass
• yield
• break
• except
• import
• print
• class
• exec
• in
• raise
• continue
• finally
• is
• return
• def
• for
• lambda
• try

数据类型
• True
• False
• None
• bytes
• strings
• numbers
• floats
• lists
• dicts

字符串转义序列(Escape Sequences)
• \
• ’
• "
• \a
• \b
• \f
• \n
• \r
• \t
• \v

旧式字符串格式化(String Formats)

• %d
• %i
• %o
• %u
• %x
• %X
• %e
• %E
• %f
• %F
• %g
• %G
• %c
• %r
• %s
• %%

操作符号
• +
• -
• *
• **
• /
• //
• %
• <
• >
• <=
• >=
• ==
• !=
• <>
• ( )
• [ ]
• { }
• @
• ,
• :
• .
• =
• ;
• +=
• -=
• *=
• /=
• //=
• %=
• **=

阅读代码
如何运用你学到的东西理解别人的代码。
首先把你想要理解的代码打印到纸上。没错,你需要打印出来,因为和屏幕输出相比,你的眼睛和大脑更习惯于接受纸质打印的内容。一次最多打印几页就可以了。
然后通读你打印出来的代码并做好标记,标记的内容包括以下几个方面:

  1. 函数以及函数的功能。
  2. 每个变量的初始赋值。
  3. 每个在程序的各个部分中多次出现的变量。它们以后可能会给你带来麻烦。
  4. 任何不包含 else 的 if 语句。它们是正确的吗?
  5. 任何可能没有结束点的 while 循环。
  6. 最后一条,代码中任何你看不懂的部分都记下来。
    接下来你需要通过注解的方式向自己解释代码的含义。解释各个函数的使用方法,各个变量的用途,以及任何其它方面的内容,只要能帮助你理解代码即可。
    最后,在代码中比较难的各个部分,逐行或者逐个函数跟踪变量值。你可以再打印一份出来,在空白处写出你要“追踪”的每个变量的值。 一旦你基本理解了代码的功能,回到电脑面前,在屏幕上重读一次,看看能不能找到新的问题点。然后继续找新的代码,用上述的方法去阅读理解,直到你不再需要纸质打印为止。

在网上搜索这些不懂的东西,快速了解弄明白。比如:不懂的是 yield 搜索栏键入 python yield 即可。

十四、项目骨架


接下来好好利用前面学到的东西,开始综合起来做项目。
这里你将学会如何建立一个项目“骨架”目录。这个骨架目录具备让项目跑起来的所有基本内容。它里 边会包含你的项目文件布局、自动化测试代码,模组,以及安装脚本。当你建立一个新项目的时候,只 要把这个目录复制过去,改改目录的名字,再编辑里边的文件就行了。

1. 骨架内容

首先,切换到你要创建你的骨架目录的盘符位置,接着
cd/要创建的根目录,准备好之后,接下来,重点来了,使用下述命令创建你的骨架目录:

$ mkdir projects
$ cd projects/
$ mkdir skeleton
$ cd skeleton
$ mkdir bin
$ mkdir NAME
$ mkdir tests
$ mkdir docs

我使用了一个叫 projects 的目录,用来存放我自己的各个项目。然后我在里边建立了一个叫做skeleton 的文件夹,这就是我们新项目的基础目录。其中叫做 NAME 的文件夹是你的项目的主文件夹,你可以将它任意取名。接下来我们要配置一些初始文件。以下是如何在 Linxu/OSX 环境下进行配置:

~/projects/skeleton $ touch NAME/__init__.py
~/projects/skeleton $ touch tests/__init__.py

Windows PowerShell 的配置方式如下:

$ new-item -type file NAME/__init__.py
$ new-item -type file tests/__init__.py

以上命令为你创建了空的模组目录,以供你后面为其添加代码。然后我们需要建立一个 setup.py 文件,这个文件在安装项目的时候我们会用到它:

try:
    from setuptools import setup
except ImportError:
    from distutils.core import setup
config = {
   'description': 'My Project',
   'author': 'My Name',
   'url': 'URL to get it at.',
   'download_url': 'Where to download it.',
   'author_email': 'My email.',
   'version': '0.1',
   'install_requires': ['nose'],
   'packages': ['NAME'],
   'scripts': [],
   'name': 'projectname'
}

setup(**config)

编辑这个文件,把自己的联系方式写进去,然后放到那里就行了。最后你需要一个简单的测试专用的骨架文件叫 tests/NAME_tests.py:

from nose.tools import *
import NAME

    def setup():
        print "SETUP!"

    def teardown():

print "TEAR DOWN!"
print "I RAN!"

2. 最终目录结构

当你完成了一切准备工作,你的目录看上去应该和我这里的一样:

$ ls -R
NAME               bin          docs          setup.py          tests
./NAME:
__init__.py
./bin:
./docs:
./tests:
NAME_tests.py       __init__.py

这是 unix 下看到的东西,不过 Windows 下也是一样的,如果以树状结构显示就是这个样子:

setup.py
NAME/
    __init__.py
bin/
docs/
tests/
     NAME_tests.py
     __init__.py

从现在开始,你应该在这层目录运行命令。如果你运行 ls -R 看到的不是这个目录架构,那你所处的 目录就是错的。例如人们经常到 tests/ 目录下运行那里的文件,但这样是行不通的。要运行你的测试,你需要到 tests/ 的上一级目录,也就是我这里显示的目录来运行。所以,如果你运行下面的命令:

$ cd tests/ # WRONG! WRONG! WRONG!
$ nosetests
Ran 0 tests in 0.000s
OK

这样做大错特错!你必须在 tests 目录的上一层运行才可以,所以假设你犯了这个错误,你应该用下面的
方法来正确执行:

$ cd .. # 离开 tests/ 目录
$ ls # CORRECT! 现在你所处的目录是正确的
NAME bin docs setup.py tests
$ nosetests
.
Ran 1 test in 0.004s
OK

记住这一条,因为人们经常犯这样的错误。

3. Python 软件包的安装

你需要预先安装一些软件包,不过问题就来了。我的本意是让这本书越清晰越干净越好,不过安装软件的方法是在是太多了,如果我要一步一步写下来,那 10 页都写不完,而且告诉你吧,我本来就是个懒人。
所以我不会提供详细的安装步骤了,我只会告诉你需要安装哪些东西,然后让你自己搞定。这对你也有好处,因为你将打开一个全新的世界,里边充满了其他人发布的 Python 软件。
首先,你要检查自己都装了哪些软件包,显示pip安装的所有库:

pip list <or> pip freeze

接下来你需要安装下面的软件包(只装没有的):

  1. pip – http://pypi.python.org/pypi/pip
  2. distribute – http://pypi.python.org/pypi/distribute
  3. nose – http://pypi.python.org/pypi/nose/
  4. virtualenv – http://pypi.python.org/pypi/virtualenv
    不要只是手动下载并且安装这些软件包,你应该看一下别人的建议,尤其看看针对你的操作系统别人是怎样建议你安装和使用的。同样的软件包在不一样的操作系统上面的安装方式是不一样的,不一样版本的 Linux 和 OSX 会有不同,而 Windows 更是不同。 我要预先警告你,这个过程会是相当无趣。在业内我们将这种事情叫做 “yak shaving(剃牦牛)”。它指的是在你做一件有意义的事情之前的一些准备工作,而这些准备工作又是及其无聊冗繁的。你要做一个很酷的 Python 项目,但是创建骨架目录需要你安装一些软件包,而安装软件包之前你还要安装package installer (软件包安装工具),而要安装这个工具你还得先学会如何在你的操作系统下安装软件,真是烦不胜烦呀。 无论如何,还是克服困难把。你就把它当做进入编程俱乐部的一个考验。每个程序员都会经历这条道路, 在每一段“酷”的背后总会有一段“烦”的。

4. 测试你的配置

安装了所有上面的软件包以后,你就可以做下面的事情了:

~/projects/skeleton $ nosetests
.
Ran 1 test in 0.007s
OK

不过如果你没有看到上面的画面,那就说明你哪里出 错了。确认一下你的 NAME 和 tests 目录下存在 __init__.py,并且你没有把tests/NAME_tests.py 命名错。

5. 使用这个骨架

剃牦牛的事情已经做的差不多了,以后每次你要新建一个项目时,只要做下面的事情就可以了:

  1. 拷贝这份骨架目录,把名字改成你新项目的名字。
  2. 再将 NAME 模组更名为你需要的名字,它可以是你项目的名字,当然别的名字也行。
  3. 编辑 setup.py 让它包含你新项目的相关信息。
  4. 重命名 tests/NAME_tests.py ,让它的名字匹配到你模组的名字。
  5. 使用 nosetests 检查有无错误。
  6. 开始写代码吧。

我这里的骨架目录:

$ Is -R
home
   TemplateCatalog
       NAME               bin         docs       setup.py        tests
       ./NAME:
       __init__.py
       ./bin:
       ./docs:
       ./tests:
       NAME_tests.py      __init__.py
   UserCatalog 
       NAME               bin         docs       setup.py         tests
       ./NAME:
       __init__.py
       ./bin:
       ./docs:
       ./tests:
       NAME_tests.py      __init__.py

home下两个目录,一个模板目录,当作被复制的模板重复利用;一个用户目录,经常使用,项目大本营。
Windows下同理,是这样子的:

ProjectTemplate
    setup.py
    NAME/
        __init__.py
    bin/
    docs/
    tests/
         NAME_tests.py
         __init__.py
UserCatalog
    setup.py
    NAME/
        __init__.py
    bin/
    docs/
    tests/
         NAME_tests.py
         __init__.py

这样一个大骨架下,一个模板,一个项目开发存放。开始项目开发写代码的编程之旅吧,如有不懂的再翻开看看或上网寻找答案。如有不足之处,望指正。
《进阶》下期见。


网站公告

今日签到

点亮在社区的每一天
去签到