// 还需要修订!
主要参考http://c.biancheng.net/python/。
必须要强调,如果想要彻底掌握Python,那么就有大量需要学习的东西,而不是专门从事相关工作的不太可能彻底研究清楚每个细节,所以应该针对基本内容和所需进行学习。学习 C++ 也是类似的。
本文只考虑 Python3,且只记录个人不熟悉部分,大部分也是基于对C的理解基础上的,另外 Python 之所以强大主要是因为丰富的库,而库是学不完的。
编译型与解释型:
对于编译型语言,编译过程需要进行五个操作:词法分析、语法分析、语义分析、性能优化、生成可执行文件,解释型语言也有类似的过程,但是不需要生成可执行文件,而是在运行时逐行直接翻译成机器语言,具体可以研究下编译原理。而Python是解释型语言。而Java 和 C# 是半编译半解释型的语言,首先根据程序生成字节码,然后在虚拟机中执行字节码。
解释型语言的主要优势在于跨平台,且由于发明时间较晚语法上更现代,开发效率高。但是也有很多劣势:在运行解释型语言的时候,我们始终都需要源代码和解释器,所以说它无法脱离开发环境。而且解释型语言效率较低,计算机的一些底层功能,或者关键算法,一般都使用 C/C++ 实现,只有在应用层面(比如网站开发、批处理、小工具等)才会使用解释型语言。
胶水语言:
Python 有很多功能强大的库,程序员可以把使用其它语言制作的各种模块(尤其是C语言和 C++)很轻松地联结在一起,因此 Python 又常被称为“胶水”语言。
Python和C++的混合编程参考这里。
应用领域:
自动化运维、人工智能、网路爬虫、科学计算、游戏开发(很多游戏使用 C++ 编写图形显示等高性能模块,而使用 Python 或 Lua 编写游戏的逻辑)等。
跟进:
PEP(Python Enhancement Proposal),全称是 Python 改进方案。它是提交 Python 变化的书面文档,也是社区对这一变化进行讨论的出发点。PEP 文档主要有 3 个用途:汇总 Python 核心开发者重要的信息,并通过 Python 发布日程;提供代码风格、文档或者其他指导意见;对提交的功能进行说明。
PEP0和PEP1。
版本:
①实现语言
我们默认使用的是CPython,也就是使用C实现的,此外还有使用Java的JPython,使用.net的IronPython,使用Python实现的PyPy,C的增强版Stackless Python。
②Python2和Python3
Python3和Python2代码有很多差别,可以参考这里实现自动转换。
Jupyter Notebook:
Jupyter 发展到现在,已经成为了一个几乎支持所有语言,能够把软件代码、计算输出、解释文档、多媒体资源整合在一起的多功能科学运行平台。
它的优势:整合了所有资源、交互性编程体验、轻松运行他人编写的代码。Jupyter 官方的 Binder 平台以及 Google 提供的 Google Colab 环境,它们可以让 Jupyter Notebook 变得和 Google Doc 在线文档一样。比如用 Binder 打开一份 GitHub 上的 Jupyter Notebook 时,就不需要安装任何 Python 库,直接在打开代码就能运行。
具体参考这里。
PEP8规范。
下划线用法。
# 1. 注释
# 这是一个单行注释
"""
多行注释通常用来为 Python 文件、模块、
类或者函数等添加版权或者功能描述信息。
"""
'''
多行注释通常用来为 Python 文件、模块、
类或者函数等添加版权或者功能描述信息。
!多行注释不支持嵌套!
!不管是多行注释还是单行注释,当注释符作为字符串的一部分出现时,
就不能再将它们视为注释标记,而应该看做正常代码的一部分:
print('''Hello,World!''')!
'''
# 2. 缩进规则
'''
在 Python 中,对于类定义、函数定义、流程控制语句、异常处理语句等,行尾
的冒号和下一行的缩进,表示下一个代码块的开始,而缩进的结束则表示此代码
块的结束。
Python 中实现对代码的缩进,可以使用空格或者 Tab 键实现。但无论是手动敲
空格,还是使用 Tab 键,通常情况下都是采用 4 个空格长度作为一个缩进量。
!同一个级别代码块的缩进量必须一样,否则解释器会报 SyntaxError 异常错误!
'''
# 3. 编码规范
'''
Python 采用 PEP 8 作为编码规范,其中 PEP 是 Python Enhancement
Proposal(Python 增强建议书)的缩写,8 代表的是 Python 代码的样式指
南。一些基本规则为:
1. 每个 import 语句只导入一个模块
2. 不要在行尾添加分号,也不要用分号将两条命令放在同一行
3. s=("建议每行不超过 80 个字符,如果超过,建议使用小括号将多行内容隐"
"式的连接起来,而不推荐使用反斜杠 \ 进行连接。除了导入模块的语句过长"
"或者注释里的 URL。")
4. 使用必要的空行可以增加代码的可读性,通常在顶级定义(如函数或类的定
义)之间空两行,而方法定义之间空一行,另外在用于分隔某些功能的位置也可
以空一行。
5. 通常情况下,在运算符两侧、函数参数之间以及逗号两侧,
都建议使用空格进行分隔。
'''
# 4. 标识符命名
'''
标识符是由字符(A~Z 和 a~z)、下划线和数字组成,但第一个字符不能是数
字。识符不能和 Python 中的保留字相同。在 Python 中,标识符中的字母是
严格区分大小写的。
Python 语言中,以下划线开头的标识符有特殊含义:
1. 以单下划线开头的标识符(如 _width),表示不能直接访问的类属性,其无
法通过 from...import* 的方式导入;
2. 以双下划线开头的标识符(如__add)表示类的私有成员;
3. 以双下划线作为开头和结尾的标识符(如 __init__),是专用标识符。
4. 规范:
> 当标识符用作模块名时,应尽量短小,并且全部使用小写字母,可以使用下划线分割多个字母,例如 game_mian
> 当标识符用作包的名称时,应尽量短小,也全部使用小写字母,不推荐使用下划线,例如 com.mr
> 当标识符用作类名时,应采用单词首字母大写的形式,例如 MyBook
> 模块内部的类名,可以采用 "下划线+首字母大写" 的形式,如 _Book
> 函数名、类中的属性名和方法名,应全部使用小写字母,多个单词之间可以用
下划线分割
> 常量命名应全部使用大写字母,单词之间可以用下划线分割
'''
!!!很多东西都是简要了解,详细学习的话,内容太多了!!!
要善于使用 help 和 dir 函数,不懂得就用这两个去查。还有,下面很多地方使用 str 指带某个字符串,实际编程中不要使用 str,因为 str
善于使用 dir 和 help 以及 vars 函数,可以快速查看不懂的函数和对象。
help(obj/obj.func...) # 除了可以查看内置函数、内置类,还能查看对象的成员,注意成员函数不要加括号
dir(obj/obj.func...) # 查看对象的所有属性、函数
vars(obj) # 查看对象的所有局部变量,更细节
事实上,无论是 Python 提供给我们的函数,还是自定义的函数,其说明文档都需要设计该函数的程序员自己编写。也就是实现__doc__ 属性属性。我们不必直接覆盖__doc__属性,只需要按照如下格式编写注释:
def my_func():
'''
这里是 help 显示的帮助文档。这里其实也最好按照 PEP8 要求编写。
'''
Python 中,语句、运算符对于是否继续进行的处理很不同,以下几种都被视作否定:
0 # 数值0
0.0 # 数值0
False # 假
"" # 空字符串
[ ] # 空列表
( ) # 空元组
{
} # 空字典
dict() # 空字典
set() # 空集合
None # 空值, None 是 NoneType 数据类型的唯一值
这些都相当于 C 中的 0,也就是对于逻辑判断或者if这样的句子,一旦发现上述的“否定”,就不再继续向下进行了。
查看方法:
import keyword
print(keyword.kwlist)
包含:
[‘False’, ‘None’, ‘True’, ‘and’, ‘as’, ‘assert’, ‘break’, ‘class’, ‘continue’, ‘def’, ‘del’, ‘elif’, ‘else’, ‘except’, ‘finally’, ‘for’, ‘from’, ‘global’, ‘if’, ‘import’, ‘in’, ‘is’, ‘lambda’, ‘nonlocal’, ‘not’, ‘or’, ‘pass’, ‘raise’, ‘return’, ‘try’, ‘while’, ‘with’, ‘yield’]
和 C 相比,比较特殊的有:
# 1. with...as...
with open("file_path", "mode") as src_name:
...
# 2. import...as...
import tensorflow as tf
# 3. except...as...
# 异常抓取
except Exception as e:
...
'''
assert expression [, arguments]
等价于:
if not expression:
raise AssertionError([arguments])
'''
assert np.isscalar(a), 'a must be scalar'
'''
因为Python有GC,所以Python的del不同于C的free和C++的delete,它只是单纯地删除变量引用。
'''
a = 1
b = a
c = [1, 2]
del a
print(b) # 输出 1
del c[0]
print(c) # 输出 [2]
在实际过程中,即便使用 del 关键字删除了指定变量,且该变量所占用的内存再没有其他变量使用,此内存空间也不会真正地被系统回收并进行二次使用,它只是会被标记为无效内存。如果想让系统回收这些可用的内存,需要借助 gc 库中的 collect() 函数。
#引入gc库
import gc
tt = 'hello'
#定义一个包含多个类型的 list
list1 = [1,4,tt,3.4,"yes",[1,2]]
del list1
#回收内存地址
gc.collect()
异常处理:
try:
fp = open(filename, "r")
print(fp.readline())
except IOError as e:
print('error!')
finally: # 一定会执行
fp.close()
# 如果需要在局部修改全局变量的值,那么必须用global修饰
foo = 100
def change_foo():
# foo = 200 # 不能改变全局
global foo # 这两句才能改变
foo = 200
# 另一种用法:
# 在函数体内也可定义全局变量:用 global 关键字对变量进行修饰后,该变量
# 就会变为全局变量。
官方没有,需要额外安装。
判断集合中是否有某个元素。对于dict,则检测是否有某个key。
==是python标准操作符中的比较操作符,用来比较判断两个对象的value(值)是否相等,只比较值;is也被叫做同一性运算符,这个运算符比较判断的是对象间的唯一身份标识,也就是id是否相同,也就是是否是同一个的引用。
a = b = [1,2,3]
c = [1,2,3]
a == b # True
a == c # True
a is b # True
a is c # False
匿名函数。
# 数字 + 1 的匿名函数
g = lambda x:x+1
g(2) # 3
闭包是指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。闭包(closure)是函数式编程的重要的语法结构。闭包也是一种组织代码的结构,它同样提高了代码的可重复使用性。创建一个闭包必须满足:必须有一个内嵌函数、内嵌函数必须引用外部函数中的变量、外部函数的返回值必须是内嵌函数。
首先举个例子说明内部函数对外部函数变量的遮蔽问题:
#全局函数
def outdef ():
name = "所在函数中定义的 name 变量"
#局部函数
def indef():
print(name) # 报错,未定义就使用
name = "局部函数中定义的 name 变量" # 遮蔽了外部 name
indef()
举例说明闭包和nonlocal的使用,下面是一个平均值计算的函数:
def make_averager(): # 闭包
count = 0 # 自由变量
total = 0
def averager(new_value): # 内嵌函数
# 如果没有nonlocal关键字声明一下,会报UnboundLocalError错
# 误,因为count和total都是不可变对象,因此我们一旦在内嵌函
# 数中试图修改其值,实际上是创建了新的对象,并把变量设置为
# 其引用,但是闭包绑定的是原来的不可变对象,因此变量会转变为
# 局部变量,而count += 1这种操作之前,并没有为其赋值,因此
# 报错。nonlocal的作用是把变量标记为自由变量,如果为nonlocal
# 声明的变量赋予新值,闭包中保存的绑定会更新。
nonlocal count, total
count += 1 # 总元素数
total += new_value # 总和
return total / count # 返回平均值
return averager # 返回内嵌函数
avg = make_average()
print(avg(10)) # 10
print(avg(20)) # 15
上面例子中,avg.__closure__是闭包绑定自由变量的地方,每个自由变量对应一个cell对象,其cell_content属性可以得到自由变量的值:avg.__closure__[0].cell_contents # 2, 对应 count 变量。
值得一提的是,avg中的自由变量名可以通过avg.__code__.co_freevars得到,这个属性是一个自由变量名称的tuple。
上述关于闭包的使用比较复杂,一个更简单的例子:
#闭包函数,其中 exponent 称为自由变量
def nth_power(exponent):
def exponent_of(base):
return base ** exponent # exponent 是自由变量
return exponent_of # 返回值是 exponent_of 函数
square = nth_power(2) # 计算一个数的平方
cube = nth_power(3) # 计算一个数的立方
print(square(2)) # 计算 2 的平方
print(cube(2)) # 计算 2 的立方
raise 用来抛出异常。一旦执行了raise语句,raise后面的语句将不能执行。语法格式为:raise [exceptionName [(reason)]]。语句中 Exception 是异常的类型(例如,NameError)参数标准异常中任一种,args 是自已提供的异常参数。不指定异常类型,则默认触发RuntimeError 异常;在 except 中使用 raise,则会原样抛出捕获到的这个异常。
举例:
try:
a = input("输入一个数:")
#判断用户输入的是否为数字
if(not a.isdigit()):
raise ValueError("a 必须是数字")
except ValueError as e:
print("引发异常:",repr(e))
同级别运算符计算顺序:
Python 中大部分运算符都具有左结合性,也就是从左到右执行;只有单目运算符(例如 not 逻辑非运算符)、赋值运算符和三目运算符例外,它们具有右结合性,也就是从右向左执行。上表中列出了所有 Python 运算符的结合性。
Python 不支持自增,i++这样的语法是没有的。但是 ++i 不会报错,因为前面的加号不过是表示正数罢了,写几个加号都没有区别。
表达式和字符串换行,字符串转义。
整除(只保留商的整数部分)。
次方。2**3 = 8
单行注释。
连续赋值:a = b = c = 1,=的右结合性。
下面的写法是错误的:
(a = 10) # 括号里不能给实参赋值?
注意位运算是单个的 a & b;逻辑是and\or\not,没有&&、||这样的。
注意逻辑运算的短路功能,而且 Python 的逻辑运算被扩展了(and 和 or 运算符会将其中一个表达式的值作为最终结果,而不是将 True 或者 False 作为最终结果):
True and False # False
100 and 200 # 100
45 and 0 # 0
"" or "a" # "a"
比较运算比正常多了:
is 判断两个变量所引用的对象是否相同
is not 判断两个变量所引用的对象是否不相同
比较运算符还可以连续使用:
0 <= score <= 100
不是 (a>b)?a:b这种,而是如下这种:
max = a if a>b else b
还可以嵌套:
a if a>b else c if c>d else d # a if a>b else (c if c>d else d)
‘字符串’、“字符串”
多行注释或者长字符串,支持直接换行。
当程序中有大段文本内容需要定义成字符串时,优先推荐使用长字符串形式,因为这种形式非常强大,可以在字符串中放置任何内容,包括单引号和双引号。
Python 用缩进表示代码块,在任何地方都可以缩进,先当与C中添加了一对{}的效果。判断、循环、函数定义等语句要求必须有缩进,且关键句后要用冒号,例如:
for _ in range(4):
if (_ % 2 == 0):
print(_)
所有循环、判断、函数定义等结构,如果啥都不填写,只是预留的,需要使用 pass 语句,例如:
if a > 10:
pass
else
pass
Python 中没有 switch 语句,注意每句后面的冒号。
for 循环,相当于C++中的foreach。只能用于 Iterator,或者能转化为 Iterator 的对象,具体参考关于迭代的部分。
while和C差不多。
for/while都可以跟一个else,在循环正常退出的时候会执行else,但是如果break/continue出来就忽略掉else了:
i = 0
while i < 10:
i += 1
else: # 如果 while 中运行了 break/continue,那么就不执行
print(i)
Python 函数支持接收多个( ≥0 )参数,且Python 函数还支持返回多个( ≥0 )值。Python 中,根据实际参数的类型不同,函数参数的传递方式可分为 2 种,分别为值传递和引用(地址)传递:①值传递:适用于实参类型为不可变类型(字符串、数字、元组);
②引用(地址)传递:适用于实参类型为可变类型(列表,字典)。
def my_func(para1, para2, *args, para3='default', **kwargs):
'''
这四种参数的顺序不能改变。
para1、para2:位置参数
*args:可变参数,形式为一个 tuple
para3:默认参数
kwargs:可变关键字参数,是一个 dict
'''
print(para1, para2, para3)
for i,(j,k) in zip(args, kwargs.items()):
print(i,j,k)
my_func(1, para2=2, 'args_1', 'args_2', kwargs_1=10, kwargs_2=2)
# para2=2这种叫做关键字参数,默认参数para3省略率
可以使用“函数名.__defaults__”查看函数的默认值参数的当前值,其返回值是一个元组。参数默认值具有继承性,比如默认参数是个 list,那么如果函数中修改了这个参数的值,默认值也会修改,也就是下次调用函数的时候就变成了不同的默认值。此外,我们还可以使用逆向参数收集过程,拆分序列作为函数参数:
def my_func(para1, para2, *args, para3 = [], **kwargs):
if len(para3): # 利用默认值继承性实现调用次数计数
para3[0] += 1
else:
para3.append(0)
print(para1, para2, para3)
print(args, kwargs)
l = [0, 0, 1, 2, 3]
d = {
'd_ele1':4, 'd_ele2':5}
# 逆向参数收集
my_func(*l, **d)
'''输出
0 0 [1]
(1,2,3) {'d_ele1':4, 'd_ele2':5}
'''
my_func(*l, **d)
'''输出
0 0 [2]
(1,2,3) {'d_ele1':4, 'd_ele2':5}
'''
函数如果不写 return 语句,则默认返回 None。如果 return 多个值,例如 return a1, a2,那么会自动包装成 tuple 返回,我们用一个变量接收,就是元组,用两个接收,就是 a1 和 a2 两个值,参考下面关于序列的部分。
还可以使用函数注解:
def my_func(a:str='123')->str:
# 形参和返回值指定,但是如果不按照这个,并不会报错,主要是给编译器用
# 函数注解没有任何语法上的意义,只是为函数参数和返回值做注解,并在运行
# 获取这些注解,仅此而已。
# 注意,如果参数有默认值,参数注解位于冒号和等号之间。
# 函数注解可以是类、字符串或者表达式比如:a:'这是一个注解',也没问题
# 给函数定义好注解之后,可以通过函数对象的 __annotations__ 属性获取,
# 它是一个字典,在应用运行期间可以获取。
print(a)
return 'ok'
print(my_func('10'))
'''输出
10
ok
'''
偏函数,制定函数的不同版本:
from functools import partial
def add( n, m ):
return a+b
# 定义偏函数,并设置参数 n 对应的实参值为 100
add_by_2 = partial( add, 2 )
add_by_2_v2 = partial( add, n = 2 )
print(add( 100, 7 ))
print(add_by_2 ( 7 ))
print(add_by_2_v2( m = 7)) # 必须是命名参数
函数可以嵌套定义,内部的函数叫做局部函数:如果所在函数没有返回局部函数,则局部函数的可用范围仅限于所在函数内部;反之,如果所在函数将局部函数作为返回值,则局部函数的作用域就会扩大,既可以在所在函数内部使用,也可以在所在函数的作用域中使用。局部函数可以用于闭包,参考上面关于nonlocal的部分。
函数本身也是对象,不过是 callable 的,可以赋值给变量,也可以当作参数/返回值传递,很方便。例如:my_len = len,那么计算序列长度的时候就可以使用 my_len 了。
lambda表达式(匿名函数),单行的函数,除了写着方便,还能在使用完后立即释放,提高程序性能。语法为:
lambda argument_list:expersion
argument_list可以是一个或者多个参数,expression是返回值。例如:f = lambda x,y,z:x+y+z,这个函数返回x+y+z的值。
内置函数和标准库函数是不一样的。内置函数是解释器的一部分,它随着解释器的启动而生效;标准库函数是解释器的外部扩展,导入模块以后才能生效。一般来说,内置函数的执行效率要高于标准库函数。
可以通过__builtins__全局变量查看支持的内建函数、内建异常等等:
dir(__builtins__)
不真的含义是:空、0、false。
all(iterable):如果 iterable 的所有元素为真或迭代器为空,返回 True 。
any(iterable):如果 iterable 的任一元素为真则返回 True。 如果迭代器为空,返回 False。
例如:
# 以下的0可以替换为''或者False,效果是一样的
# 以下结果都是 True
all([]);all([1,2,'a']);all('123');all((1,2,'a'));all({
'a':0,'b':'b'})
any('001');any([0,0,1]);any((0,0,1));any({
0:0, 'a':'a'})
# 以下结果都是 False
all([0,1])#...
any([]);any([0,False,''])#...
ascii(object)、repr(object):返回一个对象可打印的字符串。非ascii字符,ascii函数的字符串中会显示其unicode码,repr则转义它:
a = '123哈'
ascii(a) # "'123\\u54c8'"
repr(a) # "'123哈'"
# 其它对象则根据
实际调用的是对象的__str__和__repr__方法。
关于 repr, 对于许多类型来说,该函数会尝试返回的字符串将会与该对象被传递给 eval() 时所生成的对象具有相同的值,在其他情况下表示形式会是一个括在尖括号中的字符串。
在编写代码时,一般会使 repr() 数来生成动态的字符串,再传入到 eval() 或 exec() 函数内,实现动态执行代码的功能。
bin(x):将一个整数转变为一个前缀为“0b”的二进制字符串。
如果 x 不是 Python 的 int 对象,那它需要定义 __index__() 方法返回一个整数。
hex(x):将整数转换为以“0x”为前缀的小写十六进制字符串。如果 x 不是 Python int 对象,则必须定义返回整数的 __index__() 方法。
oct(x):将一个整数转变为一个前缀为“0o”的八进制字符串。结果是一个合法的 Python 表达式。如果 x 不是 Python 的 int 对象,那它需要定义 __index__() 方法返回一个整数。
class bool([x]):返回逻辑检测的结果。
任何对象都可以进行逻辑值的检测,以便在 if 或 while 作为条件或是作为下文所述布尔运算的操作数来使用。一个对象在默认情况下均被视为真值,除非当该对象被调用时其所属类定义了 __bool__() 方法且返回 False 或是定义了 __len__() 方法且返回零。
class complex([real[, imag]]):返回值为 real + imag*1j 的复数,或将字符串或数字转换为复数。如果第一个形参是字符串,则它被解释为一个复数,并且函数调用时必须没有第二个形参。第二个形参不能是字符串。对于一个普通 Python 对象 x,complex(x) 会委托给 x.__complex__()。 如果 __complex__() 未定义则将回退至 __float__()。 如果 __float__() 未定义则将回退至 __index__()。
class float([x]):返回从数字或字符串 x 生成的浮点数。对于一个普通 Python 对象 x,float(x) 会委托给 x.__float__()。 如果 __float__() 未定义则将回退至__index__()。
class int(x, base=10):返回一个基于数字或字符串 x 构造的整数对象,或者在未给出参数时返回 0。 如果 x 定义了 __int__(),int(x) 将返回 x.__int__()。 如果 x 定义了 __index__(),它将返回 x.__index__()。
a,b,c = '10', '10.2', '10+10j'
int(a) # 10 : int
int(b) # 10.2 : float
float(a) # 10.0 : float
float(b) # 10.2 : float
complex(c) # 10+10j : complex
**breakpoint(*args, kws):它调用 sys.breakpointhook() ,直接传递 args 和 kws 。默认情况下,sys.breakpointhook()调用pdb.set_trace(),不需要参数,主要是简化 pdb 调试的写法:
# 旧的写法
foo()
import pdb; pdb.set_trace()
bar()
# 新的写法
foo()
breakpoint()
bar()
class bytearray([source[, encoding[, errors]]]):返回一个新的 bytes 数组。 bytearray 类是一个可变序列,包含范围为 0 <= x < 256 的整数。可选形参 source 可以用不同的方式来初始化数组:
class bytes([source[, encoding[, errors]]]):
返回一个新的“bytes”对象, 是一个不可变序列,包含范围为 0 <= x < 256 的整数。bytes 是 bytearray 的不可变版本。
callable(object):
如果参数 object 是可调用的就返回 True,否则返回 False。 请注意类是可调用的(调用类将返回一个新的实例);如果实例所属的类有__call__() 则它就是可调用的。
chr(i):返回 Unicode 码位为整数 i 的字符的字符串格式。
ord©:chr的逆函数,输入一个unicode字符,输出其Unicode码位。
@classmethod:把一个方法封装成类方法。一个类方法把类自己作为第一个实参,就像一个实例方法把实例自己作为第一个实参。
classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
class C:
@classmethod
def f(cls, arg1, arg2, ...): ...
类方法的调用可以在类上进行 (例如 C.f()) 也可以在实例上进行 (例如 C().f())。 其所属类以外的类实例会被忽略。 如果类方法在其所属类的派生类上调用,则该派生类对象会被作为隐含的第一个参数被传入。
@staticmethod:将方法转换为静态方法。静态方法不会接收隐式的第一个参数。静态方法的调用可以在类上进行 (例如 C.f()) 也可以在实例上进行 (例如 C().f())。Python中的静态方法与Java或C ++中的静态方法类似。
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1):
把一个字符串当作程序执行,将 source 编译成代码或 AST(抽象语法树,是源代码语法结构的一种抽象表示。) 对象。代码对象可以被 exec() 或 eval() 执行。source 可以是常规的字符串、字节字符串,或者 AST 对象。
eval(expression[, globals[, locals]]):实参是一个字符串,expression 参数会作为一个 Python 表达式(从技术上说是一个条件列表)被解析并求值,并使用 globals 和 locals 字典作为全局和局部命名空间。如果 globals 字典存在且不包含以 __builtins__ 为键的值,则会在解析 expression 之前插入以此为键的对内置模块 builtins 的引用。
这个函数也可以用来执行任何代码对象(如 compile() 创建的)。这种情况下,参数是代码对象,而不是字符串。如果编译该对象时的 mode 实参是 ‘exec’ 那么 eval() 返回值为 None。
exec(object[, globals[, locals]]):这个函数支持动态执行 Python 代码。object 必须是字符串或者代码对象。如果是字符串,那么该字符串将被解析为一系列 Python 语句并执行(除非发生语法错误)。如果是代码对象,它将被直接执行。
exec() 函数支持动态执行语句。 globals() 和 locals() 函数各自返回当前的全局和本地字典,因此您可以将它们传递给 eval() 或 exec() 来使用。
例子:
>>>str = "for i in range(0,10): print(i)"
>>> c = compile(str,'','exec') # 编译为字节代码对象
>>> c
<code object <module> at 0x10141e0b0, file "", line 1>
>>> exec(c)
0
1
2
3
4
5
6
7
8
9
>>> str = "3 * 4 + 5"
>>> a = compile(str,'','eval')
>>> eval(a)
17
eval 和 exec 的另一个区别是:eval() 执行完要返回结果,且只能是简单的单行语句字符串,而 exec() 执行完不返回结果。举个例子:
dic={
} #定义一个字
dic['b'] = 3 #在 dic 中加一条元素,key 为 b
print (dic.keys()) #先将 dic 的 key 打印出来,有一个元素 b
exec("a = 4", dic) #在 exec 执行的语句后面跟一个作用域 dic
print(dic.keys()) #exec 后,dic 的 key 多了一个
print(eval(repr('hello, world!'))) # repr的用法
在使用 Python 开发服务端程序时,这两个函数应用得非常广泛。例如,客户端向服务端发送一段字符串代码,服务端无需关心具体的内容,直接跳过 eval() 或 exec() 来执行,这样的设计会使服务端与客户端的耦合度更低,系统更易扩展。
在使用 eval() 或是 exec() 来处理请求代码时,函数 eval() 和 exec() 常常会被黑客利用,成为可以执行系统级命令的入口点,进而来攻击网站。解决方法是:通过设置其命名空间里的可执行函数,来限制 eval() 和 exec() 的执行范围。
TensorFlow 中先将张量定义在一个静态图里,这就相当将键值对添加到字典里一样;TensorFlow 中通过 session 和张量的 eval() 函数来进行具体值的运算,就当于使用 eval() 函数进行具体值的运算一样。
下面的 name 都是字符串。
getattr(object, name[, default]):返回对象命名属性的值。如果指定的属性不存在,且提供了 default 值,则返回它,否则触发 AttributeError。
delattr(object, name):如果对象允许,该函数将删除指定的属性。setattr(object, name, value):类似地,用于改变一个现有属性的值或者新增属性。
都是 class 而不是函数。
目前有两种内置集合类型,set 和 frozenset。 set 类型是可变的 — 其内容可以使用 add() 和 remove() 这样的方法来改变。 由于是可变类型,它没有哈希值,且不能被用作字典的键或其他集合的元素。 frozenset 类型是不可变并且为 hashable — 其内容在被创建后不能再改变;因此它可以被用作字典的键或其他集合的元素。
class str(object=b’’, encoding=‘utf-8’, errors=‘strict’):
返回 object 的 字符串 版本。 如果未提供 object 则返回空字符串。如果 encoding 或 errors 均未给出,str(object) 返回 object.__str__(),这是 object 的“非正式”或格式良好的字符串表示。 对于字符串对象,这是该字符串本身。 如果 object 没有 __str__() 方法,则 str() 将回退为返回 repr(object)。
如果 encoding 或 errors 至少给出其中之一,则 object 应该是一个 bytes-like object (例如 bytes 或 bytearray)。 在此情况下,如果 object 是一个 bytes (或 bytearray) 对象,则 str(bytes, encoding, errors) 等价于 bytes.decode(encoding, errors)。 否则的话,会在调用 bytes.decode() 之前获取缓冲区对象下层的 bytes 对象。
dir([object]):如果没有实参,则返回当前本地作用域中的名称列表。如果有实参,它会尝试返回该对象的有效属性列表。
如果对象有一个名为 __dir__() 的方法,那么该方法将被调用,并且必须返回一个属性列表。这允许实现自定义 __getattr__() 或 __getattribute__() 函数的对象能够自定义 dir() 来报告它们的属性。
如果对象不提供 __dir__(),这个函数会尝试从对象已定义的 __dict__ 属性和类型对象收集信息。结果列表并不总是完整的,如果对象有自定义 __getattr__(),那结果可能不准确。
divmod(a, b):它将两个(非复数)数字作为实参,并在执行整数除法时返回一对商和余数。
对于整数,结果和 (a // b, a % b) 一致。对于浮点数,结果是 (q, a % b) ,q 通常是 math.floor(a / b) 但可能会比 1 小。在任何情况下, q * b + a % b 和 a 基本相等;如果 a % b 非零,它的符号和 b 一样,并且 0 <= abs(a % b) < abs(b) 。
__import__(name, globals=None, locals=None, fromlist=(), level=0):与 importlib.import_module() 不同,这是一个日常 Python 编程中不需要用到的高级函数。
这个一般用不到。
enumerate(iterable, start=0):返回一个枚举对象。iterable 必须是一个序列,或 iterator,或其他支持迭代的对象。 enumerate() 返回的迭代器的 __next__() 方法返回一个元组,里面包含一个计数值(从 start 开始,默认为 0)和通过迭代 iterable 获得的值。
等价于:
def enumerate(sequence, start=0):
n = start
for elem in sequence:
yield n, elem
n += 1
zip(*iterables):创建一个聚合了来自每个可迭代对象中的元素的迭代器。返回一个元组的迭代器,其中的第 i 个元组包含来自每个参数序列或可迭代对象的第 i 个元素。 当所输入可迭代对象中最短的一个被耗尽时,迭代器将停止迭代。
相当于:
def zip(*iterables):
# zip('ABCD', 'xy') --> Ax By
sentinel = object()
iterators = [iter(it) for it in iterables]
while iterators:
result = []
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
return
result.append(elem)
yield tuple(result)
如果我们想把一个数据分块,每个块大小为n,可以采用如下方法:
zip(*[iter(s)]*n)。
zip() 与 * 运算符相结合可以用来拆解一个列表:
>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> zipped = zip(x, y)
>>> list(zipped)
[(1, 4), (2, 5), (3, 6)]
>>> x2, y2 = zip(*zip(x, y))
>>> x == list(x2) and y == list(y2)
True
zip 返回的每一组都会打包成一个元组!
注意这些函数都不改变原来的对象,只是生成一个新的。
filter、map、reversed都是返回迭代器,因此比如我们用这些处理 list,那么我们需要使用 list 函数把处理好的结果再转换回来,例如:
l = [i for i in range(10)] # [0,1,2,3,4,5,6,7,8,9]
filtered_l = list(filter(lambda i:i%2, l)) # [1,3,5,7,9]
sorted则直接返回list。
filter:删除某些元素
map:把每个元素都进行处理,元素数量不变
reversed:倒转
sorted:排序
filter(function, iterable):用 iterable 中函数 function 返回真的那些元素,构建一个新的迭代器。iterable 可以是一个序列,一个支持迭代的容器,或一个迭代器。如果 function 是 None ,则会假设它是一个身份函数,即 iterable 中所有返回假的元素会被移除。
相当于:
# function 不为 None
(item for item in iterable if function(item))
# function 为 None
(item for item in iterable if item)
map(function, iterable, …):返回一个将 function 应用于 iterable 中每一项并输出其结果的迭代器。如果传入了额外的 iterable 参数,function 必须接受相同个数的实参并被应用于从所有可迭代对象中并行获取的项。当有多个可迭代对象时,最短的可迭代对象耗尽则整个迭代就将结束。
返回值是个 map,可以用 list 函数把它转换为 list。
reversed(seq):返回一个反向的 iterator。 seq 必须是一个具有 __reversed__() 方法的对象或者是支持该序列协议(具有从 0 开始的整数类型参数的 __len__() 方法和 __getitem__() 方法)。
sorted(iterable, *, key=None, reverse=False):根据 iterable 中的项返回一个新的已排序列表。具有两个可选参数,它们都必须指定为关键字参数。
key 指定带有单个参数的函数,用于从 iterable 的每个元素中提取用于比较的键 (例如 key=str.lower)。 默认值为 None (直接比较元素)。reverse 为一个布尔值。 如果设为 True,则每个列表元素将按反向顺序比较进行排序。
计算序列的长度、找出序列中的最大元素、找出序列中的最小元素、计算元素和。
format(value[, format_spec]):将 value 转换为 format_spec 控制的“格式化”表示。默认的 format_spec 是一个空字符串,它通常和调用 str(value) 的结果相同。
调用 format(value, format_spec) 会转换成 type(value).__format__(value, format_spec)。 ,所以实例字典中的 __format__() 方法将不会调用。
globals():返回表示当前全局符号表的字典。这总是当前模块的字典(在函数或方法中,不是调用它的模块,而是定义它的模块)。通过该字典,我们还可以访问指定变量,甚至如果需要,还可以修改它的值。
locals():更新并返回表示当前本地符号表的字典。 在函数代码块但不是类代码块中调用 locals() 时将返回自由变量。使用 locals() 函数获得所有局部变量组成的字典时,可以像 globals() 函数那样,通过指定键访问对应的变量值,但无法对变量值做修改。
hash(object):返回该对象的哈希值(如果它有的话)。哈希值是整数。它们在字典查找元素时用来快速比较字典的键。如果对象实现了自己的 __hash__() 方法,请注意,hash() 根据机器的字长来截断返回值。
一般数字和字符串可以求hash,其它类型则先使用str(object)函数转换为字符串再求hash。
help([object]):启动内置的帮助系统(此函数主要在交互式中使用)。如果没有实参,解释器控制台里会启动交互式帮助系统。
id(object):返回对象的“标识值”。该值是一个整数,在此对象的生命周期中保证是唯一且恒定的。两个生命期不重叠的对象可能具有相同的 id() 值。
input([prompt]):该函数从输入中读取一行,将其转换为字符串(除了末尾的换行符)并返回。当读取到 EOF 时,则触发 EOFError。
isinstance(object, classinfo):如果参数 object 是参数 classinfo 的实例或者是其 (直接、间接或 虚拟) 子类则返回 True。如果 classinfo 是类型对象元组(或由其他此类元组递归组成的元组),那么如果 object 是其中任何一个类型的实例就返回 True。
issubclass(class, classinfo):如果 class 是 classinfo 的 (直接、间接或 虚拟) 子类则返回 True。和 isinstance 略有区别,这第一个参数是类名。
Python 迭代功能的重要函数。
iter(object[, sentinel]):
返回一个 iterator 对象。根据是否存在第二个实参,第一个实参的解释是非常不同的。如果没有第二个实参,object 必须是支持迭代协议(有 __iter__() 方法)的集合对象,或必须支持序列协议(有 __getitem__() 方法,且数字参数从 0 开始)。如果有第二个实参 sentinel,那么 object 必须是可调用的对象。这种情况下生成的迭代器,每次迭代调用它的 __next__() 方法时都会不带实参地调用 object。
next(iterator[, default]):通过调用 iterator 的 __next__() 方法获取下一个元素。如果迭代器耗尽,则返回给定的 default,如果没有默认值则触发 StopIteration。
用于处理大型数据。
memoryview为支持buffer protocol的对象提供了按字节的内存访问接口,其间不会拷贝内存,有点类似于C中的指针。默认的bytes 和 bytearray支持buffer procotol。
函数memoryview(obj)返回由给定实参创建的“内存视图”对象。可以避免数组的频繁拼接和新对象的产生,提高效率,例如我们处理 socket:
def read(size):
ret = memoryview(bytearray(size))
remain = size
while True:
data = sock.recv(remain)
length = len(data)
ret[size - remain: size - remain + length] = data # 直接进行内存操作
if len(data) == remain:
break
remain -= len(data)
return ret
# 以上函数返回的结果用struct.unpack处理时,由于上述函数返回值是memoryview,可以提高struct.unpack的效率。
需要内存操作的时候应该更深入研究,更多讨论参考这里:参考1,参考2,参考3。
class object:返回一个没有特征的新对象。object 是所有类的基类。它具有所有 Python 类实例的通用方法。这个函数不接受任何实参。由于 object 没有 __dict__,因此无法将任意属性赋给 object 的实例。
open(file, mode=‘r’, buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None):打开 file 并返回对应的 file object。如果该文件不能打开,则触发 OSError。
打开模式:
上述的形态字符串都可以再加一个b字符,如rb、w+b或ab+等组合,加入b 字符用来告诉函数库打开的文件为二进制文件,而非纯文字文件。
要读取和写入原始字节,请使用二进制模式并不要指定 encoding。buffering 是一个可选的整数,用于设置缓冲策略。
print (value,…,sep=’’,end=’\n’,file=sys.stdout,flush=False):
value:值
sep:输出的各个值之间的分隔符,默认是空格
end: 结束后干啥,默认是换行
file: 输出到哪里,默认是stdout,也就是console
flush:是否刷新输出缓存,应该是和文件flush那种操作有关的
class property(fget=None, fset=None, fdel=None, doc=None):用于处理 getter、setter、deleter。可以直接用这个函数,也可以使用类似的装饰器。下面两个形式等价:
# 形式1
class C:
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = property(getx, setx, delx, "I'm the 'x' property.")
# 形式2
class C:
def __init__(self):
self._x = None
@property
def x(self): # 相当于 getter
"""I'm the 'x' property."""
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
# 使用
c = C()
c.x = 10 # 调用 setter
print(c.x) # 调用 getter
del c.x # 调用 deleter
class range(start, stop[, step])
range 实际上是一个不可变的序列类型,用于生成序列。range()返回range()对象,可以直接用list()/set()/tuple函数转为list/set/tuple。但是 dict 不行!
round(number[, ndigits]):返回 number 舍入到小数点后 ndigits 位精度的值。 如果 ndigits 被省略或为 None,则返回最接近输入值的整数。如果与两个倍数的距离相等,则选择偶数 (因此,round(0.5) 和 round(-0.5) 均为 0 而 round(1.5) 为 2)。
对于一般的 Python 对象 number, round 将委托给 number.__round__。
注意,这里存在陷阱,对于浮点数,由于存储精度的问题,有时候在边界附近四舍五入会出错。比如round(2.675,2) -> 2.67;round(2.6751, 2) -> 2.68。
class slice(start, stop[, step]):返回一个表示由 range(start, stop, step) 所指定索引集的 slice 对象。切片对象具有仅会返回对应参数值(或其默认值)的只读数据属性 start, stop 和 step。
a = [1,2,3]
sl = slice(0,3,2)
a[sl] # [1,3]
这在 NumPy 中有大用。
super([type[, object-or-type]]):返回一个代理对象,它会将方法调用委托给 type 的父类或兄弟类。这对于访问已在类中被重载的继承方法很有用。
super 有两个典型用例。 在具有单继承的类层级结构中,super 可用来引用父类而不必显式地指定它们的名称,从而令代码更易维护。 这种用法与其他编程语言中 super 的用法非常相似。第二个用例是在动态执行环境中支持协作多重继承。
更多介绍参考。
class type(object)、class type(name, bases, dict):传入一个参数时,返回 object 的类型。 返回值是一个 type 对象,通常与 object.__class__ 所返回的对象相同。
对于内置的数字类型int、float什么的,用type函数就能看到这些变量的类型属于其中的哪一个。
三个参数版本则动态定义一个类型!例如:
def new_method(self):
print('new_method')
NewClass = type('NewClass', (object,) {
'new_var':10, 'new_method':new_method})
vars([object]):返回模块、类、实例或任何其它具有 __dict__ 属性的对象的 __dict__ 属性。不带参数时,vars() 的行为类似 locals()。
'''
############### 数字 ##############
1. Python 整数的取值范围是无限的,当所用数值超过计算机自身的计算能力
时,Python 会自动转用高精度计算(大数计算)。
2. 整数的不同进制:
十进制:10
二进制:0b/0B 1100
八进制:0o/0O 12
十六进制: 0x/0X a
3. 数字分隔,通常每隔三个数字添加一个下划线,类似于英文数字中的逗号:
138_164_475_80
4. Python 只有一种小数类型,就是 float,其指数写法:
2.1E/e5 # 2.1*10^5
2.1E/e-5 # 2.1*10^(-5)
即使指数写法中,没有小数点,但是这也是浮点数!
5. 复数 complex,使用j或者J表示虚部:
a + bj
complex(a, b)
6. 布尔类型可以当做整数来对待,即 True 相当于整数值 1,False 相当于整
数值 0。e.g. True+1 = 2。
'''
更多信息使用 dir/help 查看。
'''
############## 字节串 ###############
1. Python bytes 类型用来表示一个字节串。bytes 只负责以字节序列的形式
(二进制形式)来存储数据,至于这些数据到底表示什么内容(字符串、数字、
图片、音频等),完全由程序的解析方式决定。
2. 字符串和 bytes 存在着千丝万缕的联系,我们可以通过字符串来创建
bytes 对象,或者说将字符串转换成 bytes 对象。
字符串->字节串 r'xxx'
bytes('xxx', encoding='UTF-8')
'xxx'.encode('UTF-8')
字节串->字符串 bytes_obj.decode('UTF-8')
3. 在 Python 中,有 2 种常用的字符串类型,分别为 str 和 bytes 类型,
其中 str 用来表示 Unicode 字符,bytes 用来表示二进制数据。str 类型和
bytes 类型之间就需要使用 encode() 和 decode() 方法进行转换。上面第2
点展示了如何用 encode、decode 进行转换。其格式为:
str.encode([encoding="utf-8"][,errors="strict"])
bytes.decode([encoding="utf-8"][,errors="strict"])
其中:
encoding : 'utf-8'、'UTF-8'、'gb2312'
errors : 'strict'、'ignore'、'replace'、'xmlcharrefreplace:'
'''
更多信息使用 dir/help 查看。
'''
############## 字符串 ###############
0. Python 字符串采用的是 Unicode[含UTF-8、UTF-16、UTF-32] 字符集,
默认应该是 UTF-8,sys 模块中的 getdefaultencoding() 函数可以获得默认
编码格式。ord() 和 chr() 函数可以在Unicode代码和字符之间转换,encode
、decode方法可以轻松改变编码格式。
1. 串内的引号除了转义,还可以通过外部单引号,内部双引号,或者反过来。
2. 前缀:
u'xxx' # 以Unicode字符来存储字符串,可以不带
r'xxx' # 原始字符串,不进行转义,原始字符串的结尾处不能是反斜杠
# 例如想表示D:\Program Files\Python 3.8\,可以:
# str1 = r'D:\Program Files\Python 3.8' '\\'
# Python 会自动将这两个字符串拼接在一起
b'xxx' # 转换为 bytes
f'xxx' # 格式化字符串, s = 'a='; a = 10.111;
# f'{s:4}{a:+010}' # a= +00010.111
# 这里控制浮点数不能用负号左对齐!
3. 格式化字符串:
【控制】
%d、%i 转换为带符号的十进制整数
%o 转换为带符号的八进制整数
%x、%X 转换为带符号的十六进制整数
%e 转化为科学计数法表示的浮点数(e 小写)
%E 转化为科学计数法表示的浮点数(E 大写)
%f、%F 转化为十进制浮点数
%g 智能选择使用 %f 或 %e 格式
%G 智能选择使用 %F 或 %E 格式
%c 格式化字符及其 ASCII 码
%r 使用 repr() 函数将表达式转换为字符串
%s 使用 str() 函数将表达式转换为字符串
【格式】
"%[+][-][0][10]d,%[10.2]f" % (10, 10)
# 对于字符串,只能使用[-]标志,和位数控制[10]这一项;对于小数,以
上三个标志可以同时存在;对于整数,指定左对齐时,在右边补 0 是没有效果
的。
# [-] 左对齐,默认右对齐
# [+] 强制数字带有符号
# [0] 宽度不足时补充 0
# [10] 指定宽度
# [10.2] 小数精度,.是必须存在的
# 还可以:
'{},{}'.format(1,2)
'{0},{1}'.format(1,2)
class CV:
def __init__(self):
self.value = 10
obj = CV()
'{name}{1[0]}{2.value}'.format(name=liu, [1,2], obj)
f'...'
'{name}{age}'.format(**person_dict),其中 person_dict = {'name':'lisa', 'age':50}
# format 的语法非常复杂,字符串中的格式控制详细查看:http://c.biancheng.net/view/4301.html
4. 转义:
在 Python 中,一个 ASCII 字符除了可以用它的实体(也就是真正的字
符)表示,还可以用它的编码值表示(转义),转义字符以\0或者\x开头,以\0
开头表示后跟八进制形式的编码值,以\x开头表示后跟十六进制形式的编码值。
ASCII 排序 0~31 的没有实体字符,有特殊的转义符号:
\n,将光标位置移到下一行开头
\r,将光标位置移到本行开头
\t,水平制表符
\a,蜂鸣器响铃
\b,退格
\\,\',\",特殊符号
5. +/*、字符串拼接、str/repr
+用于字符串拼接,*用于字符串重复。对于字符串常量,可以用如下方法实
现拼接:str = "字符串1" "字符串2"。注意这只适用于常量。
str() 和 repr() 函数虽然都可以将数字转换成字符串,但它们之间是有
区别的:str() 用于将数据转换成适合人类阅读的字符串形式;repr() 用于将
数据转换成适合解释器阅读的字符串形式,也就是使用引号将字符串包围起来。
6. len
len(str) # 字符串长度
len(str.encode()) # 字符串UTF-8字节数
7. list = str.split(sep,maxsplit)
字符串分割。sep:分割符号,默认为空格,maxsplit:最大分割次数。
连续的空格视为一个空格。
8. newstr = str.join(iterable)
合并字符串。str表示合并时候的分隔符,例如:
s = ['a', 'b']
' '.join(s) # a b
9. str.count(sub[,start[,end]])
检索 sub 在start - end间出现的次数。
10. str.find(sub[,start[,end]])
检索字符串中是否包含目标字符串,如果包含,则返回第一次出现该字符串
的索引;反之,则返回 -1。Python 还提供了 rfind() 方法,与 find() 方
法最大的不同在于,rfind() 是从字符串右边开始检索。
11. str.index(sub[,start[,end]])
同 find() 方法类似,index() 方法也可以用于检索是否包含指定的字符
串,不同之处在于,当指定的字符串不存在时,index() 方法会抛出异常:
ValueError。字符串变量还具有 rindex() 方法,其作用和 index() 方法类
似,不同之处在于它是从右边开始检索。
12. str.ljust(width[, fillchar])、rjust 和 center
字符串对齐,填充指定的字符,并对齐。fillchar默认是空格。
13. str.startswith(sub[,start[,end]])、endwith
检索字符串是否以指定字符串开头、结尾。
14. str.title()、lower()、upper()
返回:单词首字母大写、全小写、全大写版本。
15. str.strip([chars])、lstrip、rstrip
[chars]默认是所有的特殊符号:空格、换行等等。
删除字符串左右两侧/某一侧的空格和特殊字符。
16. 字符串比较
str1 > str2 # 从str1[0]和str2[0]比起,按照 ascii/unicode 码比
较,如果不等,那么返回结果,如果相等,那么继续向后判断。
'''
Python 序列(Sequence)是指按特定顺序依次排列的一组数据,它们可以占用一块连续的内存,也可以分散到多块内存中。Python 中的序列类型包括列表(list)、元组(tuple)、字典(dict)和集合(set)。
列表(list)和元组(tuple)比较相似,可以理解为数组吧,但是列表是可以修改的,而元组是不可修改的;字典(dict)和集合(set)存储的数据都是无序的,每份元素占用不同的内存,其中字典元素以 key-value 的形式保存。字典的键值,集合的元素都不能重复,重复的会自动覆盖,且必须是不可变的类型。实际上还有 frozenset,这个集合不允许添加、删除元素。
各种序列都支持类似的操作方法,但是注意集合和字典不支持索引、切片、相加和相乘操作。序列可以任意嵌套,其中 tuple/frozenset 和是不可变的,其它都是可变的。此外,还可以直接与赋值运算符取出序列的所有元素:
a = 1, 2 # (1, 2)
b = {
'1':'a', '2':'b'}
c,d = a # 1, 2
c,d = b # '1', '2'
几种数据结构对应的括号:
list : []
tuple : (),如果单个元素,应该写成(e,),逗号必须有
dict: {key:val},
set: {},必须有元素,否则被视为空字典
和C不同 Python 支持负数索引,-1表示最后一个元素。切片的语法则为sname[start : end : step],这显得很灵活。start默认为0,end默认为-1,step默认为1。后一个冒号可以省略。
序列加法为拼接,乘一个整数为延长,in语句可以判断序列中是否存在某个元素,not in 和 in 是相反的。
seq1 = [i for i in range(5)] # [1,2,3,4,5]
seq1[:],seq1[::],seq1[:-1] # [1,2,3,4,5]
seq1[1:4:2] # [2,4]
seq2 = seq1*2 # [1,2,3,4,5,1,2,3,4,5]
del seq2[0] # [2,3,4,5,1,2,3,4,5]
seq1+seq2 # [1,2,3,4,5,2,3,4,5,1,2,3,4,5]
1 in seq1: # True
del seq2 # None
itm指元素,idx指索引,obj也指元素(有时为了表明可以是任何对象)。可以使用 dir 或者 help 查看所有的方法、属性和用法。
支持各种对象,且每个元素类型可以不同。列表的操作:
(1)查找,修改:①切片操作可以获取,修改数据;l[:2]=[…],如果step=1,那么元素数量不需要一致,step > 1,则必须一致;②l.index(obj, start=0, end=-1)查找元素首次出现的idx;③l.count(obj),查找元素个数。
(2)加入元素:①直接使用加号;②l.append(obj),尾部追加,obj可以是任何对象,且被视为一个元素;③l.extend(obj),尾部追加,obj可以是任何对象,但是如果是列表、元组等,会被视为多个元素;④l.insert(idx, obj),中间插入,insert() 会将 obj 插入到 listname 列表第 index 个元素的位置,无论 obj 是啥都看作整体;⑤l[idx:idx] = […],空切片插入法;⑥没有 push 函数。
(3)删除元素:pop(idx=-1)、remove(itm)、clear()、del l[start : end : step]。分别是删除指定索引元素、删除指定元素、清空、删除切片内容。
(4)拷贝:copy方法,数值类型深拷贝,其余浅拷贝。
注意,+和*实际上生成了一个新的list,其它操作则没有。
tuple也是类似地,但是tuple一旦创建就不可修改,所以所有修改操作都不行,但是+例外,因为它创建了一个新的对象。如果tuple中的元素本身是list这种可变的,那么我们可以间接修改这个元素的值。
对于一些静态变量(比如元组),如果它不被使用并且占用空间不大时,Python 会暂时缓存这部分内存。这样的话,当下次再创建同样大小的元组时,Python 就可以不用再向操作系统发出请求去寻找内存,而是可以直接分配之前缓存的内存空间,这样就能大大加快程序的运行速度。
python -m timeit 'x=(1,2,3,4,5,6)'
# 20000000 loops, best of 5: 9.97 nsec per loop
python -m timeit 'x=[1,2,3,4,5,6]'
# 5000000 loops, best of 5: 50.1 nsec per loop
此外,元组可以在映射(和集合的成员)中当做“键”使用,而列表不行。
Python 字典(dict)是一种无序的、可变的序列,它的元素以“键值对(key-value)”的形式存储。字典类型有时也称为关联数组或者散列表(hash)。它是通过键将一系列的值联系起来的,这样就可以通过键从字典中获取指定项,但不能通过索引来获取。
字典中的键必须唯一、字典中的键必须不可变,因此各种常量以及 tuple 都是可以的。dict 的创建方法有:①{}法;②d.fromkeys(list,value=None),其中 list 包含所有键,value 是默认值;③d = dict(a1=1, _b=‘b’) # 这种方法key只能是字符串类型的,且不能加引号,且不能数字开头;d = dict([(‘key1’, 1), [2, 2]])或者d = dict(([1,2],(1,2)))或者d = dict(zip([1,2], (1,2)))。
访问/修改/增删查:①d[key] # 获得,修改,添加,删除(del);②d.get(key[,default=None]) # 找不到返回 default;d.setdefault(key, defaultvalue) # 也是查找,但是如果不存在就创建它,使用 defaultvalue,并返回 defaultvalue;③查找是否存在某个键值可以使用in/not in语句;④d.update({})或d.update([(key,val), [key,val]]) # 一次修改多个元素的值;⑤d.pop(key)/d.popitem() # 删除/随机删除(实际上是底层存储的最后一个,但是dict没有顺序)某个元素。
其它方法,使用 dir(dict) 可以查看:
d = dict()
d.keys() # 返回字典中的所有键,类型 dict_keys
d.values() # 返回字典中所有键对应的值,类型 dict_values
d.items() # 返回字典中所有的键值对,类型 dict_items
# 这些函数返回值不是常见类型,Python 3.x 不希望用户直接操作这几个方法
# 的返回值。但是可以用 list/tuple/set 函数强行转换为我们熟悉的数据;for 语句也支持。
for k,v in d.items():
print('key:',k,' val:',v)
d_ = d.copy() # 返回一个字典的拷贝,键都是深拷贝,值则数值进行深
# 拷贝,其余都是浅拷贝。
如果不调用 dict 的函数,那么对 dict 的各种操作都是操作其键:
d = {
'1':'a', '2':'b'}
list(d) # ['1', '2']
a, b = d # '1', '2'
a, b = d.values() # 'a', 'b'
a, b = d.items() # ('1', 'a'), ('2', 'b')
集合的元素必须是不可变的类型,且分为两种 set 可以增删、frozenset 不可以,集合基本就是数学上的集合。
集合 set/frozenset 在上面有了初步的介绍,这里再补充一些。创建方法类似 dict,但是注意空集合不能用{},必须用set();集合的访问则主要使用 for 语句,不能使用索引;集合最常用的操作是向集合中添加、删除元素,以及集合之间做交集、并集、差集等运算。
s.add(ele) # ele 必须不可变
s.remove(ele) # 不存在报错 KeyError
s.discard(ele) # 类似 remove,但是不报错
集合运算(set1={1,2,3} 和 set2={3,4,5}):
此外,还涉及较多的函数,使用 dir 可以查看。上述操作符其实都有函数支撑,还有很多类似 dict 操作的函数。参考这里。
面向对象编程(Object-oriented Programming,简称 OOP),是一种封装代码的方法。类和对象是 Python 的重要特征,相比其它面向对象语言,Python 很容易就可以创建出一个类和对象。同时,Python 也支持面向对象的三大特征:封装、继承和多态。
class ClassNameDescriptor:
'''
本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方
式。类属性的描述符,描述符是 Python 中复杂属性访问的基础,它在内部
被用于实现 property、方法、类方法、静态方法和 super 类型。
描述符基于__set__(self, obj, type=None)、__get__(self, obj,
value) 和__delete__(self, obj)。其中 __set__ 也叫 setter,
__get__ 也叫 getter。实现了 setter 和 getter 方法的描述符类被称
为数据描述符,如果只实现了 getter 方法,则称为非数据描述符。
每次使用类对象.属性(或者 getattr(类对象,属性值))的调用方式时,
都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:
①验证该属性是否为类实例对象的数据描述符;
②如果不是,就查看该属性是否能在类实例对象的 __dict__ 中找到;
③最后,查看该属性是否为类实例对象的非数据描述符。
查找有描述符的属性的时候,都会调用描述符的 __get__() 方法,并返回
它的值;同样,每次在对该属性赋值时,也会调用 __set__() 方法。当每
次使用 del 类对象.属性(或者 delattr(类对象,属性))语句时,都会
调用描述符的 __delete__ 方法。
除了使用描述符类自定义类属性被调用时做的操作外,还可以使用
property() 函数或者 @property 装饰器。实际上,@property装饰器就
是通过描述符的方式实现的。
'''
def __init__(self, name):
self.name = name
def __get__(self, obj, obj_type):
# getter
# 对 name 进行预处理后,返回值
return self.name
def __set__(self, obj, val):
# setter
# 可以判断 val 是否符合要求,并预处理
self.name = val
def __delete__(self, obj):
# 删除大规模数据
pass
class ClassDemoParrent1(dict):
class_name1 = 'ClassDemoParrent1'
def __init__(self, instance_name):
self.instance_name = instance_name
self.show()
class ClassDemoParrent2:
'''
如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类
是Python 中所有类的父类,即要么是直接父类,要么是间接父类)。我们
实现功能的时候,可以考虑继承 Python 的基本内置类型,或者
collections 模块中的很多有用的容器,这样提高了我们的开发效率。
'''
class_name2 = 'ClassDemoParrent2'
def __init__(self, instance_name):
self.instance_name = instance_name
self.show()
class ClassDemo(ClassDemoParrent1, ClassDemoParrent2):
'''
继承:
ClassDemoParrent1、ClassDemoParrent2 是父类,ClassDemo 是
子类,Python 支持多继承机制。子类拥有父类所有的属性和方法,即便该
属性或方法是私有的。子类还会覆盖掉父类的同名属性和方法。使用多继承
经常需要面临的问题是,多个父类中包含同名的类方法。对于这种情况,
Python 的处置措施是:根据子类继承多个父类时这些父类的前后次序决
定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。这是
由方法解析顺序(Method Resolution Order,简称 MRO)决定的,目前
Python 的 MRO 采用 C3 算法。可以使用类的 __mro__ 属性或者 mro()
方法查看有关类的 MRO。
构造函数在 Python 继承中和普通函数没有区别,因此子类实现了
__init__ 方法,那么父类的 __init__ 方法就会被覆盖。我们可以手动调
用父类的方法,无论是构造函数还是普通函数(super() 相当于 super(子
类名, self)):
在子类中:
①super().方法名()
会自动传入 self 参数,但是只能调用第一个父类。
②父类名.方法名(self)
必须手动把子类的 self 参数传入,但是可以调用任何一个
父类中的方法。
在子类外:
① 子类的实例名 = 子类名()
父类名.方法名(子类的实例名)
super 的使用有很多陷阱,为了避免,需要:尽可能避免使用多继承,可以
使用一些设计模式来替代它;super 的使用必须一致,即在类的层次结构
中,要么全部使用 super,要么全不用;调用父类时应提前查看类的层次结
构(针对三方库),也就是使用类的 __mro__ 属性或者 mro() 方法查看有
关类的 MRO。
最后,注意多个父类中的属性不要重名!如果不是刻意的,子类的属性也不
要和父类属性重名,一定注意!!!
'''
'''
类属性/类变量,所有类的实例化对象都同时共享类变量,
可以用类名调用/修改/添加:ClassDemo.class_name,而通过类对象对
类变量赋值,其本质将不再是修改类变量的值,而是在给该对象定义新的
实例变量。类中,实例变量和类变量可以同名,通过类调用的是类变量,
通过实例调用的是实例变量。
这个类变量还使用了数据描述符,虽然描述符啥都没做~~~
'''
class_name = ClassNameDescriptor('ClassDemo')
# 还可以使用 property() 函数或者 @property 装饰器封装属性操作
'''property()方法
__class_name = 'ClassDemo'
def class_name_getter(self):
# 处理
return self.__class_name
def class_name_setter(self, val):
# 处理
self.__class_name = val
def class_name_deleter(self):
# 处理
pass
class_name = property(class_name_getter, class_name_setter, class_name_deleter, 'class_name')
'''
'''@property 方法
__class_name = 'ClassDemo'
@property
def class_name(self):
# 处理
return self.__class_name
@class_name.setter
def class_name(self, val):
# 处理
self.__class_name = val
@class_name.deleter
def class_name(self):
# 处理
pass
'''
public_proterty = 'public_proterty'
_private_property = '_private_property'
__private_property = '__private_property'
'''
无论是方法还是属性,甚至是类本身/函数,控制其访问权限的方法为:
①不以下划线开头:公有的,随便访问
②双下划线开头:私有的,外部不能访问
实际上,Python 是把它改了个名字,把所有双下划线开头的都改成了
:_类名__属性名/方法名的格式,我们非得在外部调用也是可以的
③单下划线开头:私有的,但是只是约定的,实际从外部也能访问,另外:
from xxx_module import * # 单下划线开头的无法导入
import xxx_module # 可以通过
# xxx_module.单下划线开头的 这种方式访问
'''
__slots__ = ('class_info', 'instance_info')
'''
我们可以动态添加属性(类属性,实例属性)和方法(实例方法、静态方法和
类方法),与此同时,Python 也提供了 __slots__ 属性,用来限制为实例
对象动态添加属性和方法(无法限制动态地为类添加属性和方法。)
如果子类也要限制外界为其实例对象动态地添加属性和方法,必须在子类中
设置 __slots__ 属性,那么子类实例对象允许动态添加的属性和方法,是
子类中 __slots__ 属性和父类 __slots__ 属性的和。
'''
# 构造函数,不定义的话,Python 自动添加默认构造函数:
# __init__(self)。self 是类实例指针,其实不是必须叫 self 这个名
# 字,只是习惯用这个。self 相当于 C++ 的 this。
def __init__(self, instance_name:str):
# 调用父类构造函数
ClassDemoParrent1.__init__(self, instance_name)
ClassDemoParrent2.__init__(self, instance_name)
# 局部变量,只在函数内部使用。
instance_name_ = instance_name
# 实例属性/实例变量,只能通过对象名访问,无法通过类名访问。
# 实例变量并不共享。可以用对象名调用/修改/添加实例变量。
self.instance_name = instance_name_
# 用类的实例对象访问类成员的方式称为绑定方法,而用类名调用类成员的
# 方式称为非绑定方法。
def instance_method(self):
# 实例方法,类的构造方法理论上也属于实例方法,只不过它比较特
# 殊。实例方法最大的特点就是,它最少也要包含一个 self 参数,
# 实例方法通常会用类对象直接调用。Python 也支持使用类名调用实
# 例方法,但此方式需要手动给 self 参数传值。
pass
instance_method_2 = lambda self: print(self.class_name)
# 实例方法的另外一种定义方式,在执行:
# class_demo_instance.instance_method_2() 时,也会自动传入
# self。实际上,类中可以任意放置可执行代码, 在解释器加载类名
# ClassDemo 时,这个语句就会执行,比如这里的 lambda。
@classmethod
def class_method(cls):
# 类方法,Python 类方法和实例方法相似,它最少也要包含一个参
# 数,只不过类方法中通常将其命名为 cls,Python 会自动将类本身
# 绑定给 cls 参数(注意,绑定的不是类对象)。cls 参数的命名也
# 不是规定的(可以随意命名),只是 Python 程序员约定俗称的习
# 惯而已。类方法推荐使用类名直接调用,当然也可以使用实例对象来
# 调用(不推荐)。
pass
@staticmethod
def static_method(self):
# 静态方法,静态方法没有类似 self、cls 这样的特殊参数,因此
# Python 解释器不会对它包含的参数做任何类或对象的绑定,因此类
# 的静态方法中无法调用任何类属性和类方法。静态方法的调用,既可
# 以使用类名,也可以使用类对象。在实际编程中,几乎不会用到类方
# 法和静态方法。
pass
@staticmethod
def class_demo_dynamicadded_static_method():
print(class_demo_dynamicadded_static_method)
ClassDemo.add_method = class_demo_dynamicadded_static_method
# 动态添加类方法,不受__slots__限制
c = ClassDemo('first_class_demo')
c.instance_info = 'instance_info'
'''
Python 还支持使用 type 函数动态创建类,具体可以参考 type 函数。事实
上,Python 解释器底层就是使用 type 函数创建类的。
'''
'''
Python 的多态不需要 C++ 中的虚函数,因为 Python 的一个数组本来就能随意保存任何对象,不像 C++ 那么死板。
'''
凡是以双下划线 “__” 开头并结尾命名的成员(属性和方法),都被称为类的特殊成员(特殊属性和特殊方法)。这些东西并不是私有的,可以从外部访问!我们也能自己定义双下划线开头并结尾的属性/方法,这些也不是私有的。
负责创建类实例的静态方法,它无需使用 staticmethod 装饰器修饰,且该方法会在 __init__() 之前被调用。一般情况下,覆写 __new__() 的实现将会使用合适的参数调用其超类的 super().__new__(),并在返回之前修改实例。__new__()的第一个参数 cls 值得是类。
class demoClass:
instances_created = 0
def __new__(cls,*args,**kwargs): # 实例计数功能
print("__new__():",cls,args,kwargs)
instance = super().__new__(cls)
instance.number = cls.instances_created
cls.instances_created += 1
return instance
def __init__(self,attribute):
print("__init__():",self,attribute)
self.attribute = attribute
class nonZero(int):
'''
__new__() 通常会返回该类的一个实例,但有时也可能会返回其他类的实
例,如果发生了这种情况,则会跳过对 __init__() 方法的调用。
'''
def __new__(cls,value): # 禁止0的整数
return super().__new__(cls,value) if value != 0 else None
def __init__(self,skipped_value):
print("__init__()")
super().__init__()
如无必要,尽量不要用它,但是MetaClass大量使用了这个方法。
构造函数,初始化对象,一般在__new__调用。
执行 print(obj) 等同于执行 print(obj.__repr__()),默认情况下,__repr__() 会返回和调用者有关的 “类名+object at+内存地址”信息,改写这个可以定制我们希望打印的信息。
关于 repr、str、format 参考这里。
无论是手动销毁(del),还是 Python 自动帮我们销毁,都会调用 __del__() 方法。当程序中有其它变量引用该实例对象时,即便手动del,该方法也不会立即执行。这和 Python 的垃圾回收机制的实现有关。Python 采用自动引用计数(简称 ARC)的方式实现垃圾回收机制。
如果我们重写子类的 __del__() 方法(父类为非 object 的类),则必须显式调用父类的 __del__() 方法,这样才能保证在回收子类对象时,其占用的资源(可能包含继承自父类的部分资源)能被彻底释放。
Python 中的 dir() 函数用来查看某个对象拥有的所有的属性名和方法名,该函数会返回一个包含有所有属性名和方法名的有序列表。dir() 函数的内部实现,其实是在调用参数对象 __dir__() 方法的基础上,对该方法返回的属性名和方法名做了排序。用于类或者对象均可。
无论是类属性还是实例属性,都是以字典的形式进行存储的,其中属性名作为键,而值作为该键对应的值。为了方便用户查看类中包含哪些属性,Python 类提供了 __dict__ 属性。该属性可以用类名或者类的实例对象来调用,用类名直接调用 __dict__,会输出该由类中所有类属性组成的字典;而使用类的实例对象调用 __dict__,会输出由类中所有实例属性组成的字典。
对于具有继承关系的父类和子类来说,父类有自己的 __dict__,同样子类也有自己的 __dict__,它不会包含父类的 __dict__。借助由类实例对象调用 __dict__ 属性获取的字典,可以使用字典的方式对其中实例属性的值进行修改,单无法通过类似的方式修改类属性的值。
Python 为所有类都提供了一个 __bases__ 属性,通过该属性可以查看该类的所有直接父类,该属性返回所有直接父类组成的元组。显然,如果在定义类时没有显式指定它的父类,则这些类默认的父类是 object 类。Python 还为所有类都提供了一个 __subclasses__() 方法,通过该方法可以查看该类的所有直接子类,该方法返回该类的所有子类组成的列表。这两个只能用于类,不能用于对象。
该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用。Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。对于可调用对象,实际上“名称()”可以理解为是“名称.__call__()”的简写。
实际上,我们用 def 定义的函数,本质上是 function 类的子类,之所以能调用,也是因为这个类实现了 __call__,所以所有函数我们都可以用函数名.__call__(参数)调用。
使用 hasattr 可以判断类是否有某个名字的成员,但是无法判断这个成员是属性还是方法,我们可用结合 __call___ 判断:
hasattr(list.append, ‘__call__’)
所谓重载运算符,指的是在类中定义并实现一个与运算符对应的处理方法,这样当类对象在进行运算符操作时,系统就会调用类中相应的方法来处理。
上面介绍的一些类的特殊成员也是运算符重载的,下面给出列表:
关于描述符在上一个小节已经有了介绍,这里介绍下__getattr__和__getattribute__的区别:在获到对象属性时,__getattribute__()是一定会被调用的,无论属性存不存在,首先都会调用这个方法;如果__getattribute__()找不到该属性,就会再调用__getattr__()这个方法,可以通过在这个方法里来设置属性不存在时的默认值。使用上面的 getattr() 方法获取属性时,也是同样的调用关系,只不过只有在 getattr() 带第三个参数作为默认值时,才会调用 __getattr__()方法。
此外,如果实现了__len__、__getitem__、__contains__三个就成为了不可变序列;再额外实现 __setitem__ 和 __delitem__ 就变成了可变序列了。注意,我们在这些函数中,应该先判断参数是否是整数或者slice对象。
就是枚举嘛。只需要继承 Enum 就好。枚举的值通常从 1 开始;枚举类不能用来实例化对象;且枚举类成员之间不能比较大小,但可以用 == 或者 is 进行比较是否相等;枚举类中各个成员的值,不能在类的外部做任何修改;枚举类还提供了一个 __members__ 属性,该属性是一个包含枚举类中所有成员的字典;枚举类中各个成员必须保证 name 互不相同,但 value 可以相同,但是此时会出现别名现象,因此需要使用@unique检查。
from enum import Enum, unique
# 定义方式一
@unique # 这会检查是否有重复的值
class Color(Enum): # 必须继承 Enum
# 为序列值指定value值
red = 1
green = 2
blue = 3
# 定义方式二
# Color = Enum("Color",('red','green','blue'))
# 调用枚举成员的三种方法
Color.red # Color(1) # Color['red']
# 取枚举名和值
Color.red.name
Color.red.value
# 遍历枚举:一
for color in Color:
print(color.name, "->", color.value)
# 遍历枚举:二
for name, member in Color.__members__.items():
print(name, "->", member.value)
# 比较
Color.red == Color.green
Color.red is Color.green
元类、装饰器、类装饰器都可以归为元编程。
MetaClass元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改。使用元类的主要目的就是为了实现在创建类时,能够动态地改变类中定义的属性或者方法。比如用于反序列化这就非常有用。
MetaClass要求:必须显式继承自 type 类;类中需要定义并实现 __new__() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 __new__() 方法会自动被执行,用来修改新建的类。注意,普通类的 __new__() 返回的是实例,MetaClass的__new__() 返回的是类。
例子:
#定义一个元类
class FirstMetaClass(type):
# cls代表动态修改的类
# name代表动态修改的类名
# bases代表被动态修改的类的所有父类
# attr代表被动态修改的类的所有属性、方法组成的字典
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个name属性
attrs['name'] = "C语言中文网"
attrs['say'] = lambda self: print("调用 say() 实例方法")
return super().__new__(cls,name,bases,attrs)
#定义类时,指定元类
class CLanguage(object, metaclass=FirstMetaClass):
pass
clangs = CLanguage()
print(clangs.name)
clangs.say()
MetaClass 仅仅是给小部分 Python 开发者,在开发框架层面的 Python 库时使用的。而在应用层,MetaClass 往往不是很好的选择。
应用场景:身份认证、
直接给出个例子吧:
import functools
def use_decorator(use_or_not): # 装饰器控制
# 层,这一层不传入函数作为参数,而是传入我们写入的参数
# use_or_not 属于自由变量
def decoratorA(fn):
if use_or_not:
@functools.wraps(fn) # 修改一些
# decorator 的特殊成员,比如不让 fn.__name__ 变为 decoratorA
def decoratorA_inner(*args, **kwargs):
print('decoratorA')
return fn(*args, **kwargs)
return decoratorA_inner
else:
return fn
return decoratorA
def decoratorB(fn):
@functools.wraps(fn)
def decoratorB_inner(*args, **kwargs):
print('decoratorB')
return fn(*args, **kwargs)
return decoratorB_inner
'''以下操作等价于
func = decoratorA(decoratorB(func))
因此,要求装饰器必须返回和原来参数/返回值相同的函数
'''
@use_decorator(True) # 这里相当于@(use_decorator(True)) =
# @decoratorA,但是能传递自由变量进去
@decoratorB
def func(name, age):
print(name, '->', age)
func('onetree', 25)
'''输出结果
decoratorA
decoratorB
onetree -> 25
'''
借助异常处理机制,甚至在程序崩溃前也可以做一些必要的工作,例如将内存中的数据写入文件、关闭打开的文件、释放分配的内存等。Python 异常处理机制会涉及 try、except、else、finally 这 4 个关键字,同时还提供了可主动使程序引发异常的 raise 语句。在整个异常处理结构中,只有 try 块是必需的,但是也不能只使用 try 块,要么使用 try except 结构,要么使用 try finally 结构。
异常处理不仅仅能够管理正常的流程运行,还能够在程序出错时对程序进行必要的处理。大大提高了程序的健壮性和人机交互的友好性。
所有的异常都是 Exception 的派生类,常见异常有:AssertionError(assert语句)、AttributeError(试图访问的对象属性不存在)、IndexError(索引越界)、KeyError(字典键不存在)、NameError(访问一个未声明的变量时)、TypeError(不同类型数据之间的无效操作)、ZeroDivisionError。详细的继承关系有:
不要过度使用异常!不要使用过于庞大的 try 块!!不要忽略捕获到的异常!!!
异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝不要使用异常处理来代替正常的业务逻辑判断。另外,异常机制的效率比正常的流程控制效率差,所以不要使用异常处理来代替正常的程序流程控制。
'''
自定义一个异常类,通常应继承自 Exception 类(直接继承),当然也可以继承自那些本身就是从 Exception 继承而来的类(间接继承 Exception)。用户自定义的异常需要用户自己决定什么时候抛出,使用 raise 手动抛出。
只要自定义的类继承自 Exception,则该类就是一个异常类,至于此类中包含的内容,并没有做任何规定。
'''
try:
'''
可能产生异常的代码块。如果发生异常的代码不在 try 语句块中,那么程
序会直接退出,因此需要谨慎处理。
'''
pass
except (Error1, Error2, ... ) [as e]:
'''
捕获 Error1、Error2 等多种类型的异常(包括这些类的派生类),并用变
量 e 指向异常。当一个 try 块配有多个 except 块时,这些 except 块
应遵循这样一个排序规则,即可处理全部异常的 except 块(参数为
Exception,也可以什么都不写)要放到所有 except 块的后面,且所有父
类异常的 except 块要放到子类异常的 except 块的后面。
e.args: 异常的错误编号和描述字符串
str(e): 异常信息,但不包括异常信息的类型
repr(e): 较全的异常信息,包括异常信息的类型
捕获异常时,有 2 种方式可获得更多的异常信息,分别是:
①使用 sys 模块中的 exc_info 方法
模块 sys 中,有两个方法可以返回异常的全部信息,分别是 exc_info()
和 last_traceback(),这两个函数有相同的功能和用法。exc_info() 方
法会将当前的异常信息以元组的形式返回,该元组中包含 3 个元素,分别
为 type、value 和 traceback,type 表示异常类型对应的类,value
表示捕获到的异常实例,traceback 是一个 traceback 对象。要查看
traceback 对象包含的内容,需要先引进 traceback 模块,然后调用
traceback 模块中的 print_tb 方法,并将 sys.exc_info() 输出的
traceback 对象作为参数参入。
②使用 traceback 模块中的相关函数
除了使用 sys.exc_info() 方法获取更多的异常信息之外,还可以使用
traceback 模块,该模块可以用来查看异常的传播轨迹,追踪异常触发的
源头。使用 traceback 模块查看异常传播轨迹,首先需要将 traceback
模块引入,该模块提供了如下两个常用方法:
traceback.print_exc([limit[, file]]):将异常传播轨迹信息输出到
控制台或指定文件中,limit 表示传播的层数,file 表示文件名。
format_exc():将异常传播轨迹信息转换成字符串。
异常的抛出使用 raise 语句,有三种使用方法:
①raise: 该语句引发当前上下文中捕获的异常(比如在 except 块中),
或默认引发 RuntimeError 异常。
②raise 异常类名称: 引发<异常类名称>类型的异常。
③raise 异常类名称(描述信息字符串): 同②,附带描述信息。
'''
pass
except (Error3, Error4, ... ) [as e]:
'''
同上。
'''
pass
except/except Exception:
'''
剩余所有类型的异常都在这里处理。
'''
pass
else:
'''
try 没有捕获到任何异常的时候才会执行,否则不执行。
else 必须和 try except 搭配使用。
'''
pass
finally:
'''
finally 块必须位于所有的 except 块之后。
无论 try 块是否发生异常,最终都要进入 finally 语句,并执行其中的
代码块。例如,当 try 块中的程序打开了一些物理资源(文件、数据库连
接等)时,由于这些资源必须手动回收,而回收工作通常就放在 finally
块中。即便当 try 块发生异常,且没有合适和 except 处理异常时,
finally 块中的代码也会得到执行。
如果程序中运行了强制退出 Python 解释器的语句(如 os._exit(1)
),则 finally 语句将无法得到执行。如果 Python 程序在执行 try
块、except 块包含有 return 或 raise 语句,则 Python 解释器执行
到该语句时,如果找到 finally 块,系统立即开始执行 finally 块,只
有当 finally 块执行完成后,系统才会再次跳回来执行 try 块、except
块里的 return 或 raise 语句。如果在 finally 块里也使用了 return
或 raise 等导致方法中止的语句,finally 块己经中止了方法,系统将不
会跳回去执行 try 块、except 块里的任何代码。
'''
输出调试信息,推荐使用 logging 模块而不是直接使用 print,这样我们可以更灵活地控制调试等级,规范输出,并便于管理输出代码。
除了异常,logging,还有 assert、调试器、print方法。
模块,英文为 Modules,任何 Python 程序都可以作为模块。
容器:对数据的封装
函数:对 Python 代码的封装
类:对方法和属性的封装
模块:对代码更高级的封装,把能够实现某一特定功能的代码编写在同一个 .py 文件中,并将其作为一个独立的模块,既可以方便其它程序或脚本导入并使用,同时还能有效避免函数名和变量名发生冲突。
# 模块的调用规则为:模块名.函数,模块看起来和命名空间类似。
# 放在同一个目录下的 .py 文件就能在别的文件中 import 进去。
'''0. 模块搜索顺序
'''
'''1. import 语句
语法:
import 模块名1 [as 别名1], 模块名2 [as 别名2],…
from 模块名 import 成员名1 [as 别名1],成员名2 [as 别名2],…
form 模块名 import * # 不推荐
但是如果模块名含有空格/数字开头,则无法用 import 语句导入,此时:
使用内置函数 __import__('模块名')即可
import的搜索路径:
①在当前目录,即当前执行的程序文件所在目录下查找;
②到 PYTHONPATH(环境变量)下的每个目录中查找;
③到 Python 默认的安装目录下查找。
以上所有涉及到的目录,都保存在标准模块 sys 的 sys.path 变量中。
因此,对于我们自己编写的模块,我们可以设置系统PYTHONPATH,或者在
sys.path 中添加自定义模块的绝对路径
sys.path.append('my_module_path')。或者把它复制到能找到的位
置去,通常来说,我们默认将 Python 的扩展模块添加在
lib\site-packages 路径下,它专门用于存放 Python 的扩展模块
和包。
导入的本质(关于__init__.py还得细化):
文件夹也当作包处理,比如可以 import my_package_dir.my_package。
导入包:import my_package 等于执行了 my_package 目录下的
__init__.py该文件,或者执行 my_package.py 文件。包也是模块!
默认地,和 __init__.py 同级的所有 .py 都是 my_package 子模块,
也就是可以通过 import my_package.同级文件名,__init__.py 是空的
就好,当然也可以在 __init__.py 中进行一些 import 或者初始化。
当我们向文件导入某个模块时,导入的是该模块中那些名称不以下划线(单
下划线“_”或者双下划线“__”)开头的变量、函数和类。因此,如果我们不
想模块文件中的某个成员被引入到其它文件中使用,可以在其名称前添加下
划线。
另外,我们可以通过在模块文件中设置 __all__ 变量,当其它文件以
“from 模块名 import *”的形式导入该模块时,该文件中只能使用
__all__ 列表中指定的成员。没有__all__就没法import *,只能指定
具体的东西。__all__只针对星号!
使用 dir(module), module.__all__可以查看包的内容。
import string
print([e for e in dir(string) if not e.startswith('_')])
无论是module还是其中的函数、类等,都能用__doc__和help函数查看头部的帮助信息。
'''
'''2. 定义模块
模块就是 .py 文件
模块中的测试代码写在:
if __name__ == '__main__':
# 测试代码
中,当我们直接运行这个 .py 文件时,__name__ = '__main__',当这个
.py 文件作为模块导入时,则 __name__ = '模块名'
为自定义模块添加说明文档,和函数或类的添加方法相同,即只需在模块开头的
位置定义一个字符串即可,用多行注释方法注释。然后可以通过模块的
__doc__ 属性查看说明文档,
'''
'''3. 第三方模块
Python官方提供第三方模块查找页面https://pypi.org/,可以使用 pip 工具
安装:pip [install/uninstall 模块名]/[list]。
pip 命令会将下载完成的第三方模块,默认安装到 Python 安装目录中的
\Lib\site-packages 目录下。
help('modules')可以查看所有的模块。
可以通过包的__file__获取该包的存储路径。因为当引入 my_package 包时,
其实际上执行的是 __init__.py 文件,因此这里查看 my_package 包的存储
路径,输出的 __init__.py 文件的存储路径。
'''
如果需要提取命令行参数,可以使用 sys 模块的 sys.argv 成员。
os.sep,路径分隔符。
sys.winver,Python 版本。
https://blog.csdn.net/weixin_44239379/article/details/86026086
浅拷贝(shallow copy)和深度拷贝(deep copy)。
所谓浅拷贝,指的是重新分配一块内存,创建一个新的对象,但里面的元素是原对象中各个子对象的引用。所谓深拷贝,是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。因此,新对象和原对象没有任何关联。
常见的浅拷贝的方法:数据类型本身的构造器、切片、copy模块的copy函数(tuple的构造和切片只是一个引用,不是浅拷贝)。注意浅拷贝后,id 是不同的,因此 is 返回 False,== 返回 True。
深拷贝方法:copy模块的deepcopy方法。深度拷贝也不是完美的,往往也会带来一系列问题。如果被拷贝对象中存在指向自身的引用,那么程序很容易陷入无限循环,不过deepcopy函数没有这个问题,我们自己实现的话需要注意。
import copy
list1 = [1]
list1.append(list1)
print(list1)
list2 = copy.deepcopy(list1) # 不会死循环
# 深度拷贝函数 deepcopy 中会维护一个字典,记录已经拷贝的对象与其 ID。
# 拷贝过程中,如果字典里已经存储了将要拷贝的对象,则会从字典直接返回。
print(list2)
参考。
for循环需要使用迭代器,如果不是迭代器但是是Iterable的,那么先调用iter()函数尝试把它转换为迭代器,如果成功,就不断调用next()直到结束。iter()函数和next()函数是内置函数,下面的章节有所介绍。此外,Iterator和Iterable必须导入才能使用:
from collections import Iterator, Iterable
可迭代对象Iterable:一个对象只要实现了__iter__()方法,那么用isinstance(xxx, Iterable)函数检查就是Iterable对象。包括序列类型(list、tuple、set、dict、str)、文件对象和自定义了 __iter__() 的对象。Iterable对象并不一定是(可能是)Iterator,这需要注意。
如果要求能在for中使用,__iter__()必须返回一个迭代器(带有__next()__ 和 __iter__()函数的对象),这样才能通过内置iter()函数转成Iterator对象,供for使用。一般情况下,是通过一些已知的可迭代对象(例如集合、序列、文件等或其它正确定义的可迭代对象)来辅助我们来实现,举例:
from collections import Iterator, Iterable
class IterObj:
def __init__(self):
self.a = [3, 5, 7, 11, 13, 17, 19]
def __iter__(self):
return iter(self.a) # 返回一个 Iterable
it = IterObj()
print(hasattr(it, "__iter__")) # True
print(isinstance(it, Iterable)) # True
print(isinstance(it, Iterator)) # False
print(isinstance(it, Generator)) # False
print(iter(it)) #
for i in it:
print(i) # 将打印3、5、7、11、13、17、19元素
在类中实现了如果只实现__getitem__()的对象也可以通过iter()函数转化成迭代器(返回某个元素而不是整个Iterable),但其本身不是可迭代对象。注意这种方式必须给__getitem__()提供一个索引,是通过索引实现的。所以当一个对象能够在for循环中运行,但不一定是Iterable对象,例如:
from collections import Iterator, Iterable
class IterObj:
def __init__(self):
self.a = [3, 5, 7, 11, 13, 17, 19]
def __getitem__(self, i):
return self.a[i]
it = IterObj()
print(isinstance(it, Iterable)) # False
print(iter(it)) #
for i in it:
print(i) # 将打印出3、5、7、11、13、17、19
迭代器对象Iterator:一个对象实现了__iter__()和__next__()方法,那么它就是一个迭代器对象,因此Iterator一定是Iterable的。集合和序列对象是可迭代的但不是迭代器,因此如果希望使用next函数需要用iter把它转换为迭代器(字典是迭代其键值),而文件对象是迭代器!一个迭代器(Iterator)对象不仅可以在for循环中使用,还可以通过内置函数next()函数进行调用。例如:
from collections import Iterator, Iterable
class IterObj:
def __init__(self):
self.a = [3, 5, 7, 11, 13, 17, 19]
self.n = len(self.a)
self.i = 0
def __iter__(self):
return iter(self.a)
def __next__(self):
while self.i < self.n:
v = self.a[self.i]
self.i += 1
return v
else:
self.i = 0
raise StopIteration()
it = IterObj()
print(isinstance(it, Iterable)) # True
print(isinstance(it, Iterator)) # True
for i in it:
print(i) # 将打印出 3、5、7、11、13、17、19
next(it) # 打印 StopIteration,因为已经在 for 遍历完了
注意,迭代器只能向后移动、不能回到开始,再次迭代只能创建另一个新的迭代对象。因此 Iterable 还是很有意义的。
Iterator实际是惰性计算的序列,是逐渐计算出数据的,一个Iterator包含无数多的数据也是可以的,只要我们写出迭代公式。
生成器对象:
一个生成器既是可迭代的也是迭代器。包括生成式(注意必须是小括号的)和 yield 的generator function。更多参考这里。生成器都是Iterator。
这种做法可以不需要消耗大量的内存来生成一个巨大的列表,只有在需要数据的时候才会进行计算。在Python中利用生成器的这些特点可以实现协程。协程可以理解为一个轻量级的线程,它相对于线程处理高并发场景有很多优势:
from collections import Iterator, Iterable
def producer(c):
n = 0
while n < 5:
n += 1
print('producer {}'.format(n))
r = c.send(n) # yield 返回值
print('consumer return {}'.format(r))
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('consumer {} '.format(n))
r = 'ok'
if __name__ == '__main__':
c = consumer()
next(c) # 启动 consumer
producer(c)
'''输出
producer 1
consumer 1
producer return ok
producer 2
consumer 2
producer return ok
producer 3
consumer 3
producer return ok
'''
generator.send() :还可以向生成器中传值,也就是 yield 语句的返回值,send() 方法可带一个参数,也可以不带任何参数(用 None 表示),当使用不带参数的 send() 方法时,它和 next() 函数的功能完全相同。但是,带参数的 send(value) 无法启动执行生成器函数。也就是说,程序中第一次使用生成器调用 next() 或者 send() 函数时,不能使用带参数的 send() 函数。
generator.close() :当程序在生成器函数中遇到 yield 语句暂停运行时,此时如果调用 close() 方法,会阻止生成器函数继续执行,该函数会在程序停止运行的位置抛出 GeneratorExit 异常。虽然通过捕获 GeneratorExit 异常,可以继续执行生成器函数中剩余的代码,带这部分代码中不能再包含 yield 语句,否则程序会抛出 RuntimeError 异常。另外,生成器函数一旦使用 close() 函数停止运行,后续将无法再调用 next() 函数或者 __next__() 方法启动执行,否则会抛出 StopIteration 异常。
generator.throw() :生成器 throw() 方法的功能是,在生成器函数执行暂停处,抛出一个指定的异常,之后程序会继续执行生成器函数中后续的代码,直到遇到下一个 yield 语句。如果到剩余代码执行完毕没有遇到下一个 yield 语句,则程序会抛出 StopIteration 异常。
class SelfException(Exception):
pass
def foo():
var = 1
while True:
try:
var = yield var
except GeneratorExit as e
print('exit')
return # 如果不退出,那么下次 yield 会引发
# RuntimeError
except SelfException as e
print('self')
f = foo()
print(f.send(None)) # 第一次执行 yield var,必须使用 None 参数
# 或者使用 next 函数代替 send
# 打印 1
print(f.send(2)) # 执行 var = ,并再次执行 yield var
# 打印 2
f.throw(SelfException())
# 打印 self
# 打印 2
f.close() # 打印 exit
Python 在存储数据时,会根据数据的读取频繁程度以及内存占用情况来考虑,是否按照一定的规则将数据存储缓存中,缓存重用的情形见下表:
注意这个是个大坑:
a, b = 1, 1
a == b # True
a is b # True,这就是个坑
a, b = 1, 2
a == b # False
a is b # False
sys 模块的 exit() 函数用于退出程序
可调用的对象必须实现的。
我们平时自定义的函数、内置函数和类都属于可调用对象,但凡是可以把一对括号()应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 callable。
如果在类中实现了 __call__ 方法,那么实例对象也将成为一个可调用对象。注意在Python中,方法也是一种高等的对象。
允许一个类的实例像函数一样被调用。实质上说,这意味着 x() 与 x.__call__() 是相同的。__call__ 参数可变。这意味着你可以定义 __call__ 为其他你想要的函数,无论有多少个参数。
当定义完成一个类时,真正发生的情况是 Python 会调用 type 类的 __call__ 运算符。它会进一步调用 __new__ 和 __init__ 函数,创建实例并初始化。
avg.__code__.co_varnames
avg.__code__.co_freevars
file
[].__sizeof__ # 40
().__sizeof__ # 24
help函数显示的内容。
print(CLanguage.name.__doc__)
可以使用“函数名.defaults”查看函数的默认值参数的当前值,其返回值是一个元组。
annotations
mro
类函数搜索顺序。
name
name: 类的名字,也就是我们通常用类名.__name__获取的。
new
实例化?metaclass?
metaclass
mdzz,不小心删库了…
np.isscalar(x)
一般情况下,我们送入的一批状态数据应该是Python原生多维数组或者在数组外围套一个np.array():
# 第一种状态的mini-batch,其中每行的四个元素是一个状态的四个分量,每行则是一个状态向量
[[1,2,3,4],
[4,5,6,7],
[1,2,2,1]]
# 第二种状态的mini-batch
array([[1,2,3,4],
[4,5,6,7],
[1,2,2,1]])
# 下面的形式是不对的
array([array([1,2,3,4]),
array([4,5,6,7]),
array([1,2,2,1])],
dtype=object)
然而,我们每次调用env.step(…)得到的原始数据是如下形式的:
(array([-0.00681161, -0.43440851, -0.00417234, 0.62287841]), 1.0, False, {
})
我们必须把状态、回报、done、附加信息都处理成上面介绍的形式,一般使用如下方法:
# 假设上面说到的原始数据mini-batch是如下数组:
result = [(array([1,2,3,4]), 1.0, False, {
}),(array([1,2,3,4]), 1.0, False, {
}),(array([1,2,3,4]), 1.0, False, {
})]
# 生成minibatch的办法
_obs, _rew, _done, _info = zip(*result)
_obs = np.stack(_obs)
_rew = np.stack(_rew)
_done = np.stack(_done)
_info = np.stack(_info)
我们可以通过列表生成式简单直接地创建一个列表,但是受到内存限制,列表容量肯定是有限的。生成器的主要作用是用来提供数据,同时又不占用太多内存。
生成器generator有两种形式:
第一种是在函数中加入yield关键字(generator函数中允许使用return,但是return 后不允许有返回值),
generator和普通function的区别为:
(1)function每次都是从第一行开始运行,而generator从上一次yield开始的地方运行。
(2)function调用一次返回一个(一组)值,而generator可以多次返回。
(3)function可以被无数次重复调用,而一个generator实例在yield最后一个值 或者return之后就不能继续调用了。
第二种则是generator表达式,形如(_ for _ in range(4))的形式,注意必须使用小括号,使用中括号会形成列表。
使用generator,直接调用其.next()函数即可,这样就会执行到下一个yield,或者执行一次小括号中的循环。当后面没有yield后,则会报出异常StopIteration。generator还可以用for循环调用。
# 斐波那契数列
def fib(max):
n = 0
a, b = 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
gen1 = (_*_ for _ in range(4))
gen2 = fib(100)
for _ in gen1:
print(_)
for _ in gen2:
print(_)