python自动化(一)基础能力:3.python基础上之数据类型,函数
写在前面的话:本章内容为本人学习大神“”的“全栈”博客后,整理总结所得。如果想要深入学习可以看看大神的博客。链接如下:
一.数据类型详解 1.字符串 1.1 定义及格式
a = 'hello world'
b = "hello world"
c = "hello 'world'"
d = 'hello "world"'
1.2 字符串常用操作
a = 'hello world'
print(a[0]) # 获取字符串的第一个元素
print(a[1]) # 获取字符串的第二个元素
print(a[-1]) # 获取字符串的最后一个元素
运行结果如下:
用len()函数。
name='xiaomingasoiffdh'
print(len(name))
用s*20即可将字符串s打印20编。
用in来判断一个字符串是否在另一个字符串中,如用a in b来判断a是否包含于b。如果包含则返回True,否则返回Flase
用max()和min()函数。
用ord()函数,如print(ord('A'))。
用split()函数,返回列表。
如
name='my_name_is_haha'
a = name.split('_') # 表示以'_'来分割字符串,返回分割后的列表
print(a)
运行结果如下:
用join()方法,如'_'.join(s)。
a = ['my', 'name', 'is', 'haha']
s = '_'.join(a) # 使用'_'来将列表中的元素拼接为字符串
print(s)
运行结果如下:
strip()去掉字符串左右两边的空格;
()去掉字符串左边的空格;
()去掉字符串右边的空格。
如
s=' I love Python '
print(s.strip())
print(s.lstrip())
print(s.rstrip())
upper()全部大写;
lower()全部小写;
()首字母大写;
()判断是否大写;
()判断是否小写。
例如:
s='I love Python'
print(s.upper())
print(s.capitialize())
print(s.islower())
2.列表 2.1 定义及格式
列表是中最基本也是最常用的数据结构之一,列表中的每一个元素被分配一个数字作为索引(即下标),用来表示该元素在列表内所排的位置,第1个元素索引是0,第2个索引是1,依次类推。
列表是一个有序可重复的集合。
names=["李大钊","陈独秀","蔡元培","陈佩斯"]
#注意:比C语言中的数组功能更强大,列表中的元素可以为不同类型
list1=[10,"人",2.5,True]
2.2列表的创建
创建列表:
2.3访问列表内的元素
lst = [1,2,3]
num = lst[1]
print(num)
会打印出2。
2.4 修改元素的值
lst = [1,2,3,4,5]
lst[1] = 'hello'
print(lst)
打印结果如下:
[1, 'hello', 3, 4, 5]
注意:不能赋值给列表中不存在的索引。
2.5.删除值 2.6 列表组合
直接将两个列表相加,是列表的拼接,不是对应位置相加。
l1 = [1,2,3]
l2 = [4,5,6]
l3 = l1 + l2
print(l3)
打印:
[1, 2, 3, 4, 5, 6]
也可用
l3 = l1.(l2),此为魔法方法,为面向对象的方法。
2.7列表的乘法
l4 = l1 * 3
相当于
l4 = l1.__mul__(3)
是将列表l1重复3次,成为一个列表;
两个列表不能直接相乘,否则会报错。
2.8判断元素是否在列表中
用关键词in:
print(2 in l1)
2.9迭代列表中的每个元素
for i in l1:print(i)
2.10 列表长度
l1 = [1,2,3]
print(len(l1))
相当于
print(l1.__len__())
2.11 最大值与最小值
l1 = [1,2,3]
print(max(l1),min(l1))
2.12 类型转换
lst = list('str')
1
属于强制类型转换
print(type(lst))
1
type()函数可以查看变量的类型。
2.13 列表排序 (1)反转
lst = [1,3,2,4]
lst.reverse()
print(lst)
打印出
[4, 2, 3, 1]
1
很显然,()函数对列表进行了改变,不再是原来的列表。
(2)排序
lst.sort()
为默认升序
lst.sort(reverse=True)
1
为降序
排序只能对元素为同种类型的值进行排序,不能对不同类型的值进行排序,要么全为数值型,要么均为字符串。
2.14 切片
切片即为对序列进行截取,选取序列中的某一段。
语法为:
list[start : end : step]
step为步长,区间为左闭右开。
lis = ['a','b','c','d','e']
print(lis[1:3])
打印
['b', 'c']
2.15 列表的内置方法 (1)()
添加一个元素到末尾
lis = ['a','b','c','d','e']
lis.append('e')
print(lis)
打印
['a', 'b', 'c', 'd', 'e', 'e']
如参数为列表,则列表作为一个元素添加到之前的列表末尾。
(2)count()
对传入的元素进行计数
lis = ['a','b','c','d','e']
print(lis.count('a'))
结果为1。
(3)()
在列表末尾一次性追加另一个序列中的多个值(用新序列扩展原来的列表)
lis = ['a','b','c','d','e']
lis.extend(['f','g'])
print(lis)
打印出
['a', 'b', 'c', 'd', 'e', 'f', 'g']
(4)()
根据索引的位置添加元素
lis = ['a','b','c','d','e']
lis.insert(2,'g')
print(lis)
打印出
['a', 'b', 'g', 'c', 'd', 'e']
(5)copy()
浅复制
lis = ['a','b','c','d','e']
lst = lis.copy()
print(lst)
相当于lst = lis[:]
(6)clear()
清空列表
3.元组 3.1 元组概念
Python的元组与列表类似,不同之处在于元组的元素不能修改,元组使用“()”
列表使用"[]"
3.2 元组操作
t=(1,2,3)
print(t[0])
修改或者删除元素的话会报错:
t[0]= 10
删除报错:
t.pop()
但是可以变向进行删除
tuple->list(进行删除操作)->tuple
3.3 可变对象与不可变对象
中,不可变类型包括:
int 、float、 字符串str 、元组tuple、bool。
可变类型包括:
列表list、集合set、字典dict。
每个对象在内存中保存了3个数据:
id(标识)
type(类型)
value(值)
举例:
列表a = [1,2,3],在内存中的一定区域,会分别保存一个对象的id(假设为0x111)、type(class list)、value([1,2,3])。
对序列中的元素进行改变即对值进行改变。
修改前:
a = [1,2,3]
print(a,id(a))
打印
[1, 2, 3] 1843535938056
改对象后:
a[0] = 10
print(a,id(a))
打印
[10, 2, 3] 1843535938056
显然,此时a对象未发生改变。
改变量后:
a = [4,5,6]
print(a,id(a))
打印
[4, 5, 6] 1384044909640
显然,此时a对象发生改变。
将a赋给新变量b:
a = [1,2,3]
b = a
b[0] = 10
print('a:',a,id(a))
print('b:',b,id(b))
打印
a: [10, 2, 3] 1963325084232
b: [10, 2, 3] 1963325084232
显示,a和b的值和id完全一致。
解释:
生成a时,a对应的值为id(假设为0x111)、type(class list)、value([1,2,3]),
将a赋给b时,其实只是将b指向了a对应的内存对象的内存地址,并未分配新的内存空间给b,a和b共用一个内存地址,所以a和b打印出来的结果一致。
a = [1,2,3]
b = a
b = [10,2,3]
print('a:',a,id(a))
print('b:',b,id(b))
打印
a: [1, 2, 3] 2260109775432
b: [10, 2, 3] 2260109775944
此时b和a不一致,因为b是通过重新赋值得到的新列表,系统会重新分配一个内存地址,所以id会不同。
3.4 ==和is
==和!=比较的是对象的值是否相等;
is和is not比较的是对象的id是否相等。
a = [1,2,3]
b = [1,2,3]
print(a == b)
print(a is b)
打印
True
False
!=和is not的用法类似。
4.字典 4.1.定义
字典即dict,是一种新的数据结构,也可以叫映射()。
字典的作用:
存储对象的容器。
列表存储数据性能很好,但是查询数据性能很差;
字典中每一个元素都有唯一的名字,通过这个名字可以快速查找到指定的元素,这个唯一的名字称为键(key),通过key可以查找到值(value),所以字典也称为键值对(key-value),每个字典可以有多个键值对,每个键值对称为一项。
字典中键和值的类型:
值可以是任意对象;
键可以是任意不可变的对象,包括int、str、bool、tuple等。
4.2 创建字典 (1)用{}
语法:{key1:,key2:,...}。
d = {'name':'Tom','age':'20','gender':'male'}
print(d,type(d))
打印
{'name': 'Tom', 'age': '20', 'gender': 'male'} <class 'dict'>
字典的键不能重复,如有重复,则后边的会覆盖前边的。
如
d = {'name':'Tom','age':'20','gender':'male','name':'Jerry'}
print(d,type(d))
打印
{'name': 'Jerry', 'age': '20', 'gender': 'male'} <class 'dict'>
即前边的Tom被覆盖。
字典还可以跨行写,如
d = {'name':'Tom','age':'20','gender':'male'}
print(d,type(d))
输出与之前相同。
(2)用dict()函数
方式一:
d = dict(name='Tom',age=20,gender='male')
print(d)
打印
{'name': 'Tom', 'age': 20, 'gender': 'male'}
方式二:
d = dict([('name','Tom'),('age',20)])
print(d,type(d))
打印
{'name': 'Tom', 'age': 20} <class 'dict'>
解释:
dict()函数可以将一个包双值子序列转化为字典。
双值序列:即序列中只有两个值,如[3,4]、('name','hello)等。
子序列:如果序列中的元素也是序列,称这个元素为子序列,如[(1,2)]即为子序列。
4.3 根据键来获取值
d = {'name':'Tom','age':'20','gender':'male'}
print(d['name'],d['age'],d['gender'])
打印
Tom 20 male
4.4 字典的常见用法 len()
获取字典的长度即字典中键值对的个数。
d = dict([('name','Tom'),('age',20)])
print(len(d))
结果为2。
in、not in
检查字典中是否含有或不含有指定的键。
d = dict([('name','Tom'),('age',20)])
print('hello' in d)
打印
False
获取字典里面的值
语法:d[key]。
d = dict([('name','Tom'),('age',20)])
print(d['name'])
打印
Tom
如将键赋值给一个变量,则通过变量访问时不需要引号,如
d = {'name':'Tom','age':'20','gender':'male'}
b = 'name'
print(d[b])
打印
Tom
如果键不存在会报错,即:
d = {'name':'Tom','age':'20','gender':'male'}
print(d['hello'])
打印
KeyError: 'hello'
1
为避免抛出异常,可以用get()方法
语法:get([key,])。
根据键来获取字典中的值,也可以指定一个默认值,作为第二个参数,这样当获取不到键的时候会返回默认值。
如
d = {'name':'Tom','age':'20','gender':'male'}
print(d.get('hello','no such key'))
打印
no such key
修改字典
语法:d[key] = value。
如果存在则覆盖,不存在则添加。
d = {'name':'Tom','age':'20','gender':'male'}
d['name'] = 'Jerry'
d['phone'] = '010-11111111'
print(d)
打印
{'name': 'Jerry', 'age': '20', 'gender': 'male', 'phone': '010-11111111'}
()函数
可以向字典当中添加key-value:
如果key已经存在于字典中,则返回key对应的值,不会对字典做任何操作;
如果key不存在,则向字典中添加这个key,并设置value。
key存在时:
d = {'name':'Tom','age':'20','gender':'male'}
result = d.setdefault('name','Jerry')
print(d,result)
打印
{'name': 'Tom', 'age': '20', 'gender': 'male'} Tom
1
key不存在时:
d = {'name':'Tom','age':'20','gender':'male'}
result = d.setdefault('phone','010-11111111')
print(d,'\n',result)
打印
{'name': 'Tom', 'age': '20', 'gender': 'male', 'phone': '010-11111111'} 010-11111111
()方法
将其他字典中的key-value添加到当前的字典中
d1 = {'a':1,'b':2,'c':3}
d2 = {'d':4,'e':5,'f':6}
d1.update(d2)
print(d1)
打印
{'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5, 'f': 6}
如存在相同的键,则覆盖:
d1 = {'a':1,'b':2,'c':3}
d2 = {'b':4,'e':5,'f':6}
d1.update(d2)
print(d1)
打印
{'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
删除 浅复制(浅拷贝)
copy():用于将字典浅复制,即创建已有字典的副本。
d = {'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
d2 = d
print(d,id(d))
print(d2,id(d2))
打印
{'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 1997496449288
{'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 1997496449288
d和d2指向同一个对象。
当d中的值改变时,d2中的值也随之改变。
d = {'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
d2 = d
d['a'] = 7
print(d,id(d))
print(d2,id(d2))
打印
{'a': 7, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2121296378120
{'a': 7, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2121296378120
用copy()方法:
d = {'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
d2 = d.copy()
print(d,id(d))
print(d2,id(d2))
打印
{'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 3102906251528
{'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 3102906251608
d和d2指向不同对象。
当d中的值改变时,d2中的值不会改变。
d = {'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
d2 = d.copy()
d['a'] = 7
print(d,id(d))
print(d2,id(d2))
打印
{'a': 7, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2430767003912
{'a': 1, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2430767003992
当字典的值为字典时,
d = {'a': {'name':'Tom','age':'20','gender':'male'}, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
d2 = d.copy()
print(d,id(d))
print(d2,id(d2))
打印
{'a': {'name': 'Tom', 'age': '20', 'gender': 'male'}, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2220628113672
{'a': {'name': 'Tom', 'age': '20', 'gender': 'male'}, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2220628114392
当d中的值的值改变时,如
d = {'a': {'name':'Tom','age':'20','gender':'male'}, 'b': 4, 'c': 3, 'e': 5, 'f': 6}
d2 = d.copy()
d['a']['name'] = 'Jerry'
print(d,id(d))
print(d2,id(d2))
打印
{'a': {'name': 'Jerry', 'age': '20', 'gender': 'male'}, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2104435669336
{'a': {'name': 'Jerry', 'age': '20', 'gender': 'male'}, 'b': 4, 'c': 3, 'e': 5, 'f': 6} 2104435670056
复制:即创建已有对象的副本,则原对象改变,副本应该不发生改变,这才是正常的复制。
修改d时,d中的’a’下的’name’的值也发生了改变,此即浅复制,只会复制字典本身,字典中还有字典不会被复制。
总结:浅复制只会复制字典本身,如果字典中还有字典,是不会被复制的。
4.5 遍历字典 keys()
该方法会返回字典所有的键。
d = {'name':'Tom','age':'20','gender':'male'}
for key in d.keys():print(d[key])
打印
Tom
20
male
2.()
返回一个序列,保存字典的值。
d = {'name':'Tom','age':'20','gender':'male'}
for value in d.values():print(value)
打印
Tom
20
male
3.items()
返回字典中所有的项,会返回一个序列,序列中包含双值子序列,双值分别为字典中的key和value。
d = {'name':'Tom','age':'20','gender':'male'}
for key,value in d.items():print(key,'=',value)
打印
name = Tom
age = 20
gender = male
二.函数 1.函数介绍
一般情况下,某段代码需要反复使用多次,
而且这段代码又具备特定的功能,
我们会把这段代码组织成为单独的功能模块,
这个功能模块就可以叫做函数
语法格式:
def 函数名():代码块...
调用格式:
函数名()
练习 封装函数打印99乘法表:
# 函数的定义
def show99():i = 1while i < 10:j = 1while j <= i:print("%d * %d = %d\t"%(j,i,i*j),end=" ")j += 1i += 1print("")
# 函数的调用,只有调用函数,函数中的代码才会被执行
show99()
运行结果如下:
2.函数的参数上
语法格式:
def 方法名(形参...):代码块(带代码中,形参可以被理解为定义好的一个变量)
方法名(实际参数)
注意:参数的个数要匹配
def add_three(name,age):print(f"my name is {name}, I am {age} years old")add_three('zhangsan',18)
运行结果如下:
3.函数的参数下 3.1位置参数
def sum_a(a,b):"""求和函数"""c = a + breturn c
print(sum_a(5,8))
调用时,按照位置将实参与形参一一对应。5对应a,8对应b
3.2关键字参数
def sum_a(a,b):"""求和函数"""c = a + breturn c
print(sum_a(b=5,a=8))
调用函数时,使用关键字将实参与形参一一对应。这样参入参数时的位置可以颠倒
3.3默认值参数
def sum_a(a,b=3):"""求和函数"""c = a + breturn c
print(sum_a(1))
在定义形参时,可以给参数设置一个默认值。注意,默认值参数必须在最后面。sum_a(a=3,b)这样是错误的写法。当实参没有给默认值参数传值时,会使用默认值
3.4形参的*args,**
*args表示元组传参,**表示字典传参
def sum_a(*args):c = argsreturn c
print(sum_a(1,2))
结果:(1, 2)
def sum_a(**kwargs):"""求和函数"""c = kwargsreturn c
print(sum_a(a=1,b=2,c=3))
结果:{'a': 1, 'b': 2, 'c': 3}
一般形参设置为(*args,**)后,可以接受任何形式的实参
4.函数的返回值
有时候并不需要对结果进行打印,而是进行一些其他的处理,这时候就需要返回值。
返回值就是函数返回的结果。
返回值可以直接使用,也可以通过一个变量来接收函数返回值的结果。
如
def fn():return 100
r= fn()
print(r)
打印100。
后面可以跟任意的对象,甚至是一个函数。
如
def fn():def fn2():print('Python')return fn2
r= fn()
print(r)
打印.fn2 at >,即返回了一个函数。
又如
def fn():def fn2():print('Python')return fn2r= fn()
print(r)
打印
.fn2 at >
如果仅仅写一个(后边不加其他值)或者不写,相当于 None。
如
def fn2():return
r = fn2()
print(r)
打印None。
在函数中,后面的代码都不会执行,一旦执行函数自动结束。
def fn3():print('Hello')returnprint('ABC')
fn3()
打印Hello,没有打印出ABC。
和break的比较:
def fn4():for i in range(5):if i == 3:#break用于退出当前循环breakprint(i)print('Loop ends')
fn4()
打印
0
1
2
Loop ends
def fn4():for i in range(5):if i == 3:#return用来结束函数returnprint(i)print('Loop ends')
fn4()
打印
0
1
2
123
没有打印出Loop ends。
计算求和返回结果并运用:
def fn(*nums):#定义变量保存结果result = 0#便利元组,将元组中的元素累加for n in nums:result += nreturn resultr = fn(1,2,3,4)
print(r+6)
打印16
def fn5():return 10
print(fn5)
print(fn5())
打印出
<function fn5 at 0x000002997A442F78>
10
12
print(fn5)是函数对象,实际上是在打印函数对象;
print(fn5())是在调用函数,实际上是在打印fn5()函数的返回值。
5.四种函数的类型 6.局部变量
在函数内部定义,用来存储临时数据的变量,称为临时变量,形参也是局部变量
7.全局变量
#例:
a = 100
def test1():print(a)
def test2():print(a)
全局变量与局部变量同名问题
a = 100
def test():a = 200print(a)
test()
#这种写法是允许的,打印出的结果为“200”,访问的为离的近的
如果需要在函数内部修改全局变量,则需要使用关键字,来声明变量。
a = 20
def fn2():#声明函数内部使用a是全局变量,此时再修改a时,就是修改全局变量。global aa = 10print('Inside fn2(),a =',a)
fn2()
print('Outside fn2(),a =',a)
打印
Inside fn2(),a = 10
Outside fn2(),a = 10
8.命名空间
命名空间实际上就是一个字典,专门用来存储变量的字典。
函数()用来获取当前作用域的命名空间,返回的是一个字典,把所有的变量存到该字典中。
如果在全局作用域中,调用()则获取全局作用域命名空间,如果在函数作用域中调用()则获取函数命名空间。
s = locals()
print(s)
打印
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000016F7E71CB88>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'Demo.py', '__cached__': None, 's': {...}}
又如
a = 20
s = locals()
print(s)
print(s['a'])
打印
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000024D0E2DCB88>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'Demo.py', '__cached__': None, 'a': 20, 's': {...}}
20
向字典中添加一个键值对,即增加一个变量。
a = 20
s = locals()
print(s)
s['c'] = 200
print(s)
打印
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000022917BECB88>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'Demo.py', '__cached__': None, 'a': 20, 's': {...}}
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x0000022917BECB88>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'Demo.py', '__cached__': None, 'a': 20, 's': {...}, 'c': 200}
在函数中定义变量:
def fn4():a = 10s = locals()print(s)
fn4()
打印{'a': 10}
def fn4():a = 10s = locals()#可以通过操作s来操作函数的命名空间,但是不建议这么做!!s['b'] = 20print(s)
fn4()
打印{'a': 10, 'b': 20}
9.递归函数
在函数中不是调用其他而是调用自身,这种调用方式,称为递归调用
9.1 递归基本概念和使用
练习:尝试求出10的阶乘10!。
用循环:
result = 1
for i in range(1,11):result *= i
print(result)
打印。
要求:创建一个函数求任意数的阶乘:
def factorial(n):result = 1for i in range(1,n+1):result *= ireturn result
print(factorial(10))
打印。
递归:
即递归式函数;
递归简单说就是自己调用自己,递归式函数就是在函数中调用自己。
递归时解决问题的一种方式,思想是将大的问题拆分成小的问题,直到不能再拆分。
递归式函数的两个条件:
1.基线条件:
问题可以分解成最小的问题,当满足基线条件时,递归不再执行。
2.递归条件:
将问题继续分解的条件。
def recursion(n):#基线条件if n == 1:return 1#递归条件return n * recursion(n - 1)
print(recursion(10))
打印
9.2 递归练习
def exponentiation(n,i):#n为底数,i为指数#基线条件,指数i为1if i == 1:return n#递归条件return n * exponentiation(n,i - 1)
print(exponentiation(2,4))
打印16。
当然,还有更简单的方法
def exponentiation(n,i):return n ** i
print(exponentiation(2,4))
即运用自带的幂运算符。
回文字符串:即字符串从前往后看和从后往前看是一样的,如、abcba。
方法:先检查第一个字符和最后一个字符是否一致,如果不一致不是回文字符串,如果一致,则看剩余部分是不是回文串。
def palindrome(s):#基线条件,如果字符串长度小于2,则这个字符串是回文串if len(s) < 2:return Trueelif s[0] != s[-1]:return False#递归条件return palindrome(s[1:-1])
print(palindrome('Python'))
打印False。
10.匿名函数
用lambda关键字创建的简单的没有函数名的函数,这种函数不用def声明
语法格式:
lambda 参数... : 表达式
sum = lambda args1,args2: args1+args2
print(sum(1,2))
#结果为:3
11.()函数
内置函数(),参数中传入可迭代的结构,即(,),可以从序列中过滤出符合条件的元素,保存到一个新的序列中。
参数:传递实现过滤功能的函数;
参数:需要过滤的序列。
返回值:过滤后的新序列。
l = [1,2,3,4,5,6,7,8,9,10]
def tri(n):if n % 3 == 0:return True
print(list(filter(tri,l)))
打印出[3, 6, 9]。
12.map()函数:
可以对可迭代对象中的所有元素做指定操作,然后将其添加到一个新的可迭代对象并返回。
匿名函数一般都是作为参数使用,其他地方一般不用。
l = [1,2,3,4,5,6,7,8,9,10]
r = map(lambda i:i+1,l)
print(list(r))
打印[2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
13.sort()方法:
该方法用来对列表中的元素进行排序,直接默认比较列表中元素的大小。
在该方法中可以接受一个关键字参数,即一个函数作为参数。
l = [34,554,67,897,436]
l.sort()
print(l)
打印[34, 67, 436, 554, 897]
同时可传递参数来对字符串长度进行比较。
l = ['ssd','adsd','frfrrffr','frfrsfrfs','rd']
l.sort(key=len)
print(l)
打印['rd', 'ssd', 'adsd', '', '']。
还可以根据类型转换后的值进行比较:
l = ['34',554,'67',897,436]
l.sort(key=int)
print(l)
打印['34', '67', 436, 554, 897]
14.()函数:
返回一个新的列表。
l = ['34',554,'67',897,436]
l2 = sorted(l,key=int)
print(l)
print(l2)
打印
['34', 554, '67', 897, 436]
['34', '67', 436, 554, 897]
15.高级函数
特点:
1.接受一个或多个函数作为参数;
2.将函数作为返回值。
满足任意一个特点即为高级函数。
练习:定义一个函数,将指定列表中的所有偶数,保存到新的列表中返回。
l = [1,2,3,4,5,6,7,8,9,10]
def evenlist(lst):new_list = []for item in lst:#判断item的奇偶if item % 2 == 0:new_list.append(item)return new_list
print(evenlist(l))
打印[2, 4, 6, 8, 10]
用函数判断奇偶时:
l = [1,2,3,4,5,6,7,8,9,10]
def evenlist(lst):def even(i):if i % 2 == 0:return Truenew_list = []for item in lst:#判断item的奇偶if even(item):new_list.append(item)return new_list
print(evenlist(l))
结果相同。
可在外函数中同时添加多个内部函数,但是这不灵活的,需要将每个函数都写出来。
当使用函数作为参数时,实际上是将相应函数的代码传递进了目标函数。
l = [1,2,3,4,5,6,7,8,9,10]
def even(i):if i % 2 == 0:return True
def tri(i):if i % 3 == 0:return True
def evenlist(func,lst):new_list = []for item in lst:#判断item的奇偶if func(item):new_list.append(item)return new_list
print(evenlist(even,l))
print(evenlist(tri,l))
即为高级函数,打印
[2, 4, 6, 8, 10]
[3, 6, 9]
16.闭包
闭包将函数作为返回值返回,也是一种高级函数。
def fn():#函数内部再定义一个函数def inner():print('In fn2()')#将inner()函数作为返回值返回return inner
print(fn())
打印.inner at >
def fn():#函数内部再定义一个函数def inner():print('In fn2()')#将inner()函数作为返回值返回return inner
r = fn()
r()
打印In fn2()。
解释:r其实就是fn()返回的函数inner对象,再调用,就会打印,所以这个函数总是能返回fn()函数内部的变量,如下:
def fn():a = 10#函数内部再定义一个函数def inner():print('In fn2()')print(a)#将inner()函数作为返回值返回return inner
r = fn()
r()
打印
In fn2()
10
r是调用fn()后返回的函数,是在fn()内部定义的,并不是全局函数,所以这个函数总是能访问到fn()函数内部的变量。
闭包的好处:
通过闭包可以创建一些只有当前函数可以访问到的对象(可以将一些私有的数据藏到闭包中)。
应用:
nums = []
def average(n):nums.append(n)return sum(nums)/len(nums)
print(average(10))
print(average(20))
print(average(30))
打印
10.0
15.0
20.0
不好的地方:nums是全局变量,会被任意访问到,可能会出现被修改、覆盖,从而导致本身的nums改变导致错误。
所以形成闭包:
def op_avg():nums = []def average(n):nums.append(n)return sum(nums)/len(nums)return average
avg = op_avg()
print(avg(10))
print(avg(20))
print(avg(30))
结果与上面相同。
总结:
形成闭包的条件:
1.函数嵌套;
2.将内部函数作为返回值返回;
3.内部函数必须使用外部函数的变量。
闭包可以确保数据的安全。
17.装饰器 17.1 装饰器的出现背景
def add(a,b):#求任意两个数的和print('Start...')r = a + bprint('End...')return rdef multiply(a,b):# 求任意两个数的积print('Start...')r = a * bprint('End...')return rr = add(1,2)
print(r)
打印
Start...
End...
3
这是添加了提示开始和结束的输出,但是如果函数较多,会出现一些问题:
def fn():print('In fn()')def fn2():print('Start...')fn()print('End...')fn2()
打印
Start...
In fn()
End...
又如
def add(a,b):#求任意两个数的和r = a + breturn rdef new_add(a,b):print('Start...')r = add(a,b)print('End...')return rr = new_add(1,2)
print(r)
打印
Start...
End...
3
很显然,会出现一定问题,每扩展一个函数会定义一个新的函数,很麻烦。
17.2 装饰器的使用
def start_end():#用来对其他函数进行扩展,使其他函数可以在执行前打印开始和结束#创建一个函数def new_func():pass#返回函数return new_func
f = start_end()
f2 = start_end()
print(f)
print(f2)
打印
<function start_end.<locals>.new_func at 0x0000018368DE2EE8>
<function start_end.<locals>.new_func at 0x0000018368DFB1F8>
扩展后:
def fn():print('In fn()')def start_end():# 用来对其他函数进行扩展,使其他函数可以在执行前打印开始和结束# 创建一个函数def new_func():print('Start...')fn()print('End...')#返回函数return new_funcf = start_end()
f()
打印
Start...
In fn()
End...
但是是固定的、不能改变,失去了意义,所以可以在函数参数中传入被扩展的函数。
def fn():print('In fn()')def start_end(func):#用来对其他函数进行扩展,使其他函数可以在执行前打印开始和结束#创建一个函数def new_func():print('Start...')func()print('End...')#返回函数return new_funcf = start_end(fn)
f()
打印
Start...
In fn()
End...
当被扩展的函数有参数时,
def add(a,b):#求任意两个数的和r = a + breturn rdef start_end(func):#用来对其他函数进行扩展,使其他函数可以在执行前打印开始和结束#创建一个函数def new_func(a,b):print('Start...')result = func(a,b)print('End...')return result#返回函数return new_funcf = start_end(add)
r = f(1,2)
print(r)
打印
Start...
End...
3
需要对任何函数进行扩展时,
def add(a,b):#求任意两个数的和r = a + breturn rdef fn():print('In fn()')def start_end(func):#用来对其他函数进行扩展,使其他函数可以在执行前打印开始和结束#创建一个函数def new_func(*args,**kwargs): #*args接收所有位置参数,**kwargs接收所有关键字参数print('Start...')result = func(*args,**kwargs)print('End...')return result#返回函数return new_func
f = start_end(add)
f2 = start_end(fn)
r = f(1,2)
print(r)
f2()
打印
Start...
End...
3
Start...
In fn()
End...
总结:像上面的(func)这类的函数即称之为装饰器。
通过装饰器可以在不修改原来函数的基础之上来对函数进行扩展,在开发过程中,都是通过对装饰器来扩展函数的功能。
三.面向对象 1.面向对象简介 1.1 面向对象基本概念
面向对象OOP,即 。
什么是对象:
对象就是内存中存储指定数据的一块区域。
实际上对象就是一个容器,专门用来存数据。
程序运行的通俗解释:
代码存在硬盘,CPU处理代码,CPU和硬盘之间有内存,解释器将代码交给内存,CPU再从内存读取。
1.2 面向对象的结构 id(标识)
用来标识对象的唯一性,每个对象都有唯一的id,每个id指向一个内存地址值。
id由解释器生成,其实就是对象的内存地址。
type(类型)
类型决定了对象有哪些功能。
可以通过type()函数来查看对象的类型。
value(值)
值就是对象中存储的具体数据,分为:
1.3从面向过程到面向对象
所谓面向对象,简单理解就是语言中所有的操作都是通过对象来进行的。
面向过程 → 面向对象:
面向过程的典型代表是C语言,是早期语言的特点,是将一件事分成一个个步骤,并用函数分别实现。
面向对象是一种思考问题的方式,是一种思想,将事物变得简单化。
对面向对象的理解:
(1)可以让复杂事务变得简单化,人由执行者过渡到指挥者;
(2)具备封装、继承和多态。
举例——孩子吃瓜:
(1)妈妈穿衣服穿鞋出门
(2)妈妈骑电动车
(3)妈妈到超市门口停好电动车
(4)妈妈买瓜
(5)妈妈结账
(6)妈妈骑电动车回家
(7)到家孩子吃瓜
这是一个典型的面向过程的例子,在现实中没问题,但是在程序中会出现问题,在复用、修改时会出现很多问题。
面向过程的思想:
将一个功能分解成一个一个的小步骤。
面向对象就是让孩子吃瓜,而不管具体怎么实现,细节在对象内部。
2.类(class)的简介
目前所用到的对象都是内置的对象,如int、float、str等。
类简单理解就是一张图纸,在程序中我们需要根据类来创建对象。
a = int(10)
print(a)
打印10。
定义一个类:
自己定义类时,类名要以大写开头。
语法:
class 类名([父类]):代码块
如
class MyClass():pass
print(MyClass)
打印
class MyClass():pass
#用MyClass创建了一个对象mc,mc是MyClass的实例
mc = MyClass()
print(mc,type(mc))
12345
打印
解释:用创建了一个对象mc,mc是的实例。
class MyClass():pass
#用MyClass创建了一个对象mc,mc是MyClass的实例
mc = MyClass()
mc2 = MyClass()
mc3 = MyClass()
mc4 = MyClass()
#用来检查一个对象是否是一个类的实例
result = isinstance(mc,MyClass)
result2 = isinstance(mc2,int)
result3 = isinstance(mc3,type(mc))
result4 = isinstance(mc4,type(mc4))
print(result,result2,result3,result4)
打印True False True True
()用来检查一个对象是否是一个类的实例。
3.对象的基本介绍 3.1 对象的创建流程
类也是一个对象,类是一个用来创建对象的对象。
class MyClass():pass
print(id(MyClass),type(MyClass))
打印28
显然,类是一个type类型的对象。
创建类的实例即对象时,实例并没有值,是空的,所对应的类也是没有值的,也是空类。
可以向对象中添加变量,对象中的变量称为属性。
class MyClass():pass
mc = MyClass()
mc.name = 'Tom'
print(mc.name)
打印Tom
即属性是添加到实例中,而不是到类中,如print(.name)会报错。
3.2 对象的定义
类和对象都是对现实生活中事物或程序内容的抽象。
实际上,所有的事物都是由两部分组成的:
※数据(属性)
※行为(方法)
在类的代码中,可以定义变量和函数;
在类中定义的变量将会成为所有实例的公共属性,所有实例都可以访问这些变量。
如
class Person():a = 10b = 20
#创建Person的实例
p1 = Person()
p2 = Person()
print(p1.a,p2.b)
打印10 20
加入有意义的属性:
class Person():name = 'Tom'
#创建Person的实例
p1 = Person()
p2 = Person()
print(p1.name)
打印Tom
在类中可以定义函数,类中的函数称为方法。
这些方法通过该类的实例都可以访问。
class Person():name = 'Tom'def speak(self):print('Hello')
#创建Person的实例
p1 = Person()
p2 = Person()
print(p1.name)
p2.speak()
打印
Tom
Hello
如果是函数,有几个形参,就传几个实参;
如果是方法,默认传递一个参数,即类中的方法至少要定义一个形参,如上面speak()的self参数。这个self参数,在我们调用对象方法时,并不需要我们去传递。系统会自动默认传递
4.属性和方法
属性和方法的查找流程:
调用一个对象的属性和方法时,解析器会在当前对象中寻找是否有该属性和方法,如果有,直接返回;
如果没有,去当前对象的类对象中寻找,如果有,则返回类对象中的属性值和方法,如果没有,则报错。
class Person():name = 'Tom'def speak(self):print('Hello')
#创建Person的实例
p1 = Person()
p2 = Person()
p1.name = 'John'
print(p1.name,p2.name)
打印John Tom。
类对象和实例对象中都可以保存属性和方法:
如果属性和方法是所有类的实例共享的,则应该保存到类对象中;
如果属性和方法是某个实例独有的,则应该保存到实例对象中;
一般,属性保存到实例对象中,方法保存到类对象方法中。
探究self:
class Person():name = ''def speak(self,name):print(self)#创建Person的实例
p1 = Person()
p2 = Person()
p1.name = 'John'
p2.name = 'Tom'
p1.speak(p1.name)
p2.speak(p2.name)
打印
<__main__.Person object at 0x0000020D3D325188>
<__main__.Person object at 0x0000020D3D3251C8>
即默认传递的参数,就是调用方法的对象本身,明明为self。
所以,要想实现对象的方法分别调用自己的属性,可以如下:
class Person():name = ''def speak(self,name):print('Hello, I\'m %s'%name)
#创建Person的实例
p1 = Person()
p2 = Person()
p1.name = 'John'
p2.name = 'Tom'
p1.speak(p1.name)
p2.speak(p2.name)
打印
Hello, I'm John
Hello, I'm Tom
另一种方法:
class Person():name = ''def speak(self):print('Hello, I\'m %s'%self.name)
#创建Person的实例
p1 = Person()
p2 = Person()
p1.name = 'John'
p2.name = 'Tom'
p1.speak()
p2.speak()
效果与前一种方法是一样的。
5. 特殊方法
class Person:name = 'Tom'def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p2 = Person()
p2.speak()
打印Hello,I'm Tom
类中没有定义属性时,手动添加属性
class Person:def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p2 = Person()
#手动向对象添加属性
p1.name = 'Tom'
p2.name = 'Jerry'
p1.speak()
p2.speak()
打印
Hello,I'm Tom
Hello,I'm Jerry
如未手动添加属性时,
class Person:def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p2 = Person()
p1.speak()
p2.speak()
便会报错:: '' has no 'name'
在上例中,对于这个类name属性是必须的,并且对于每一个对象name属性的值是不一样的,将name属性手动添加,很容易出错。
5.1 特殊方法:
即魔术方法,都是以__开头、__结尾的方法。
特殊方法不需要我们调用,会在特殊的时候自己调用。
学习特殊方法:
※特殊方法什么时候调用;
※特殊方法有什么作用。
class Person:def __init__(self):print('hello')def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p1.__init__()
打印
hello
hello
hello打印了两次,因为类的实例创建时,会自动调用了一次,再手动调用了一次,所以调用了两次。
class Person:def __init__(self):print('hello')def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p2 = Person()
p3 = Person()
p4 = Person()
打印
hello
hello
hello
hello
可以说,每创建一个实例对象,特殊方法就执行一次。
对象的创建流程:
p1 = Person()
(1)创建了一个变量;
(2)在内存中创建了一个新的对象;
(3)执行类中的代码块的代码,并且只在类中执行一次;
(4)()方法执行。
即如类的定义中除特殊方法还有别的代码语句,则其他语句先执行,如,
class Person:print('In class Person')def __init__(self):print('hello')def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p2 = Person()
p3 = Person()
p4 = Person()
打印
In class Person
hello
hello
hello
hello
()方法会在对象创建以后立即执行,向新创建的对象初始化属性,
class Person:def __init__(self):print(self)def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p2 = Person()
p3 = Person()
p4 = Person()
打印
<__main__.Person object at 0x000002E1EEC36708>
<__main__.Person object at 0x000002E1EEC36EC8>
<__main__.Person object at 0x000002E1EEC36F08>
<__main__.Person object at 0x000002E1EEC36F48>
即打印self即打印出对象,self代表对象本身。
class Person:def __init__(self):self.name = 'Tom'def speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p1.speak()
打印Hello,I'm Tom
为了解决之前出现过的问题,可以在()方法中添加参数,通过self向新建的对象初始化对象。
class Person:def __init__(self,name):self.name = namedef speak(self):print('Hello,I\'m %s'%self.name)p1 = Person()
p1.speak()
初始化时未添加name,会报错
: () 1 : 'name'
所以必须在初始化时即添加,
class Person:def __init__(self,name):self.name = namedef speak(self):print('Hello,I\'m %s'%self.name)p1 = Person('Tom')
print(p1.name)
p2 = Person('Jerry')
print(p2.name)
p1.speak()
p2.speak()
打印
Tom
Jerry
Hello,I'm Tom
Hello,I'm Jerry
6.类的基本结构:
class 类名([父类]):公共属性#对象的初始化方法__init__(self,...):...#其他的方法def method(self,...):...
要求:尝试定义一个车的类。
class Car():'''属性:name、color方法:run()、didi()'''def __init__(self,name,color):self.name = nameself.color = colordef run(self):print('Car in running...')def didi(self):print('%s didi'%self.name)c = Car('daben','white')
print(c.name,c.color)
c.run()
c.didi()
打印
daben white
Car in running...
daben didi
定义对象时,希望对象能发挥一些作用,应该保证数据的安全性和有效性,
class Car():'''属性:name、color方法:run()、didi()'''def __init__(self,name,color):self.name = nameself.color = colordef run(self):print('Car in running...')def didi(self):print('%s didi'%self.name)c = Car('daben','white')
#修改属性
c.name = 'aodi'
print(c.name,c.color)
c.run()
c.didi()
结果就会改变
aodi white
Car in running...
aodi didi
要增加数据的安全性,要做到:
1.属性不能随意修改,有确实要改的需要才改,没有真实的需要才改;
2.属性不能改为任意的值,要保证有效。
7.封装
封装是面向对象的三大特性之一(另外两个是继承、多态)。
封装指的是隐藏对象中一些不希望被外部访问到的属性和方法。
class Dog:def __init__(self,name):self.name = named = Dog('erha')
d.name = 'kaisa'
print(d.name)
打印kaisa
此例中name属性即未被隐藏,会被任意修改。
如何修改对象中的一个属性:
将对象的属性名修改为外部不知道的名字。
class Dog:def __init__(self,name):self.hidden_name = namedef speak(self):print('Hello,I\'m %s'%self.hidden_name)d = Dog('erha')
d.name = 'kaisa'
d.speak()
打印Hello,I'm erha
此时不能修改,要想修改,
class Dog:def __init__(self,name):self.hidden_name = namedef speak(self):print('Hello,I\'m %s'%self.hidden_name)d = Dog('erha')
d.hidden_name = 'kaisa'
d.speak()
打印Hello,I'm kaisa,这又不是封装,成了最初的形式。
如何获取(修改)对象当中的属性:
通过提供()和()方法来访问和修改属性。
class Dog:def __init__(self,name):self.hidden_name = namedef speak(self):print('Hello,I\'m %s'%self.hidden_name)def get_name(self):#获取对象的属性return self.hidden_namedef set_name(self,name):#修改对象属性self.hidden_name = named = Dog('erha')
print(d.get_name())
d.set_name('kaisa')
print(d.get_name())
d.speak()
打印
erha
kaisa
Hello,I'm kaisa
使用封装确实在一定程度上增加了程序的复杂性,
但是它确保了数据的安全性:
1.隐藏了属性名,使调用者无法任意修改对象中的属性。
2.增加了和方法,可以很好地控制属性是否是可读的:
如果希望属性是可读的,则可以直接去掉方法;
如果希望属性不能被完结访问,则可以直接去掉方法。
3.使用方法设置属性,可以增加数据的验证,确保数据的值是正确的。
增加了程序的安全性。
4.可以在读取属性和设置属性的方法中做一些其他的操作。
class Dog:def __init__(self,name,age):self.hidden_name = nameself.hidden_age = agedef speak(self):print('Hello,I\'m %s'%self.hidden_name)def get_name(self):#获取对象的属性return self.hidden_namedef set_name(self,name):#修改对象属性self.hidden_name = namedef get_age(self):#获取对象的属性return self.hidden_agedef set_age(self,age):#修改对象属性self.hidden_age = aged = Dog('erha',5)
print(d.get_age())
d.set_age(-5)
print(d.get_age())
打印
5
-5
显然,把狗的年龄设为负值程序是没报错的,但是是没有实际意义的,所以我们可以增加对数据的判断。
class Dog:def __init__(self,name,age):self.hidden_name = nameself.hidden_age = agedef speak(self):print('Hello,I\'m %s'%self.hidden_name)def get_name(self):#获取对象的属性return self.hidden_namedef set_name(self,name):#修改对象属性self.hidden_name = namedef get_age(self):#获取对象的属性return self.hidden_agedef set_age(self,age):#修改对象属性#判断再处理if age > 0:self.hidden_age = aged = Dog('erha',5)
print(d.get_age())
d.set_age(-5)
print(d.get_age())
打印
5
5
8.封装进阶
class Person():def __init__(self,name):self.hidden_name = namedef get_name(self):return self.hidden_namedef set_name(self,name):self.hidden_name = namep = Person('Tom')
p.set_name('Jerry')
print(p.get_name())
打印Jerry,即属性值会改变。改为
class Person():def __init__(self,name):self.__name = namedef get_name(self):return self.__namedef set_name(self,name):self.__name = namep = Person('Tom')
print(p.get_name())
打印Tom
class Person():def __init__(self,name):self.__name = namedef get_name(self):return self.__namedef set_name(self,name):self.__name = namep = Person('Tom')
print(p.__name)
会报错: '' has no ''
class Person():def __init__(self,name):self.__name = namedef get_name(self):return self.__namedef set_name(self,name):self.__name = namep = Person('Tom')
p.__name = 'Jerry'
print(p.get_name())
打印还是Tom,没有被修改。
总结:可以为对象的属性使用双下划线开头,__xxx。
双下划线开头的属性是类的隐藏属性,隐藏属性只能在类的内部访问,无法通过外部访问。
隐藏属性是自动为属性改了一个名字,实际改的名字是:类名_属性名:
->
class Person():def __init__(self,name):self.__name = namedef get_name(self):return self.__namedef set_name(self,name):self.__name = namep = Person('Tom')
print(p.get_name())
p._Person__name = 'Jerry'
print(p.get_name())
打印
Tom
Jerry
即又可以对属性进行修改。
在写代码的时候,可以用一个下划线,既容易书写,外部能访问,且不易被修改。
一般情况下:使用_开头的属性都是私有属性,没有特殊情况不要修改。
9.装饰器@
用来将get()方法转化为对象的属性。
添加装饰器以后,就可以像调用属性一样调用方法。
class Person():def __init__(self,name):self._name = namedef name(self):return self._namep = Person('Tom')
print(p.name())
打印Tom
用了装饰器以后,
class Person():def __init__(self,name):self._name = name@propertydef name(self):print('The method is run')return self._namep = Person('Tom')
print(p.name)
输出结果为:
The method is run
Tom
加入后,
class Person():def __init__(self,name):self._name = name@propertydef name(self):print('The method is run')return self._name@name.setterdef name(self,name):self._name = namep = Person('Tom')
print(p.name)
p.name = 'Jerry'
print(p.name)
打印
The method is run
Tom
The method is run
Jerry
10.继承 10.1 引入
class Doctor():name = ''age = ''def treat(self):print('treat a patient')class soldier():name = ''age = ''def protect(self):print('Protect the people')
以上定义了两个类,但是又重复的属性name、age,如果每定义一个类都要分别定义这两个属性,很重复冗余,所以可以抽象出一个类来封装它们的公共属性和方法,即它们的父类这些类都继承自它们的父类。
如
class Person():name = ''age = ''
10.2 继承的特点:
1.提高了代码的复用性;
2.让类与类之间产生联系,有了这个联系,才有了多态。
继承是面向对象的三大特性之一,其他两个特性是封装、多态。
定义一个动物类
class Animal:def run(self):print('Running...')def sleep(self):print('Sleeping...')a = Animal()
a.run()
再定义一个狗类,
class Dog:def run(self):print('Running...')def sleep(self):print('Sleeping...')
这样定义狗类,有大量重复性代码,表现较差,我们应该使狗类继承自动物类,继承动物类的属性和方法。
class Dog(Animal):passd = Dog()
d.run()
d.sleep()
打印
Running...
Sleeping...
在定义类时可以在类名+括号,括号内的指定的是当前类的父类(超类、基类,super)。
class Dog(Animal):def Housekeep(self):print('Housekeeping...')d = Dog()
d.run()
d.sleep()
d.Housekeep()
r1 = isinstance(d,Dog)
r2 = isinstance(d,Animal)
print(r1,r2)
打印
Running...
Sleeping...
Housekeeping...
True True
显然,d既是Dog的实例,也是的实例,所以Dog也继承自了。
如果在创建类的时候省略了父类,则默认父类是;
是所有类的父类,所有类都继承自。
()检查一个类是否是另一个类的子类。
class Animal:def run(self):print('Running...')def sleep(self):print('Sleeping...')class Dog(Animal):def Housekeep(self):print('Housekeeping')r1 = issubclass(Dog,Animal)
r2 = issubclass(Animal,object)
print(r1,r2)
证明了所有类都是的子类,所有对象都是的实例。
10.3 方法的重写
如果在子类中有和父类重名的方法,通过子类的实例去调用方法时,会调用子类的方法而不是父类的方法,这个特点称为方法的重写(覆盖,)。
如,
class Animal:def run(self):print('Running...')def sleep(self):print('Sleeping...')class Dog(Animal):def run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')d = Dog()
d.run()
打印Dog is ...
下边再详细说明,
class A(object):def test(self):print('A...')class B(A):passclass C(B):passc = C()
c.test()
打印A...。
类B中加入方法test()后,
class A(object):def test(self):print('A...')class B(A):def test(self):print('B...')class C(B):passc = C()
c.test()
打印B...
类C中加入方法test()后,
class A(object):def test(self):print('A...')class B(A):def test(self):print('B...')class C(B):def test(self):print('C...')c = C()
c.test()
打印C...。
当我们调用一个对象时,会有先去当前对象寻找是否有该方法,如果有直接调用;如果没有,则取当前对象的父类中寻找,如果有,直接调用父类中的方法,如果没有,则去父类的父类寻找,如果有直接调用,以此类推,直到找到,如果依然没有则报错。
10.4 super()
父类中所有的方法都会被子类继承,包括特殊方法,如()等,也可以重写特殊方法。
class Animal:def __init__(self,name):self._name = namedef run(self):print('Running...')def sleep(self):print('Sleeping...')@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass Dog(Animal):def run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')d = Dog()
d.run()
会抛出异常,: () 1 : 'name'
因为初始化没有传入参数,因为父类初始化要传入name这个参数,子类也必须传入。
传入参数后,
class Animal:def __init__(self,name):self._name = namedef run(self):print('Running...')def sleep(self):print('Sleeping...')@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass Dog(Animal):def run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')d = Dog('erha')
print(d.name)
打印erha
调用方法修改属性,
class Animal:def __init__(self,name):self._name = namedef run(self):print('Running...')def sleep(self):print('Sleeping...')@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass Dog(Animal):def run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')d = Dog('erha')
d.name = 'demu'
print(d.name)
打印demu。
如果需要在子类中定义新的属性时,即要扩展属性时,
class Animal:def __init__(self,name):self._name = namedef run(self):print('Running...')def sleep(self):print('Sleeping...')@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass Dog(Animal):def __init__(self,name,age):self._name = nameself._age = agedef run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = aged = Dog('erha',10)
print(d.name)
print(d.age)
打印
erha
10
12
可以直接调用父类中的()来初始化父类中的属性,
class Animal:def __init__(self,name):self._name = namedef run(self):print('Running...')def sleep(self):print('Sleeping...')@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass Dog(Animal):def __init__(self,name,age):Animal.__init__(self,name)self._age = agedef run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = aged = Dog('erha',10)
print(d.name)
print(d.age)
即Dog类中的初始化方法中,name属性通过的初始化调用获取,可以将父类的多个属性传给子类,但是继承的父类是固定的,这是需要用super()动态地继承父类属性。
super()可以用来获取当前类的父类,并且通过super()返回的对象,调用父类方法时不需要传入self。
class Animal:def __init__(self,name):self._name = namedef run(self):print('Running...')def sleep(self):print('Sleeping...')@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass Dog(Animal):def __init__(self,name,age):super().__init__(name)self._age = agedef run(self):print('Dog is running...')def sleep(self):print('Dog is sleeping...')def Housekeep(self):print('Dog is housekeeping')@propertydef age(self):return self._age@age.setterdef age(self, age):self._age = aged = Dog('erha',10)
print(d.name)
print(d.age)
执行结果与之前相同。
10.5 多重继承
语法:类名.bases
可以用来获取当前类的所有直接父类。
class A(object):def test(self):print('A...')class B(A):def test2(self):print('B...')class C(B):passprint(C.__bases__)
print(B.__bases__)
print(A.__bases__)
打印
(<class '__main__.B'>,)
(<class '__main__.A'>,)
(<class 'object'>,)
在中支持多重继承,即可以为一个类同时指定多个父类。
class A(object):def test(self):print('A...')class B(object):def test2(self):print('B...')class C(A,B):passprint(C.__bases__)
打印(, )。
class A(object):def test(self):print('A...')class B(object):def test2(self):print('B...')class C(A,B):passc = C()
c.test()
c.test2()
打印
A...
B...
12
显然,子类可以同时继承多个父类的方法。
但是一般在实际中没有特殊情况,尽量避免使用多重继承,因为多重继承会让代码过于复杂;
如果多个父类中有同名的方法,则会在第一个父类中寻找,找不到再继续往下寻找…
如,
class A(object):def test(self):print('A...test')class B(object):def test(self):print('B...test')def test2(self):print('B...')class C(A,B):passc = C()
c.test()
会打印A...test
如果A中没有test()方法,
class A(object):passclass B(object):def test(self):print('B...test')def test2(self):print('B...')class C(A,B):passc = C()
c.test()
会打印B...test
11.多态
多态是面向对象的三大特征之一。
多态字面理解即多种形态,在面向对象中指一个对象可以以不同的形态呈现。
class A(object):def __init__(self,name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass B(object):def __init__(self, name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self, name):self._name = namedef speak(obj):print('Hello,%s' % obj.name)a = A('Tom')
b = B('Jerry')
speak(a)
speak(b)
打印
Hello,Tom
Hello,Jerry
定义一个新类C,但是没有name属性时,
class A(object):def __init__(self,name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass B(object):def __init__(self, name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self, name):self._name = nameclass C(object):pass
def speak(obj):print('Hello,%s' % obj.name)a = A('Tom')
b = B('Jerry')
c= C()
speak(a)
speak(b)
speak(c)
会抛出异常,: 'C' has no 'name',
增加类型检查,
class A(object):def __init__(self,name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self,name):self._name = nameclass B(object):def __init__(self, name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self, name):self._name = nameclass C(object):passdef speak(obj):print('Hello,%s' % obj.name)def test_speak(obj):'''类型检查'''if isinstance(obj,A):print('Hello,%s' % obj.name)a = A('Tom')
b = B('Jerry')
c= C()
test_speak(a)
test_speak(b)
test_speak(c)
打印Hello,Tom。
在()这个函数中做了类型检查,也就是obj是A类型的对象的时候,才可以正常执行,其他类型的对象无法使用该函数。
这个函数其实就是违反了多态。
违反了多态的函数,只适合于一种类型的对象,无法适用于其他类型的对象,导致函数的适用性非常差。
多态使面向对象的编程更加具有灵活性。
l = [1,2,3]
s = 'python'
print(len(l),len(s))
如上,len()函数既可以得到list的长度,又可以获取str的长度,
len()就可以检查不同对象类型的长度就是面向对象的特征之一——多态。
len()的底层实现:
之所以len()函数能获取长度,是因为这些对象中有一个特殊方法()方法;
换句话说,只要对象中有()方法,就可以通过len()方法来获取对象的长度。
class B(object):def __init__(self, name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self, name):self._name = nameb = B('Jerry')
print(len(b))
会报错: of type 'B' has no len()。
在B中加入()方法后,
class B(object):def __init__(self, name):self._name = name@propertydef name(self):return self._name@name.setterdef name(self, name):self._name = namedef __len__(self):return 1b = B('Jerry')
print(len(b))
会打印1
面向对象的三大特征:
1.封装:确保对象中的数据更安全;
2.继承:保证对象的可扩展性;
3.多态:保证了程序的灵活性。
12.类中的属性和方法
类属性是直接在类中定义的属性,类属性可以通过类或类的实例访问;
通过实例对象添加的属性属于实例属性。
class A(object):#类属性count = 0a = A()
print(A.count)
print(a.count)
打印
0
0
修改实例的属性,
class A(object):#类属性count = 0a = A()
a.count = 5
print('In class,',A.count)
print('In instance,',a.count)
打印
In class, 0
In instance, 5
修改类中的属性,
class A(object):#类属性count = 0a = A()
A.count = 5
print('In class,',A.count)
print('In instance,',a.count)
打印
In class, 5
In instance, 5
可总结出:类属性只能通过类对象来修改,无法通过实例对象来修改;
class A(object):def __init__(self):#实例属性self.name = 'Tom'a = A()
print(a.name)
打印Tom
在初始化中定义的属性为实例属性,只能通过实例属性来访问和修改,类对象无法访问和修改。
如,
class A(object):def __init__(self):#实例属性self.name = 'Tom'a = A()
print(A.name)
报错: type 'A' has no 'name'
在类中定义的以self为第一个参数的方法都是实例方法;
实力方法在调用时,会将调用对象作为self传入;
class A(object):def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')
a = A()
a.test()
打印in test
类对象调用时,
class A(object):def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')
a = A()
A.test()
报错: test() 1 : 'self'
但是通过类对象调用时,传入实例对象多为方法的参数不会报错。
即通过类对象调用方法时,不会自动穿self,必须手动传self。
class A(object):def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')
a = A()
A.test(a)
输出结果相同。
即a.test()等价于A.test(a)。
可以通过装饰器来调用类方法;
类方法的第一个参数是cls,也会被自动传递,cls就是当前的类对象。
class A(object):count = 0def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')@classmethoddef test2(cls):#实例方法print('in test2',cls)a = A()
A.test2()
打印in test2
调用类属性,
class A(object):count = 0def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')@classmethoddef test2(cls):#实例方法print('in test2',cls)print(cls.count)a = A()
A.test2()
打印
in test2 <class '__main__.A'>
0
通过实例调用类方法,
class A(object):count = 0def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')@classmethoddef test2(cls):#实例方法print('in test2',cls)print(cls.count)a = A()
a.test2()
结果相同。
可总结:类方法可以通过类对象调用,也可通过实例方法调用。
A.test2()等价于a.test2()。
静态方法:
用装饰器来调用类方法
class A(object):count = 0def __init__(self):#实例属性self.name = 'Tom'def test(self):#实例方法print('in test')@classmethoddef test2(cls):#实例方法print('in test2',cls)print(cls.count)@staticmethoddef test3():print('in test3')a = A()
A.test3()
a.test3()
打印
in test3
in test3
静态方法,基本上是一个和当前类无关的方法,它只是一个保存到当前类中的函数;
静态方法一般是工具方法,和当前类无关。