自学两个月的python,这是两个月来做的总结。包括:
变量和简单类型;列表、元组和字典;流程控制;函数和lambda表达式;类和对象;异常处理;Python类的特殊方法;Python标准库与第三方库。
内容是参考《疯狂python讲义》以及《python语言程序设计》总结的知识点,有错的或者不详细的可以参考这两本书。
第2章变量和简单类型
1.dir(builtins)获取python解释器的内置常量和内置函数。
2.标识符使用规则:1)由字母、数字下划线组成,数字不能打头;2)不能是关键字;
3)不能包含空格。
3.如果字符串本身包含了单引号或双引号,此时需要进行处理。
1)使用不同的引号将字符串括起来;2)用转义字符对引号进行转义。
4.拼接字符串。使用“+”号作为字符串的拼接字符串。
5.为了将数值转换成字符串,可以使用str()或repr()函数。str本身是python内置的类型,repr()是一个函数。s1+str(p1)+repr(p2)。
6.input()函数用于向用户生成一条提示,然后获取用户输入的内容。每次都是返回一个字符串。
7.原始字符串以“r”开头,原始字符串不会把反斜线当成特殊字符,此时用于转义的反斜线会变成字符串的一部分。
8.字节串bytes有多个字节组成,以字节为单位进行操作。讲义P25。
9.在字节串中每个数据单元都是字节(Byte),也就是8位(bit),其中4位可以用一个十六进制数表示。 print(bytes(‘我爱Python编程’,encoding=‘utf-8’))
10.转义字符。\b退格符;\n换行符;\r回车符;\t制表符;\”双引号符;\’单引号符;\反斜线。
11.用“%”对各种类型的数据进行格式化输出。讲义P29。
12.print("%s is a %s years old boy"%(user,age)) %s为转换说明符。
13.用“%”格式化。1)“-”指定左对齐;2)“+”表示数值总是带着符号;3)“0”表示不补充空格,而是补充0。 如%06d %+06d %-6d
14. dir():列出指定类或模块包含的全部内容。 help():查看某个函数或方法的帮助文档。
15. 运算符:
赋值运算符:= += -= *=等
算术运算符:+ - * / // % **
位运算符:& | ^ ~ << >> 交 并 异或 非
索引运算符:[ ] 如a[2:8:3],索引2到8,步长为3
比较运算符:> >= < <= == != is is not bool类型True False
逻辑运算符:and与 or或 not非 返回值为bool类型
16. 三目运算符:True_statements if expression else False_statements
先对逻辑表达式expression求值,如果逻辑表达式返回True,则执行并返回True_statements;否则执行并返回False_statements的值。
print(a) if a>b else print(b)
第3章 列表、元组和字典
1.列表和元组都按顺序保存元素,都可以使用索引,因此列表和元组都可以通过索引访问元素。元组是不可修改的,列表是可以修改的。字典则以键值对的形式保存数据。
2.创建列表[ele1,ele2,ele3,…]。创建元组(ele1,ele2,ele3,…)。
3.切片:tuple[start : end : step]
4.列表和元组支持加法运算,加法的和就是将两个列表或元组所包含的元素的总和。元组和列表之间不能相加。sum=a+b
5.乘法运算,将列表和元组包含的元素重复N次。mul_tuple=a_tuple3
6.in运算符,判断列表或元组是否包含某个元素。 # print(20 in a_tuple) 返回True或False
7.长度len()。最大值max()。最小值min()。
8.序列封包:程序把多个值赋给一个变量时,python会自动将多个值封装成元组.
vals=10,20,30 print(vals , type(vals) , vals[1]) # (10,20,30) tuple 20
9. 序列解包:将序列(元组或列表等)直接赋值给多个变量,此时序列的各元素会被依次赋值给每个变量(要求个数相等)。
a,b,c,d,e = a_tuple x,y,z=10,20,30 xyz=10,20,30 x,y,z=y,z,x
在解包时,允许左侧变量之前添加“”,那么该变量就代表一个列表,可以保存多个集合元素。
a , c , *d = range(10)。
10. list()函数可以将元组、区间等对象转换为列表。
a_list = list(a_tuple) b_list = list(a_range)
11. tuple()函数可以将列表、区间等对象转换为元组。
a_tuple = tuple(a_list) b_tuple = tuple(a_range)
12. append()函数可为列表增加元素,参数可以是单个值、元组、列表等(可嵌套),将传入的参数追加到列表后面。
a_list = [‘python’,20,-2]
a_list.append((‘java’,4.6))
print(a_list) # [‘python’, 20, -2, (‘java’, 4.6)]
#被追加的元组或列表只是列表的一个元素,这就是嵌套。
- extend()函数,用来追加列表中的元素。
a_list = [‘python’,20,-2]
a_list.extend((‘java’,4.6))
print(a_list) #[‘python’, 20, -2, ‘java’, 4.6]
- insert(n, tuple)函数,在指定位置将元素插入列表。n为索引位置。
- del语句,用于执行删除操作。
del a del a[2] del a[1, 3] del a[2, -2, 2]
remove(str),只删除找第一个找到的元素,不存在则引发ValueError。
clear()是清空列表的所有元素。 c_list.remove(30) c_list.clear() - 列表的元素相当于变量,可以用索引和切片对列表的元素赋值。
a_list[2] = ‘python’ b_list[1:3] = [‘a’, ‘b’] - 在交互式解释器中输入dir(list)即可看到列表包含的所有方法。
count():用于统计列表中某个元素出现的次数。 a_list.count(n)
index():用于判断某个元素在列表中出现的位置。 a_list.index(str, start, end)
pop():用于将列表当成“栈”使用,实现元素出栈功能。先入后出。P57
用append()依次入栈,再用pop()依次出栈,并返回出栈的元素。
reverse():用于将列表中的元素反向存放。
sort():对列表元素进行排序。 sort(key = len, reverse = True) - 字典也是一种常用的数据结构,它用于存放具有映射关系的数据。通过关键数据key来访问value。键值对key_value。键key不允许重复,且为不可变类型。
- 创建字典。scores = {‘key1’:’value1’, ‘key2’:’valuer2’, …} empty_dict = {}空字典
dict的key可以是字符串、整数、元组。不能是列表。
或者用dict()函数创建的字典。 dict1 = dict()
dict2 = dict(key1 = 1, key2 = 2,…) 此时key不能用表达式 - 通过索引key访问value,不存在key时引发KeyError。 print(scores[‘语文’])
添加键值对,要求key不存在。 scores[‘数学’] = 93
用del语句删除键值对。 del scores[‘语文’]
赋值,新赋的value会覆盖原有的value。 scores[‘数学’] = 95
如果要判断字典是否含有指定的key,可以使用in或not is运算符。
列表不允许对不存在的索引赋值;但字典允许对不存在的key赋值,这样就会增加一个key-value。 - dir(dict)查看该类包含的操作符。
clear(),清空字典,使其成为空字典。 cars.clear()
get(),根据key获取value,当访问不存在的key时,会返回None。
update(),更新字典。覆盖已存在key-value对,不存在则创建新的。
cars.update({‘key1’:value1, ‘key2’:value2,…})
items(),获取所有的key-value对,返回dict_items。
keys(),获取所有的key,返回dict_keys。
values(),获取所有的value,返回dict_value。
Python不希望用户直接操作这三个方法,但可通过list()转换成列表。
ims = cars.items() print(list(ims))
pop(),用于获取指定的key对应的value,并删除这个key-value对。
popitem(),随机弹出一个键值对,假随机,因为键值对的顺序不可知,弹出的总是底层存储的最后一个键值对。(可以使用序列解包)
setdefault(),根据key获取value,返回值为value。当key不存在时,则返回默认值(None),可设置。 print(cars.setdefault(“python”,9.2)
fromkeys(),使用多个给定的key创建字典,相对应的value默认值为None,可以传入一个参数作为value。(意义不大)通常使用dict类调用。
a_dict=dict.fromkeys((13,17), ‘good’) # {17: ‘good’, 13: ‘good’} - 使用字典格式化字符串。
在字符串模板中按key指定变量,然后通过字典为字符串模板中的key设置值。适用含有大量变量的字符串模板。
temp = ‘书名是:%(name)s,价格是:%(price)010.2f,出版社是:%(publish)s’
book = {‘name’:‘疯狂Python讲义’, ‘price’:118,‘publish’:‘电子社’}
print(temp%book)
#书名是:疯狂Python讲义,价格是:0000118.00,出版社是:电子社
第4章 流程控制
1.分支结构。if、elif、else。 不要遗忘冒号
2.缩进。代码块一定要缩进,否则就不是代码块。不要随意缩进。
3.if条件的类型。bool、str、list、dict
4.在使用if else语句时,总是优先把包含范围小的条件放在前面处理。
5.pass语句,即空语句。有时候程序需要占一个位、放一条语句,但又不希望这条语句做任何事,可通过pass语句实现。
6.assert断言语句。它用于对一个bool表达式进行断言,如果该表达式为True,该程序继续执行,否则程序会引发AssertionError错误。
if 条件为False : 程序引发AssertionError错误
7.循环结构:初始化语句,循环条件,循环体,迭代语句。
8.while循环。先判断条件再执行。
9.使用while循环遍历列表和元组。
while i < len(a_tuple):
print(a_tuple[i]) #根据i来访问元组的元素
i += 1 #while循环也可以用来遍历列表
10.for-in循环。用于遍历范围、列表、元素和字典等可迭代对象包含的元素。
for 变量 in 字符串|范围|集合等 :
statements
1)变量将会在每次循环开始时自动被赋值,不能在循环体中对变量赋值。
2)可用于遍历任何可迭代对象。
for i in range(0, 8)
11.使用for-in遍历列表和元组。列表或元素有几个元素,循环体就执行几次,针对每个元素执行一次,循环计数器会依次被赋值为元素的值。
for i in a_tuple: for i in a_list
12.使用for-in循环遍历字典。 使用items()、keys()、values()进行遍历。
for key, value in my_dict.items():
for key in my_dict.keys():
for value in my_dict.values():
13.循环使用else。 Python的循环体都可以定义else代码块。
count_i = 0
while count_i < 5:
print(‘count_i小于5:’, count_i)
count_i +=1
else:
print(‘count_i大于或等于5:’, count_i)
for循环同意可以使用else代码块,遍历结束后,for循环会执行else代码块。在else代码块中,循环计数器的值依然等于最后一个元素的值。
14.嵌套循环。嵌套循环就是把内层循环当成外层循环的循环体。只有内层循环的循环条件为假时,才会完全跳出内层循环,才可以结束外层循环的当次循环,开始执行下一次循环。
15.for表达式。 [表达式 for 循环计数器 in 可迭代对象]
a_list = [xx for x in range(10)] #a_list集合包含10个元素
在for表达式后添加if条件,这样只迭代符合条件的元素。
b_list = [xx for x in range(10) if x%20]
将表达式的方括号改成圆括号
此时表达式生成的不是列表,而是一个生成器(generator)
c_list = (x*x for x in range(10) if x%20) #使用for表达式创建生成器
for i in c_list: #使用 for循环迭代生成器
print(i, end=’\t’)
for表达式支持多层嵌套,并且可指定if条件。
16.zip()函数可以把两个列表“压缩”成一个zip对象(可迭代对象)
c_list = [x for x in zip(a_list, b_list)]
17.reversed()函数可接受各种序列参数,然后返回一个“反向排序”的迭代器。
a = range(0,10)
[x for x in reversed(a)]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]a #该函数对参数本身不会产生影响
range(0, 10) #对列表和元组也可以进行反转
18.sorted(),该函数接收一个可迭代对象作为参数,返回一个元素排序的列表a = [20, 30, -1.2, 3.5, 90, 3.6]
sorted(a) #不会改变所传入的迭代对象。
[-1.2, 3.5, 3.6, 20, 30, 90]
在使用sorted()时还可以传入一个reversed参数sorted(a, reverse = True)
[90, 30, 20, 3.6, 3.5, -1.2]
还可以传入一个key参数,该参数可指定一个函数来生成排序的关键值。
例如传入的为len,则根据字符串长度排序。
19.break,跳出循环体,执行循环体后面的语句。
20.continue,结束当前循环,执行下一次循环。
21.return,结束该函数或方法,循环自然也随之结束。
第5章 函数和lambda表达式
1.定义函数:def 函数名(形参列表):
//有零条到多条可执行语句组成的函数
[return [返回值]]
2.help()函数,查看该函数的说明文档。
3.如果程序需要多个返回值,则即可将多个返回值包装成列表之后返回,也可直接返回多个值。
4.递归函数。在一个函数体内调用它自身。
5.关键字参数。在调用函数时,需要根据函数指定的参数严格排序。使用关键字则无需遵守。print(girth(a = 1, b = 2)) a和b是函数指定的参数。
6.位置参数必须在关键字参数前。print(girth(b = 2, 1))是错误的。
7.参数默认值。定义时指定默认值。可以省略该形参传入参数值,使用该形参的默认值。Python要求将带默认值的参数,定义在形参列表的最后。
8.参数收集。在参数前加“*”号,意味着该参数可接收多个参数值,多个参数值被当做元组传入。 def test(a, books) test(5, “python”, “java”)
9.加两个“”号,可以用来收集字典。 def test(a, b, *books, **scores)
test(1, 2, “python”, “java”, 语文=89, 数学=94)
10.逆向参数收集。指的是在程序列表、元组、字典等对象的前提下,把它们的元素“拆开”后传给函数的参数。
def test(name, message):
函数内容
my_list = [‘python’, ‘java’]
test(*my_list)
不使用逆向收集,不加星号就行。字典根据关键字传入参数。
11.Python中函数的参数传递机制都是“值传递”。值传递,就是将实际参数值的副本传入函数,而参数本身不会受到影响。
12.如果参数是一个可变对象(列表、字典等)程序开始创建一个字典对象,并定义一个dw引用变量指向字典对象,这意味着此时内存中有两个东西:对象本身和指向该对象的引用变量。
程序开始调用定义好的swap()函数,调用时dw变量作为参数传入swap()函数,同样采用值传递。但传入的dw是一个引用变量(指针),因此系统复制的是dw变量,并未复制字典本身。
13.不管什么类型的参数,在Python函数中对参数直接使用”=”符号赋值是没有用的,不能改变参数。值传递。
如果需要让函数修改某些数据,则可以通过把这些数据包装成列表、字典等可变对象,然后把可变对象作为参数传入函数。
14.局部变量:在函数中定义的变量,包括参数。
全局变量:在函数外面、全局范围内定义的变量。
15.Python提供了三个函数来获取指定范围内的“变量字典”
globals():该函数返回全局范围内所有变量组成的“变量字典”。
locals():该函数返回当前局部范围内所有变量组成的“变量字典”。
vars(object):获取在指定范围内所有变量组成的“变量字典”。
16.如果在函数之外使用locals()函数,同样会获取全局范围内所有变量组成的“变量字典”。在函数内globals()使用,总是获取全局范围内的“变量字典”。
17.在函数内部对不存在的变量赋值时,默认就是重新定义新的局部变量。如果在函数外对name赋值,然后在函数内再对name赋值,这样name的全局变量就被遮蔽了。
18.访问被遮蔽的全局变量。 print(globals()[‘name’])
在函数中声明全局变量。 global name print(name)
19.局部函数。在函数体内定义的函数。局部函数对外部是隐藏的,局部函数只在其封闭函数内有效。其封闭函数也可以返回局部函数。
20.使用函数变量。Python的所有函数都是function对象,这意味着可以把函数本身赋值给变量。
def pow(a, b) my_fun = pow print(my_fun(1, 2))
21.使用函数作为函数形参。在调用某个函数时传入不同的参数作为参数,从而动态改变这段代码。
def map: def cube: data = [] print(map(data, cube))
22.使用函数作为返回值。(返回局部函数)
def get_match_fun(type):
def cube(n):
return n*n*n
return cube
23.由于局部函数作用域仅停留在其封闭函数内,局部函数的函数名的作用太有限了,其意义不大,可用lambda表达式代替局部函数。
def get_math(type):
if type=='cube':
return lambda n:n*n
math_fun = get_math('cube') #调用get_math,程序返回一个嵌套函数。
print(math_fun(5)) #输出25
26.lambda表达式必须使用lambda关键字定义。
可以有多个参数,参数之间用逗号隔开。冒号右边是返回值。
表达式只能是单行表达式。
lambda [parameter_list]
27.lambda表达式的用途。
单行函数。lambda可以省去定义函数的过程,让代码更加简洁。
不需要多次使用的函数。使用lambda可以在用完后立即释放,提高性能。
28.Python语言既支持面向过程编程,也支持面向对象编程。而函数和lambda表达式就是Python面向过程编程的语法基础。
第6章 类和对象
1.Python支持面向对象的三大特征:封装、继承和多态,子类继承父类同样可以继承父类的变量和方法。
2.类(class)和对象(object)。其中类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。
3.函数定义使用def关键字,类定义使用class关键字。类由类头和类体构成。
class ClassName:
‘’’类的帮助信息’’’
statement #语句
4.Python类最主要的两个成员是变量和方法。
类变量属于类本身,用于定义该类本身所包含的状态数据。
实例变量属于类的对象,用于定义对象所包含的状态数据。
方法用于定义该类的对象的行为或功能实现。
5.Python是门动态语言,因此类所包含的类变量和实例变量 可以动态增加或删除。程序新变量赋值就是增加类变量;用del语句可以删除已有的类变量。
6.在类中定义的方法默认是实例方法。实例方法的第一个参数会被绑定到方法的调用者(该类的实例),因此实例方法至少应该定义一个self参数。
7.实例方法中有一个特别的方法。init,该方法称为构造方法,用于构造该类的对象,Python通过调用构造方法返回该类的对象(无需使用new)。
8.下面的程序将定义一个Person类。
class Person :
'这是一个学习Python定义的一个Person类'
hair = 'black' # 定义一个类变量
def __init__(self, name = 'Charlie', age=8):
self.name = name
self.age = age # 为Person对象增加2个实例变量
def say(self, content):
print(content) # 定义了一个say方法
9.创建对象的根本途径是构造方法,调用某个类的构造方法即可创建这个类的对象。
调用Person类的构造方法,返回一个Person对象
将该Person对象赋给p变量
p = Person() #p是Person类的对象
10.对象访问方法或变量的语法是:对象. 变量|方法(参数)。对象是主调者,用于访问该对象的变量或方法。
print(p.name, p.age) # 输出p的name、age实例变量 # Charlie 8
p.name = ‘李刚’ # 访问p的name实例变量,直接为该实例变量赋值
调用p的say()方法,第1个形参(self)是自动绑定的,因此指定第2个形参的值
p.say(‘Python语言很简单,学习很容易!’)
print(p.name, p.age) # 再次输出p的name、age实例变量 # 李刚 8
11.类定义的是多个对象的特征,因此类不是一个具体存在的实体,对象才是一个具体存在的实体。
12.对象的动态特性。为p对象动态增加和删除实例变量。
p.skills = [‘programming’, ‘swimming’] # 为p对象增加一个skills实例变量
print(p.skills) #输出结果[‘programming’, ‘swimming’]
del p.name # 删除p对象的name实例变量
print(p.name) # 再次访问p的name实例变量,会引发AttributeError
13.为p对象动态增加的方法,Python不会自动将调用者自动绑定到第一个参数,即使第一个参数命名为self也没用。例如如下代码。
def info(self): # 先定义一个函数
print("---info函数---", self) #使用info函数对p的foo方法赋值(动态绑定方法)
p.foo = info
p.foo(p) #使用lambda表达式为p对象的bar方法赋值(动态绑定方法)
p.bar = lambda self: print('--lambda表达式--', self)
p.bar(p)
-
如果希望动态增加的方法也能自动绑定到第一个参数,则可借助于types模块下的MethodType。
def intro_func(self, content):
print(“我是一个人,信息为:%s” % content)
from types import MethodType # 导入MethodType
p.intro = MethodType(intro_func, p) #将intro_func第一个参数绑定为p
p.intro(“生活在别处”) # 第一个参数已经绑定了,无需传入 -
self参数最大的作用就是引用当前方法的调用者。
假设定义一个Dog类,这个Dog对象的run方法需要调用它的jump()方法,此时就能通过self参数作为jump()方法的调用者。程序如下。class Dog: #定义一个Dog类
def jump(self): #定义一个jump方法
print(“正在执行jump方法”)
def run(self): #定义一个run方法,run方法需要借助jump
self.jump() #使用self参数引用调用run方法
print(“正在执行run方法”)
p = Dog() #将Dog类的对象赋值给p变量。
p.run() #调用p的run -
使用self参数作为方法的返回值可以让代码更加简洁,但可能造成实际意义的模糊。
-
方法是类或对象的行为特征的抽象,但Python方法其实也是函数,其定义方式、调用方式和函数都非常相似,它与函数有莫大的关系。
def foo (): # 定义全局空间的foo函数
print(“全局空间的foo方法”)
bar = 20 # 全局空间的bar变量
class Bird:
def foo(): # 定义Bird空间的foo函数
print(“Bird空间的foo方法”)
bar = 200 # 定义Bird空间的bar变量 调用全局空间的函数和变量
foo() print(bar) 调用Bird空间的函数和变量
Bird.foo() print(Bird.bar)
18.如果想使用类来调用实例方法,则必须手动为方法的第一个参数传入参数值。
u = Bird() Bird.foo(参数) #此法等同于u.foo()
19.Python完全支持定义类方法,甚至支持定义静态方法。
使用@classmethod修饰的方法就是类方法;
使用@staticmethod修饰的方法就是静态方法。
20.两者都推荐使用类调用。类方法的第一个参数会自动绑定到类本身,而静态方法不会自动绑定。
class Bird:
@classmethod # classmethod修饰的方法是类方法
def fly (cls): #参数建议命名为cls
print('类方法fly: ', cls)
@staticmethod # staticmethod修饰的方法是静态方法
def info (p):
print('静态方法info: ', p)
Bird.fly() # 调用类方法,Bird类会自动绑定到第一个参数
Bird.info('crazyit') #调用静态方法。不会自动绑定,需要手动绑定第一个参数
b = Bird() # 创建Bird对象
b.fly() # 使用对象调用fly()类方法。 #类方法fly: <class '__main__.Bird'>
b.info('fkit') # 使用对象调用info()静态方法。 #静态方法info: fkit
#最后两个依然还是类调用。
21.@函数装饰器。使用@符号引用已有的函数后,可用于修饰其他函数。当程序使用“@函数”(比如函数A)装饰另一个函数(比如函数B)时,实际上完成如下两步。
1)将被装饰的函数(函数B)作为参数传给@符号引用的函数(函数A)
2)将函数B替换(装饰)成第1)步的返回值。
def funA(fn):
print(‘A’)
fn() #执行传入的fn参数
return ‘fkit’
@funA #装饰效果相当于funA(funB),funB将会被替换成该语句的返回值
def funB: #由于funA()返回fkit,因此funB就是fkit
print(‘B’)
print(funB) #输出结果:A B fkit
22.通过@符号来修饰函数,可以在被修饰函数之前、之后、抛出异常后增加某种处理逻辑的方式,这就是其他编程语言中的AOP(面向切面编程),计算机二级不考。
23.global_fn = lambda p: print('执行lambda表达式,p参数: ', p)
class Category:
cate_fn = lambda p: print('执行lambda表达式,p参数: ', p)
global_fn('fkit') # ①调用全局范围内的global_fn,为参数p传入参数值
c = Category()
c.cate_fn() # ②调用类命名空间内的cate_fn,Python自动绑定第一个参数
上面程序分别在全局空间、类命名空间定义两个lambda表达式,在全局空间内定义的lambda表达式就相当于一个普通函数,因此要用调用函数的方式调用lambda,并绑定一个参数值。
对于在类命名空间定义的lambda表达式,相当于在该类命名空间中定义一个函数,这个函数变成了实例方法,因此要用调用方法的方式来调用lambda表达式,Python同样会为该方法的第一个参数(self)绑定参数值。
24.在类命名空间内定义的变量就属于类变量。
25.类变量是属于在类命名空间内定义的变量,因此程序不能直接访问类变量,必须使用类名来调用类变量。全局范围和函数内访问。
class Address :
detail = '广州'
post_code = '510660'
def info (self):
print(Address.detail) # 输出 广州 # 通过类来访问类变量
print(Address.post_code) # 输出 510660
print(Address.detail) # 通过类来访问Address类的类变量 #输出 广州
addr = Address()
addr.info() #执行Address类中的info函数
Address.detail = '佛山' # 修改Address类的类变量
Address.post_code = '460110'
addr.info() #再次执行Address类中的info函数 #输出 佛山460110
26.Python允许使用对象来访问该对象所属类的类变量。(推荐使用类访问类变量)
def info (self): print(‘info方法中:’, self.detail) #输出 广州
27.Python如果通过对象尝试对类变量赋值此时性质就变了。Pyhon是动态语言,赋值语句往往意味着定义新的变量。如果程序通过类修改两个类变量的值,程序中的实例变量也不会受到任何影响。
class Inventory:
item = '鼠标'
quantity = 2000 # 定义两个类变量
def change(self, item, quantity): # 定义实例方法
self.item = item # 定义新的实例变量
self.quantity = quantity
iv = Inventory() # 创建Inventory对象
iv.change('显示器', 500)
print(iv.item) # 显示器 # 访问iv的item和quantity实例变量
print(iv.quantity) # 500
print(Inventory.item) # 鼠标 # 访问Inventory的item和quantity类变量
print(Inventory.quantity) # 2000
28.使用property函数可以将getter、setter等访问器方法定义成属性。(相当于实例变量)
property(fget = None, fset = None, fdel = None, doc = None)
getter读属性,setter写属性,del删除属性,doc文档说明
29.该程序对Rectangle对象的size属性进行读、写、删除操作。
class Rectangle:
def __init__(self, width, height): # 定义构造方法
self.width = width
self.height = height
def setsize (self , size): # 定义setsize()函数,执行写操作
self.width, self.height = size
def getsize (self): # 定义getsize()函数,执行读操作
return self.width, self.height
def delsize (self): # 定义delsize()函数,执行删除操作
self.width, self.height = 0, 0
# 使用property定义属性
size = property(getsize, setsize, delsize, '用于描述矩形大小的属性')
print(Rectangle.size.__doc__) # 访问size属性的说明文档,并输出
rect = Rectangle(4, 3)
print(rect.size) # 访问rect的size属性 # (4, 3)
rect.size = 9, 7 # 对rect的size属性赋值
print(rect.width) # 访问rect的width、height实例变量 # 9
print(rect.height) # 7
del rect.size # 删除rect的size属性 访问rect的width、height实例变量
print(rect.width) # 0
print(rect.height) # 0
print(dir(Rectangle))
30.还可以使用@property装饰器来修饰方法,使之成为属性。
class Cell:
@property # 使用@property修饰方法,相当于为该属性设置getter方法
def state(self):
return self._state
@state.setter # 为state属性设置setter方法
def state(self, value):
if 'alive' in value.lower():
self._state = 'alive'
else:
self._state = 'dead'
c = Cell()
c.state = 'Alive' # 修改state属性
print(c.state) # 访问state属性 # alive
31.封装。将对象的状态信息隐藏在对象内部,不允许不允许外部程序员直接访问对象内部信息,而是通过该类所提供的方法实现对内部信息的操作和访问。
32.为了实现良好的封装,需要从两个方面来考虑。
1)将对象的属性和实现细节隐藏起来,不允许外部直接访问。
2)把方法暴露出来,让方法来控制对这些属性进行安全的访问和操作。
33.只要将Python类的成员命名为以双下划线开头的,Python就会把它们隐藏起来。
34.程序将User的两个实例变量分别命名为__name和__age,程序只能通过访问器方法进行访问。
class User :
def __hide(self):
print('示范隐藏的hide方法')
def getname(self): #读name语句
return self.__name
def setname(self, name): #写name语句
if len(name) < 3 or len(name) > 8:
raise ValueError('用户名长度必须在3~8之间')
self.__name = name
name = property(getname, setname) #定义成属性,读,写
def setage(self, age):
if age < 18 or age > 70:
raise ValueError('用户名年龄必须在18在70之间')
self.__age = age
def getage(self):
return self.__age
age = property(getage, setage)
u = User() # 创建User对象
u.name = 'fkit' # 对name属性赋值,实际上调用setname()方法
u.age = 25
print(u.name) # fkit
print(u.age) # 25
u.__hide() #尝试调用隐藏的__hide(),引发错误
u._User__hide() # 调用隐藏的__hide()方法 #示范隐藏的hide方法
u._User__name = 'fk' # 对隐藏的__name属性赋值
print(u.name) # 访问User对象的name属性(实际上访问__name实例变量)
35.Python并没有提供真正的隐藏机制,Python类定义的所有成员默认都是公开的。即使让该成员的名字以双下划线开头,也可以绕过隐藏。
36.继承。Python的继承是多继承机制,即一个子类可以同时有多个父类。继承是实现软件复用的重要手段。
class SubClass(SuperClass1, SuperClass2, …): #sub子类,super父类
37.如果在定义一个Python类时并未指定这个类的直接父类,则这个类默认继承object类。因此object类是所有类的直接父类或间接父类。
38.子类扩展(extend)了父类,父类派生(derive)出子类。
class Fruit:
def info(self):
print("我是一个水果!重%g克" % self.weight)
class Food:
def taste(self):
print("不同食物的口感不同")
class Apple(Fruit, Food): # 定义Apple类,继承了Fruit和Food类
pass
a = Apple() # 创建Apple对象
a.weight = 5.6
a.info() # 调用Apple对象的info()方法 #我是一个水果!重5.6克
a.taste() # 调用Apple对象的taste()方法 #不同食物的口感不同
主程序创建了Apple对象之后,可以访问Apple对象的info()和taste()方法,这就是继承的作用,子类扩展了父类,这样子类就可以复用父类的方法。
39.Python虽然在语法上支持多继承,但尽量不要使用多继承。使用单继承可以保证编程思路清晰,避免很多麻烦。如果多个父类中包含了同名的方法,此时排在前面的方法会“遮蔽”排在后面的方法。
40.子类需要重写父类的方法。子类包含与父类同名的方法的现象称为方法重写(Override),也称为方法覆盖。
class Ostrich(Fruit): # 定义子类
def info(self): # 重写Fruit类的info方法
print("我是一个体重%g克的水果!" % self.weight)
os = Ostrich() #创建Ostrich()对象
os.weight = 5.6
os.info() # 输出 我是一个体重5.6克的水果!
41.使用未绑定方法调用被重写的方法。在通过类名调用实例方法时,Python不会为实例方法的第一个参数self自动绑定参数值,而是需要程序显示绑定第一个参数self。这种机制被称为未绑定方法。
class BaseClass:
def foo (self):
print('父类中定义的foo方法')
class SubClass(BaseClass): #子类继承父类
def foo (self): # 重写父类的foo方法
print('子类重写父类中的foo方法')
def bar (self):
print('执行bar方法')
self.foo() # 直接执行foo方法,将会调用子类重写之后的foo()方法
BaseClass.foo(self) # 使用类名调用实例方法调用父类被重写的方法
sc = SubClass() # 输出 执行bar方法 子类重写父类中的foo方法
sc.bar() 父类中定义的foo方法
42.子类也会继承得到父类的构造方法,如果有多个直接父类,那么排在前面的父类的构造方法会被优先使用。排在后面的父类就无法使用。
43.为了让子类能同时初始化两个父类中的实例变量,子类应该定义自己的构造方法,就是重写父类的构造方法。子类的构造方法调用父类的构造方法有两种。
1)使用未绑定方法;
2)使用super()函数调用父类的构造方法。
44.super()其实是一个类,因此调用super()的本质就是调用super类的构造方法来创建super对象。
class Employee :
def __init__ (self, salary):
self.salary = salary
def work (self):
print('普通员工正在写代码,工资是:', self.salary)
class Customer:
def __init__ (self, favorite, address):
self.favorite = favorite
self.address = address
def info (self):
print('我是一个顾客,我的爱好是: %s,地址是%s' % (self.favorite, self.address))
class Manager(Employee, Customer): # Manager继承了Employee、Customer
def __init__(self, salary, favorite, address): # 重写父类的构造方法
print('--Manager的构造方法--')
super().__init__(salary) # 通过super()函数调用父类的构造方法
#super(Manager, self).__init__(salary) # 与上一行代码的效果相同
Customer.__init__(self, favorite, address)#使用未绑定方法调用父类的构造方法
m = Manager(25000, 'IT产品', '广州') # 直接输出 --Manager的构造方法--
m.work() #① # 普通员工正在写代码,工资是: 25000
m.info() #② # 我是一个顾客,我的爱好是: IT产品,地址是广州
程序中①、②号代码都可正常运行,这正是程序对Manager重写的构造方法,分别调用了两个父类的构造方法来完成初始化的结果。
45.Python的动态性。为对象动态添加方法,只是对当前对象有效。如果希望为所有实例都添加方法,则可通过为类添加方法来实现。
class Cat:
def __init__(self, name): # 只定义了构造方法
self.name = name
def walk_func(self):
print('%s慢慢地走过一片草地' % self.name)
d1, d2 = Cat('Garfield'), Cat('Kitty') # 序列解包
#d1.walk() # 因为d1中不存在walk方法,引发AttributeError
Cat.walk = walk_func # 为Cat动态添加walk()方法,自动绑定self
#d1、d2调用walk()方法
d1.walk() d2.walk()
46.Python动态性的隐患。程序定义好的类,完全有可能在后面被其他程序修改,这就带来了一些不确定性。如果程序要限制为某个类动态添加属性和方法,则可通过__slots__属性来指定。
47.__slots__属性的值是一个元组,该元组的所有元素列出了该类的实例允许动态添加的所有属性名和方法名。
class Dog:
__slots__ = ('walk', 'age', 'name') # 只允许动态为实例添加这3种方法
def __init__(self, name):
self.name = name
def test():
print('预先定义的test方法')
d = Dog('Snoopy')
from types import MethodType #打开types库中的MethonType
d.walk = MethodType(lambda self: print('%s正在慢慢地走' % self.name), d)
d.age = 5 # 此语句是允许的
d.walk() # 输出 Snoopy正在慢慢地走
#d.foo = 30 # 添加额外属性,引发AttributeError
# MethonType的作用,创建绑定的实例方法对象。
method(function, instance) #方法(函数,实例)
-
__slots__属性并不限制通过类来动态添加方法
Dog.bar = lambda self: print(‘abc’)
d.bar() # 输出 abc -
__slots__属性制定的限制只对当前类的实例起作用,对该类派生出来的子类不起作用。如果要限制子类的实例动态添加属性,需要在子类中定义__slots__属性,允许添加的范围是子类和父类的__slots__元组的和。
-
如何使用type()直接查看某个类的类型。
class Role:
pass
r = Role()
print(type®) # 查看变量r的类型 #<class ‘main.Role’>
print(type(Role)) # 查看Role类本身的类型 #<class ‘type’>
当程序使用class定义Role类时,也可以解释为定义了一个特殊的对象(type类的对象),并将该对象赋值给Role变量。因此,程序使用class定义的所有类都是type类的实例。
51.使用type()函数来动态创建类。
def fn(self):
print('fn函数')
Dog = type('Dog', (object,), dict(walk=fn, age=6)) # 使用type()定义Dog类
d = Dog() # 创建Dog对象
print(type(d)) # <class '__main__.Dog'>
print(type(Dog)) # <class 'type'>
d.walk() # fn函数
print(Dog.age) # 6
52.type(object_or_name,bases,dict)
参数一:创建的类名。
参数二:该类继承的父类集合。使用元组指定多个父类,一个父类许多一个逗号。
参数三:改字典对象为该类绑定的类变量和方法。
53.使用metaclass可以在创建类时动态修改类定义,使某一批类全部具有某种特征。
class ItemMetaClass(type): # 定义ItemMetaClass,继承type
# cls代表动态修改的类
# name代表动态修改的类名
# bases代表被动态修改的类的所有父类
# attr代表被动态修改的类的所有属性、方法组成的字典
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个cal_price方法
attrs['cal_price'] = lambda self: self.price * self.discount
return type.__new__(cls, name, bases, attrs)
程序定义了一个ItemMetaClass类,继承了type类,并重写了__new__方法,并为目标类动态添加了一个cal_price方法。
54.使用metaclass定义了Book类,程序同上。
class Book(metaclass=ItemMetaClass):
__slots__ = ('name', 'price', '_discount')
def __init__(self, name, price):
self.name, self.price = name, price
@property
def discount(self):
return self._discount
@discount.setter
def discount(self, discount):
self._discount = discount
b = Book("疯狂Python讲义", 89)
b.discount = 0.76
print(b.cal_price()) # 创建Book对象的cal_price()方法 # 67.64
55.多态。当同一个变量在调用同一个方法时,完全可能呈现出多种行为。
class Bird:
def move(self, field):
print('Brid类:%s' % field)
class Dog:
def move(self, field):
print('Dog类:%s' % field)
x = Bird()
x.move('Brid') # 调用x变量的move()方法 # Brid类:Brid
x = Dog()
x.move('Dog') # 调用x变量的move()方法 # Dog类:Dog
上述程序,同一变量x在执行同一个move方法时,由于x指向的对象不同,因此它呈现出不同的行为特征,这就是多态。 程序详见《讲义》P148
c = Canvas() c.draw_pic(Rectangle()) c.draw_pic(Triangle())
56.多态性是允许你将父对象设置成为一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。
57.检查类型。Python提供了如下两个函数来检查类型。
1)issubclass(cls, class_or_tuple):检查cls(类名)是否为后一个类或元组包含的多个类 中任意类的子类。
2)isinstance(obj, class_or_tuple):检查obj(变量)是否为后一个类或元组包含的多个 类中任意类的对象。
58.issubclass用于判断是否为子类,isinstance用于判断是否为该类或子类的实例。
hello = "Hello" # 定义一个字符串
print('"Hello"是否是str类的实例: ', isinstance(hello, str)) # 输出True
print('"Hello"是否是object类的实例: ', isinstance(hello, object)) # 输出True
print('str是否是object类的子类: ', issubclass(str, object)) # 输出True
print('"Hello"是否是tuple类的实例: ', isinstance(hello, tuple)) # 输出False
print('str是否是tuple类的子类: ', issubclass(str, tuple)) # 输出False
59.枚举类。实例有限且固定的类。比如季节类,只有四个对象。
60.使用Enum列出多个枚举值来创建枚举类。
import enum
#定义Season枚举类
Season = enum.Enum('Season', ('SPRING', 'SUMMER', 'FALL', 'WINTER'))
print(Season.SPRING) # 直接访问指定枚举 # Season.SPRING
print(Season.SPRING.name) # 访问枚举成员的变量名 # SPRING
print(Season.SPRING.value) # 访问枚举成员的值 # 1
Enum()函数的构造方法:第一个参数是枚举类的类名;
第二个参数是一个元组,用于列出所有的枚举值。
61.通过枚举变量名或枚举值访问指定枚举对象。
print(Season[‘SUMMER’]) # 根据枚举变量名访问枚举对象 # Season.SUMMER
print(Season(3)) # 根据枚举值访问枚举对象 # Season.FALL
62.枚举还提供了一个__members__属性,该属性返回一个dict字典,字典包含了该枚举的所有枚举实例。程序可通过遍历__members__属性来访问枚举的所有实例。
for name, member in Season.members.items(): # 遍历Season枚举的所有成员
print(name, ‘=>’, member, ‘,’, member.value)
63.如果要继承更复杂的枚举,则可通过继承Enum来派生枚举类,在这种方式下程序就可以为枚举额外定义方法了。 程序详见《讲义》P151
64.枚举构造器。枚举也是类,因此枚举也可以定义构造器。在定义枚举实例时必须为构造器参数设置值。例如如下程序:
import enum
class Gender(enum.Enum):
MALE , FEMALE= '男', '阳刚之力', '女', '柔顺之美'
def __init__(self, cn_name, desc):
self._cn_name, self._desc = cn_name, desc
@property
def desc(self):
return self._desc
@property
def cn_name(self):
return self._cn_name
print('FEMALE的name:', Gender.FEMALE.name) # 访问FEMALE的name
print('FEMALE的value:', Gender.FEMALE.value) # 访问FEMALE的value
print('FEMALE的cn_name:', Gender.FEMALE.cn_name) # 访问自定义的cn_name属性
print('FEMALE的desc:', Gender.FEMALE.desc) # 访问自定义的desc属性
第7章 异常处理
1.异常机制主要依赖于try、except、else、finally和raise五个关键字。
2.使用try…except捕获异常。
try:
… # 业务实现代码
except( Erro1, Erro2, …) as e:
alert # 警告,输出不合法
goto retry # 转到重试
如果在执行try块时出现异常,系统自动生成一个异常对象,该异常返回到解释器。解释器收到异常,并寻找处理该异常对象的except块。找不到捕获异常的except块,则运行时环境终止,解释器也将退出。
3.异常类的继承体系。Python所有的异常类的基类是BaseException,用户要实现自定义异常,应该继承BaseException的子类Exception,Excepton又包括多个Error类。
import sys
try:
a, b = int(sys.argv[1]), int(sys.argv[2])
c = a / b
print(“您输入的两个数相除的结果是:”, c )
except IndexError:
print(“索引错误:运行程序时输入的参数个数不够”)
4.多异常捕获。将多个异常类用圆括号括起来,中间用逗号隔开。(try块同上)
try:
…
except (IndexError, ValueError, ArithmeticError):
print(“程序发生了数组越界、数字格式异常、算术异常之一”)
except:
print(“未知异常”)
5.访问异常信息。
args:该属性返回异常的错误编码和描述字符串。
errno:该属性返回异常信息的错误编号。
strerror:该属性返回异常的描述字符串。
with_traceback():通过该方法可处理异常的传播轨迹信息。
6.访问异常对象,需要在单个异常类或异常类元组之后使用as,再加上变量对象。
def foo():
try:
fis = open("a.txt");
except Exception as e:
print(e.args) # (2, 'No such file or directory')
print(e.errno) # 2
print(e.strerror) # No such file or directory
foo()
7.else块,当try块没有出现异常块时,程序会执行else块。
8.放在else块中的代码所引发的异常不会被except块捕获。所以,如果希望某段代码的能被后面的except块捕获,这段代码应放在try块。如果希望某段代码的异常能向外传播,这段代码应该放在else块中。
9.使用finally回收资源。有些时候在try块里打开了一些物理资源(例如数据库连接、网络连接和磁盘文件等),这些物理资源都必须被显示回收。
10.为了保证一定能回收在try块中打开的物理资源,异常处理机制提供了finally块。在任何情况下,finally块总会被执行。Python完整的异常处理语法结构如下:
try:
… # 业务实现代码
except SubException as e:
… # 异常处理块1
except SubException2 as e:
… # 异常处理块2
else:
… # 正常处理块
finally:
… # 资源回收块
11.尽量避免在finally块里使用return或raise等导致方法中止的语句。
12.异常处理嵌套。在try块、except块或finally块中包含完整的异常处理流程。
13.使用raise引发异常。在程序中自行引发异常。
raise [ExceptionName[(reason)]] # reason原因,添加提示信息
raise:单独一个raise。引发当前上下文中捕获的异常,或默认引发RuntimeError异常。
raise + 异常类:该语句引发指定的异常对象。
raise + 异常对象:引发指定的异常对象。
14.上面三种用法最终都是要引发一个异常实例,raise语句每次只能引发一个异常实例。
def main():
try: # 使用try...except来捕捉异常
mtd(3) # 此时即使程序出现异常,也不会传播给main函数
except Exception as e:
print('程序出现异常:', e)
mtd(3) # 不使用try...except捕捉异常,异常会传播出来导致程序中止
def mtd(a):
if a > 0:
raise ValueError("a的值大于0,不符合要求")
# 输出结果:ValueError: a的值大于0,不符合要求 # 红字显示
main()
15.自定义异常类。 class AuctionException(Exception): pass
16.except和raise同时使用。为了实现通过多个方法协作处理同一个异常的情形,可以在except块中结合raise语句来完成。
class AuctionException(Exception): pass #自定义异常类
class AuctionTest:
def __init__(self, init_price):
self.init_price = init_price # init_price起拍价
def bid(self, bid_price):
d = 0.0
try:
d = float(bid_price)
except Exception as e:
print("转换出异常:", e) # 此处只是简单地打印异常信息
# 再次引发自定义异常
raise AuctionException("竞拍价必须是数值,不能包含其他字符!") # ①
if self.init_price > d:
raise AuctionException("竞拍价比起拍价低,不允许竞拍!")
def main():
at = AuctionTest(20.4)
try:
at.bid("df")
except AuctionException as ae:
# 再次捕获到bid()方法中的异常,并对该异常进行处理
print('main函数捕捉的异常:', ae)
main()
17.如果程序需要将原始信息异常的详细信息直接传播出去,可以用自定义异常对原始异常进行包装,将①号的代码改成如下形式:(异常包装或异常转译)
raise AuctionException(e) # 把原始异常e包装成了AuctionException异常
18.raise可以不需要参数。此时raise将会自动引发当前上下文激活的异常;否则,通常引发RuntimeError异常。
将①号的代码改成raise,不写入参数,那么raise语句会再次引发改except块所捕获的ValueError异常。
19.异常传播轨迹。异常对象提供了一个with_traceback用于处理异常的传播轨迹,查看异常的传播轨迹可追踪异常触发的源头。
20.当程序运行时,经常会发生一系列函数或方法调用,从而形成“函数调用栈”。异常的传播则相反;只要异常没有被完全捕获,异常就从发生异常的函数或方法逐渐向外传播,首先传给该函数或方法的调用者,该调用者再传给其他调用者,最后传给解释器,此时解释器会中止该程序,并打印异常的传播轨迹信息。
21.traceback模块,用来处理异常传播轨迹。traceback模块提供了如下两种常用方法。
traceback.print_exc():将异常传播轨迹信息输出到控制台或指定文件中。
format_exc():将异常传播信息转换为字符串。、
22.成功的异常处理实现应该实现的目标:1)使程序代码混乱最小化;2)捕获并保留诊断信息;3)通知合适人员;4)采用合适的方式结束异常活动。
23.不要过度使用异常。异常处理机制的初衷是将不可预期异常的处理代码和正常的业务逻辑处理代码分离,因此绝对不要使用异常处理来代替正常的业务逻辑判断。
24.不要使用过于庞大的try块。将大块的try块分割成多个可能出现异常的程序段落,并把它们放在单独的try块中,从而分别捕获并处理异常。
25.不要忽略捕获到的异常。处理异常;重新引发异常;在合适的层处理异常。
第8章 Python类的特殊方法
1.Python类中的特殊方法、特殊属性有些需要开发者重写,有些规则可以直接调用,掌握这些常见的特殊方法、特殊属性也是最重要的。
2.repr()是Python类中的一个特殊方法,由于object类已提供了该方法,而所有的Python类都是object类的子类,因此所有Python对象都具有__repr__()方法。
3.repr()是一个“自我描述”的方法。例如当程序员该对象时,系统将会输出对该对象的“自我描述”信息。返回值:类名 + object at + 内存地址
例如:<main.Item object at 0x000002AF6D163518>
4.如果用户需要自定义类能实现“自我描述”的功能,就必须重写__repr__()方法
class Apple:
def init(self, color, weight): # 实现构造器
self.color, self.weight = color, weight;
# 重写__repr__()方法,用于实现Apple对象的“自我描述”
def repr(self):
return “Apple[color=” + self.color +", weight=" + str(self.weight) + “]”
a = Apple(“红色” , 5.68)
print(a) # 打印Apple对象 Apple[color=红色, weight=5.68]
5.构造方法__del__()用于销毁Python对象。当程序不再需要一个Python对象时,系统必须把该对象所占用的内存空间释放出来,这个过程称为垃圾回收。
6.只有当对象的引用计数变为0时,该对象才会被回收。如果一个对象有多个变量引用它,那么del其中一个变量是不会回收该对象的。
7.对象的__dir__方法用于列出该对象内部的所有属性(包括方法)名,该方法将会返回包含所有属性(方法)名的序列。如下程序示范了__dir__()方法的功能。
class Item:
def __init__ (self, name, price):
self.name, self.price = name, price
def info ():
pass
im = Item('鼠标', 29.8) # 创建一个Item对象,将之赋给im变量
print(im.__dir__()) # 返回所有属性(包括方法)组成列表
print(dir(im)) # 返回所有属性(包括方法)排序之后的列表
输出name、prince、info三个属性和方法,以及大量系统内置的属性和方法。
8.__dict__属性,用于查看对象内部存储的所有属性名和属性值组成的字典。程序使用__dict__属性既可查看对象的所有内部状态,也可通过字典语法来访问或修改指定属性的值。
im = Item('鼠标', 28.9) # 部分程序同上
print(im.__dict__) # ① # {'name': '鼠标', 'price': 28.9}
print(im.__dict__['price']) # 通过__dict__访问price属性
im.__dict__['name'] = '键盘' # 修改name
print(im.name) # 键盘
程序中①号代码,直接输出对象内部存储的所有属性名和属性值组成的divt对象。
9.当程序操作(访问、设置、删除)对象的属性时,系统同样会执行该对象特定的方法。
getattribute(self, name):当程序访问对象的name属性时被自动调用;
getattr(self, name):当程序访问对象的name属性且该属性不存在时被自动调用;
setattr(self, name, value):当程序对对象的name属性赋值时被自动调用;
delattr(self, name):当程序删除对象的name属性时被自动调用。
10.程序可通过重写这些方法来“合成”属性。
class Rectangle:
def __init__ (self, width, height):
self.width, self.height = width, height
def __setattr__(self, name, value): # 设置name属性
if name == 'size':
self.width, self.height = value
else:
self.__dict__[name] = value
def __getattr__(self, name): # 读取name属性
if name == 'size':
return self.width, self.height
else:
raise AttributeError
def __delattr__(self, name): # 删除name属性
if name == 'size':
self.__dict__['width'], self.__dict__['height'] = 0, 0
11.与反射相关。如果程序在运行过程中要动态判断是否包含某个属性(包括方法),甚至要动态设置某个属性值,可以通过Python的反射来支持实现。
12.在动态检查对象是否包含某些属性(包括方法)相关的函数如下:
hasattr(obj, name):检查obj对象是否包含名为name的属性或方法;
getattr(object, name[,default]):获取object对象中名为name的属性的属性值;
setattr(obj, name, value, /):将obj对象的name属性设为value。
13.通过以上函数来动态操作Python对象的属性。
class Comment:
def __init__ (self, detail, view_times):
self.detail, self.view_times = detail, view_times
def info ():
print("一条简单的评论,内容是%s" % self.detail)
c = Comment('疯狂Python讲义很不错', 20)
#判断是否包含指定的属性或方法
print(hasattr(c, 'detail'),hasattr(c, 'view_times'),hasattr(c, 'info')) # True True True
#获取指定属性的属性值
print(getattr(c, 'detail'), getattr(c, 'view_times')) # 疯狂Python讲义很不错 20
#print(getattr(c, info, '默认值')) # 由于info是方法,错误,name 'info' is not defined
setattr(c, 'detail', '天气不错') # 为指定属性设置属性值
setattr(c, 'view_times', 32)
print(c.detail, c.view_times) # 输出重新设置后的属性值 # 天气不错 32
14.程序使用setattr()函数可改变Python对象的属性值;使用该函数设置的属性不存在时,则添加该属性,反正Python是动态语言。
setattr(c, ‘test’, ‘新增的测试属性’) # 设置不存在的属性,即为对象添加属性
print(c.test) # 新增的测试属性
15.在使用setattr()函数重新设置对象的方法时,新设置的方法是未绑定方法。
def bar ():
print('一个简单的bar方法')
setattr(c, 'info', bar) # 将c的info方法设为bar函数
c.info() # 一个简单的bar方法
-
程序可通过判断属性(或方法)是否包含__call__属性来确定它是否可调用。
print(hasattr(c.detail, ‘call’)) # False # 不可调用
print(hasattr(c.view_times, ‘call’)) # False
print(hasattr(c.info, ‘call’)) # True -
实际上x(arg1, arg2, …)只是x.call(arg1, arg2, …)的快捷写法,因此可以为自定义类添加__call__方法,从而使得该类的实例也变成可调用的。
class Role:
def init (self, name):
self.name = name
def call(self):
print(‘执行Role对象’)
r = Role(‘管理员’)
r() # 直接调用Role对象,就是调用该对象的__call__方法 # 执行Role对象 -
与序列相关。序列最重要的特征就是可包含多个元素。
len(self):该方法的返回值决定序列中元素的个数。
getitem(self, key):获取指定索引对应的元素。key应该是整数值或slice对象(切片)。
contains(self, key, value):该方法判断序列是否包含指定元素。
setitem(self, key, value):该方法设置指定索引对应的元素。
delitem(self, key):该方法删除指定索引对应的元素。 -
如果程序要实现不可变序列(只能读取),只要实现上面的3个方法就行;如果程序要实现可变序列(可修改),则需要实现上面5个方法。
下面的程序会实现一个字符串序列,在该序列中默认每个字符的长度都是3,该序列按AAA、AAB、AAC…(26进制数)这种格式排序。def check_key (key): # 检查序列的索引
if not isinstance(key, int): raise TypeError(‘索引值必须是整数’)
if key < 0: raise IndexError(‘索引值必须是非负整数’)
if key >= 26 ** 3: raise IndexError(‘索引值不能超过%d’ % 26 ** 3)
class StringSeq:
def init(self):
self.__changed = {} # 用于存储被修改的数据
self.__deleted = [] # 用于存储已删除元素的索引
def len(self):
return 26 ** 3
def getitem(self, key): # 根据索引获取序列中元素
check_key(key)
if key in self.__changed: # 如果在self.__changed中找到已经修改后的数据
return self.__changed[key]
if key in self.__deleted: # 如果key在self.__deleted中,说明该元素已被删除
return None
three = key // (26 * 26) # 否则根据计算规则返回序列元素
two = ( key - three * 26 * 26) // 26
one = key % 26
return chr(65 + three) + chr(65 + two) + chr(65 + one) # 数字转字母
def setitem(self, key, value): # 根据索引修改序列中元素
check_key(key)
self.__changed[key] = value
# 将修改的元素以key-value对的形式保存在__changed中
def delitem(self, key): # 根据索引删除序列中元素
check_key(key)
# 如果__deleted列表中没有包含被删除key,添加被删除的key
if key not in self.__deleted : self.__deleted.append(key)
# 如果__changed中包含被删除key,删除它
if key in self.__changed : del self.__changed[key]
sq = StringSeq() # 创建序列
print(len(sq)) # 获取序列的长度,len()方法的返回值 # 17576
print(sq[26*26]) # BAA
print(sq[1]) # 打印没修改之后的sq[1] # ‘AAB’
sq[1] = ‘fkit’ # 修改sq[1]元素
print(sq[1]) # 打印修改之后的sq[1] # ‘fkit’
del sq[1] # 删除sq[1]
print(sq[1]) # 再次打印sq[1] # None
sq[1] = ‘crazyit’ # 再次对sq[1]赋值
print(sq[1]) # crazyit
该序列本身并不保存序列元素,序列会根据索引动态就算序列元素,因此该序列元素需要保存被修改、被删除的序列元素。
20. 实现迭代器。for-in循环遍历列表、元组和字典等,这些对象都是可迭代的,因此都属于迭代器。如果开发者需要实现迭代器,只要实现如下两个方法即可。
iter(self):该方法返回一个迭代器(irerator),该迭代器中包含一个__next__()用来返回下一个元素。
reversed(self):该方法主要为内建的reversed()反转函数提供支持,反转指定的迭代器。
21. 定义一个代表斐波那契数列的迭代器。(数列的元素等于前两个元素之和)
class Fibs: # 定义一个代表斐波那契数列的迭代器
def __init__(self, len):
self.first, self.sec = 0, 1 # 第1个数和第2个数
self.__len = len # 序列长度
def __next__(self): # 定义迭代器所需的__next__方法
if self.__len == 0: # 如果__len__属性为0,结束迭代
raise StopIteration
self.first, self.sec = self.sec, self.first + self.sec # 完成数列计算
self.__len -= 1 # 数列长度减1
return self.first
def __iter__(self): # 定义__iter__方法,该方法返回迭代器
return self
fibs = Fibs(10) # 创建Fibs对象
print(next(fibs)) # 获取迭代器的下一个元素
for el in fibs: # 使用for循环遍历迭代器
print(el, end=' ')
上面的程序定义了一个__iter__()方法,该方法返回self,因此它要求该类必须提供__next__()方法,用来获取迭代器的下一个元素。程序使用__len属性控制数列的剩余长度,当__len为0时,程序停止遍历。
22. 程序可以使用内置的iter()函数将列表、元组等转换成迭代器。
my_iter = iter([2, ‘fkit’, 4]) # 将列表转换为迭代器
print(my_iter.next()) # 2 # 依次获取迭代器的下一个元素
print(my_iter.next()) # fkit
23. 扩展系统已有的列表、元组或字典。如果程序明确需要一个特殊的列表、元组或字典类,程序只要继承系统已有的列表、元组或字典类,然后重写或新增方法即可。
下面的程序将会示范开发一个新的字典类,这个字典类可根据value来获取key。由于字典中value是可以重复的,因此该方法会返回指定value对应的全部key组成的列表。
class ValueDict(dict): # 定义ValueDict类,继承dict类
def __init__(self, *args, **kwargs): # 定义构造函数
super().__init__(*args, **kwargs) # 调用父类的构造函数
def getkeys(self, val): # 新增getkeys方法
result = []
for key, value in self.items(): # items,获取所有键值对
if value == val: result.append(key)
return result
my_dict = ValueDict(语文 = 92, 数学 = 89, 英语 = 92)
print(my_dict.getkeys(92)) # ['语文', '英语']
my_dict['编程'] = 92
print(my_dict.getkeys
(92)) # [‘语文’, ‘英语’, ‘编程’]
24. 生成器(generator)。生成器也会提供__next__()方法,使用for循环来遍历生成器。生成器与迭代器的区别在于:迭代器是先定义一个包含yield语句的函数,然后通过调用该函数来创建生成器。
25. 创建生成器:1)定义一个包含yield语句的函数;2)调用第1步创建的函数得到生成器。
def test(val, step): # 此类使用生成器来定义一个差值递增的数列。
print("--------函数开始执行------")
cur = 0
for i in range(val): # 遍历0~val
cur += i * step # cur添加i*step
yield cur
-
yield cur语句的作用:1)每次返回一个值,有点类似于return语句;2)冻结执行,程序每次执行到yield语句就会被暂停。
在程序被yield语句冻结之后,当程序再次调用next()函数获取生成器的下一个值时,程序才会继续向下执行。在调用包含yield语句的函数并不会立即执行,它只是返回一个生成器。只有当程序通过next()函数调用生成器或遍历生成器时,函数才会真正执行。 -
保留上面函数中的yield cur语句,执行如下程序。(类定义同上)
t = test(10, 2) # 程序不会立即执行test()函数
print(next(t)) # 0,生成器“冻结”在yield处 # 获取生成器的第一个值
print(next(t)) # 2,生成器再次“冻结”在yield处
for ele in t: # 使用for循环遍历生成器,相当于不断的使用next()函数来获取生成器下一个值
print(ele, end=’ ') # 6 12 20 30 42 56 72 90 -
将生成器转换成列表和元组。
t = test(10, 1) # 再次创建生成器
print(list(t)) # 将生成器转换成列表 [0, 1, 3, 6, 10, 15, 21, 28, 36, 45]
t = test(10, 3) # 再次创建生成器
print(tuple(t)) # 将生成器转换成元组 (0, 3, 9, 18, 30, 45, 63, 84, 108, 135) -
Python主要提供了两种方法来创建生成器:
1)使用for循环的生成器推导式;2)调用带yield语句的生成器函数。 讲义P188 -
生成器可以按需、逐个返回数据,减少占用的内存。
-
为了实现生成器与“外部程序”动态地交换数据,需要借助于生成器的send()方法,与next()方法一样,用于获取生成器所生成的下一个值,并将生成器“冻结”在yield语句处。但send()方法可以接收一个参数,该参数值会被发送给生成器函数。
在程序内部,程序可通过yield表达式来获取send()方法所发送的值。在第一次调用send()函数时,只能为该方法传入None参数。
1)外部程序通过send()方法发送数据;2)生成器函数使用yield语句接收数据。 -
只有当生成器被“冻结”之后,外部程序才能使用send()方法向生成器发送数据。获取生成器第一次所生成的值,应该使用next()函数;如果使用send()方法获取,应为该方法传入None参数。
def square_gen(val):
i = 0
out_val = None
while True:
# 使用yield语句生成值,使用out_val接收send()方法发送的参数值
out_val = (yield out_val ** 2) if out_val is not None else (yield i ** 2)
# 如果程序使用send()方法获取下一个值,out_val会获取send()方法的参数
if out_val is not None : print("====%d" % out_val)
i += 1
sg = square_gen(5)
print(sg.send(None)) # 0 # 第一次调用send()方法获取值,只能传入None作为参数
print(next(sg)) # 1
#调用send()方法获取生成器的下一个值,参数9会被发送给生成器
print(sg.send(9)) # ====9 \n 81
print(next(sg)) # 9 # 再次调用next()函数获取生成器的下一个值 -
close():用于停止生成器 sg.close()
throw():用于在生成器内引发(yield语句)引发一个异常。 sg.throw() -
运算符重载的特殊方法。Python允许为自定义类提供特殊方法,这样可以让自定义类的对象也支持各种运算符的运算。 此处不做介绍,讲义P191。
例如修改object.add(self, other),使两个Rectangle类执行加法运算。class Rectangle:
def init(self, width, height):
self.width = width
self.height = height
def setSize (self , size): # 定义setSize()函数
self.width, self.height = size
def getSize (self): # 定义getSize()函数
return self.width, self.height
size = property(getSize, setSize) # 使用property定义属性(读,写,删,说明)
def add(self, other): # 定义__add__方法,该对象可执行+运算
# 要求参与+运算的另一个运算数必须是Rectangle
if not isinstance(other, Rectangle): #isinstance返回对象是否是类或子类的实例
raise TypeError(’+运算要求目标是Rectangle’) # 引发错误
return Rectangle(self.width + other.width, self.height + other.height)
def repr(self): # 进行“自我描述”
return ‘Rectangle(width=%g, height=%g)’ % (self.width, self.height)
r1, r2 = Rectangle(4, 5), Rectangle(3, 4)
print(r1) #Rectangle(width=4, height=5)
r = r1 + r2 # 对两个Rectangle执行加法运算
print® # Rectangle(width=7, height=9)