Python25 面向对象
该文章部分转载,部分原创
引子
你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战,那至少需要2个角色,一个是人, 一个是狗,且人和狗都有不同的技能,比如人拿棍打狗, 狗可以咬人,怎么描述这种不同的角色和他们的功能呢?
你搜罗了自己掌握的所有技能,写出了下面的代码来描述这两个角色
def person(name,age,sex,job): #这类函数相当于模板
data = {
'name':name,
'age':age,
'sex':sex,
'job':job
}
return data
def dog(name,dog_type):
data = {
'name':name,
'type':dog_type
}
return data
上面两个方法相当于造了两个模子,游戏开始,你得生成一个人和狗的实际对象吧,怎么生成呢?
d1 = dog("李磊","京巴")
p1 = person("严帅",36,"F","运维")
p2 = person("林海峰",27,"F","Teacher")
两个角色对象生成了,狗和人还有不同的功能呀,狗会咬人,人会打狗,对不对? 怎么实现呢,。。想到了, 可以每个功能再写一个函数,想执行哪个功能,直接 调用 就可以了,对不?
def bark(d):
print("dog %s:wang.wang..wang..."%d['name'])
def walk(p):
print("person %s is walking..." %p['name'])
d1 = dog("李磊","京巴")
p1 = person("严帅",36,"F","运维")
p2 = person("林海峰",27,"F","Teacher")
walk(p1) bark(d1)
上面的功能实现的简直是完美!
但是仔细玩耍一会,你就不小心干了下面这件事
p1 = person("严帅",36,"F","运维")
bark(p1) #把人的对象传给了狗的方法
事实 上,这并没出错。很显然,人是不能调用狗的功能的,如何在代码级别实现这个限制呢?
def person(name, age, sex, job):
def walk(p):
print("person %s is walking..." % p['name'])
data = {
'name': name,
'age': age,
'sex': sex,
'job': job,
'walk': walk
}
return data
def dog(name, dog_type):
def bark(d):
print("dog %s:wang.wang..wang..." % d['name'])
data = {
'name': name,
'type': dog_type,
'bark': bark #将bark这个函数的内存地址retur给dog
}
return data
d1 = dog("李磊","京巴")
p1 = person("严帅",36,"F","运维")
p2 = person("林海峰",27,"F","Teacher")
print (d1['bark'](d1)) #(d1['bark'](d1)) == (data['bark'](data))
# d1['bark'](p1) 把人的对象传给了狗的方法
执行结果:
dog 李磊:wang.wang..wang...
None #这里是因为bark函数没有被return,所以为空
此时p1无法传入bark函数,因为bark函数存在dog函数中;且p1变量不能等于dog函数,因为p1的参数数量和dog函数的形参不一样。
p2['walk'](p2)
执行结果:
person 林海峰 is walking...
目前已经解决了两个 角色 之间的功能混用
你是如此的机智,这样就实现了限制人只能用人自己的功能啦。
但,我的哥,不要高兴太早,刚才你只是阻止了两个完全 不同的角色 之前的功能混用, 但有没有可能 ,同一个种角色,但有些属性是不同的呢? 比如 ,大家都打过cs吧,cs里有警察和恐怖份子,但因为都 是人, 所以你写一个角色叫 person(), 警察和恐怖份子都 可以 互相射击,但警察不可以杀人质,恐怖分子可以,这怎么实现呢? 你想了说想,说,简单,只需要在杀人质的功能里加个判断,如果是警察,就不让杀不就ok了么。 没错, 这虽然 解决了杀人质的问题,但其实你会发现,警察和恐怖分子的区别还有很多,同时又有很多共性,如果 在每个区别处都 单独做判断,那得累死。
你想了想说, 那就直接写2个角色吧, 反正 这么多区别, 我的哥, 不能写两个角色呀,因为他们还有很多共性 , 写两个不同的角色,就代表 相同的功能 也要重写了,是不是我的哥? 。。。
好了, 话题就给你点到这, 再多说你的智商 也理解不了了!
像以上这种情况就可以使用面向对象了。
面向对象用于实现更复杂的一些关系描述。
面向过程 VS 面向对象
编程范式
编程是 程序 员 用特定的语法+数据结构+算法组成的代码来告诉计算机如何执行任务的过程 , 一个程序是程序员为了得到一个任务结果而编写的一组指令的集合,正所谓条条大路通罗马,实现一个任务的方式有很多种不同的方式, 对这些不同的编程方式的特点进行归纳总结得出来的编程方式类别,即为编程范式。 不同的编程范式本质上代表对各种类型的任务采取的不同的解决问题的思路, 大多数语言只支持一种编程范式,当然也有些语言可以同时支持多种编程范式。 两种最重要的编程范式分别是面向过程编程和面向对象编程。
面向过程编程(Procedural Programming)
Procedural programming uses a list of instructions to tell the computer what to do step-by-step.
面向过程编程依赖 - 你猜到了- procedures,一个procedure包含一组要被进行计算的步骤, 面向过程又被称为top-down languages, 就是程序从上到下一步步执行,一步步从上到下,从头到尾的解决问题 。基本设计思路就是程序一开始是要着手解决一个大的问题,然后把一个大问题分解成很多个小问题或子过程,这些子过程再执行的过程再继续分解直到小问题足够简单到可以在一个小步骤范围内解决。
举个典型的面向过程的例子, 数据库备份, 分三步,连接数据库,备份数据库,测试备份文件可用性。
代码如下:
def db_conn():
print("connecting db...")
def db_backup(dbname):
print("导出数据库...",dbname)
print("将备份文件打包,移至相应目录...")
def db_backup_test():
print("将备份文件导入测试库,看导入是否成功")
def main(): #main是一个大块,这个大块集成了很多小块(将很多独立的函数集成到一块)
db_conn()
db_backup('my_db')
db_backup_test()
if __name__ == '__main__':
main()
这样做的问题也是显而易见的,就是如果你要对程序进行修改,对你修改的那部分有依赖的各个部分你都也要跟着修改, 举个例子,如果程序开头你设置了一个变量值 为1 , 但如果其它子过程依赖这个值 为1的变量才能正常运行,那如果你改了这个变量,那这个子过程你也要修改,假如又有一个其它子程序依赖这个子过程 , 那就会发生一连串的影响,随着程序越来越大, 这种编程方式的维护难度会越来越高。
所以我们一般认为, 如果你只是写一些简单的脚本,去做一些一次性任务,用面向过程的方式是极好的,但如果你要处理的任务是复杂的,且需要不断迭代和维护 的, 那还是用面向对象最方便了。
面向对象编程
OOP(object oriented programing)编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述,使用面向对象编程的原因一方面是因为它可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
类:相当于模板
对象:相当于角色
面向对象:相当于用类和对象创建各种模型
面向对象的几个核心特性如下
Class 类
一个类即是对一类拥有相同属性的对象的抽象、蓝图、原型(相当于模板,都有这个模板中定义的属性)。在类中定义了这些对象的都具备的属性(variables(data))、共同的方法(如人物模型,都有眼、手、脚等)
Object 对象
一个对象即是一个类的实例化后实例(之前人与狗的小游戏程序中,将参数传入到函数中,形成一个字典data,这个过程就是实例化,而实例化后的data就是实例),一个类必须经过实例化后方可在程序中调用(函数如果没有被实例化是没法调用的),一个类可以实例化多个对象(比如张三和李四两个对象),每个对象亦可以有不同的属性(一个叫张三,一个叫李四,名字的属性就不同),就像人类是指所有人,每个人是指具体的对象,人与人之前有共性,亦有不同
面向对象的特性(能带来什么好处)
-
Encapsulation 封装
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
封装:防止数据被篡改和泄露 使外部程序不需要关注对象的内部构造,只需要通过此对象对外提供的接口进行直接访问即可(比如QQ,你只需要知道怎么使用即可,不需要了解QQ内部的构造、代码等内容)
- Inheritance 继承
一个类可以派生出子类,在这个父类里定义的属性、方法自动被子类继承
通过父类-》子类的方式以最小代码量实现 不同角色的共同点和不通点
- Polymorphism 多态
多态是面向对象的重要特性,简单点说:“一个接口,多种实现”,指一个基类中派生出了不同的子类,且每个子类在继承了同样的方法名的同时又对父类的方法做了不同的实现,这就是同一种事物表现出的多种形态。
编程其实就是一个将具体世界进行抽象化的过程,多态就是抽象化的一种体现,把一系列具体事物的共同点抽象出来, 再通过这个抽象的事物, 与不同的具体事物进行对话。
对不同类的对象发出相同的消息将会有不同的行为。比如,你的老板让所有员工在九点钟开始工作, 他只要在九点钟的时候说:“开始工作”即可,而不需要对销售人员说:“开始销售工作”,对技术人员说:“开始技术工作”, 因为“员工”是一个抽象的事物, 只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。
多态允许将子类的对象当作父类的对象使用,某父类型的引用指向其子类型的对象,调用的方法是该子类型的方法。这里引用和调用方法的代码编译前就已经决定了,而引用所指向的对象可以在运行期间动态绑定
上面的术语比较难理解,其实多台就是继承一个父类而展现的多种形态,比如人,人分为白人、黑人、黄种人,这三种皮肤的人都属于人,但是却以不通的形态展现出来,每一种皮肤的人呢又都会说话,而说话呢又分了很多种语言,这就是在不断的继承父类,然后又以多种形态展现出来的方式。
面向对象编程(Object-Oriented Programming )介绍
对于编程语言的初学者来讲,OOP不是一个很容易理解的编程方式,大家虽然都按老师讲的都知道OOP的三大特性是继承、封装、多态,并且大家也都知道了如何定义类、方法等面向对象的常用语法,但是一到真正写程序的时候,还是很多人喜欢用函数式编程来写代码,特别是初学者,很容易陷入一个窘境就是“我知道面向对象,我也会写类,但我依然没发现在使用了面向对象后,对我们的程序开发效率或其它方面带来什么好处,因为我使用函数编程就可以减少重复代码并做到程序可扩展了,为啥子还用面向对象?”。 对于此,我个人觉得原因应该还是因为你没有充分了解到面向对象能带来的好处,今天我就写一篇关于面向对象的入门文章,希望能帮大家更好的理解和使用面向对象编程。
无论用什么形式来编程,我们都要明确记住以下原则:
写重复代码是非常不好的低级行为
你写的代码需要经常变更
开发正规的程序跟那种写个运行一次就扔了的小脚本一个很大不同就是,你的代码总是需要不断的更改,不是修改bug就是添加新功能等,所以为了日后方便程序的修改及扩展,你写的代码一定要遵循易读、易改的原则(专业数据叫可读性好、易扩展)。
如果你把一段同样的代码复制、粘贴到了程序的多个地方以实现在程序的各个地方调用 这个功能,那日后你再对这个功能进行修改时,就需要把程序里多个地方都改一遍,这种写程序的方式是有问题的,因为如果你不小心漏掉了一个地方没改,那可能会导致整个程序的运行都 出问题。 因此我们知道 在开发中一定要努力避免写重复的代码,否则就相当于给自己再挖坑。
还好,函数的出现就能帮我们轻松的解决重复代码的问题,对于需要重复调用的功能,只需要把它写成一个函数,然后在程序的各个地方直接调用这个函数名就好了,并且当需要修改这个功能时,只需改函数代码,然后整个程序就都更新了。
其实OOP编程的主要作用也是使你的代码修改和扩展变的更容易,那么小白要问了,既然函数都能实现这个需求了,还要OOP干毛线用呢? 呵呵,说这话就像,古时候,人们打仗杀人都用刀,后来出来了枪,它的主要功能跟刀一样,也是杀人,然后小白就问,既然刀能杀人了,那还要枪干毛线,哈哈,显而易见,因为枪能更好更快更容易的杀人。函数编程与OOP的主要区别就是OOP可以使程序更加容易扩展和易更改。
小白说,我读书少,你别骗我,口说无凭,证明一下,好吧,那我们就下面的例子证明给小白看。
相信大家都打过CS游戏吧,我们就自己开发一个简单版的CS来玩一玩。
暂不考虑开发场地等复杂的东西,我们先从人物角色下手, 角色很简单,就俩个,恐怖份子、警察,他们除了角色不同,其它基本都 一样,每个人都有生命值、武器等。 咱们先用非OOP的方式写出游戏的基本角色
#role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
上面定义了一个恐怖份子Alex和一个警察Jack,但只2个人不好玩呀,一干就死了,没意思,那我们再分别建立一个恐怖分子和警察吧,
role 1
name = 'Alex'
role = 'terrorist'
weapon = 'AK47'
life_value = 100
money = 10000
#rolw 2
name2 = 'Jack'
role2 = 'police'
weapon2 = 'B22'
life_value2 = 100
money2 = 10000
#role 3
name3 = 'Rain'
role3 = 'terrorist'
weapon3 = 'C33'
life_value3 = 100
money3 = 10000
#rolw 4
name4 = 'Eric'
role4 = 'police'
weapon4 = 'B51'
life_value4 = 100
money4 = 10000
4个角色虽然创建好了,但是有个问题就是,每创建一个角色,我都要单独命名,name1,name2,name3,name4…,后面的调用的时候这个变量名你还都得记着,要是再让多加几个角色,估计调用时就很容易弄混啦,所以我们想一想,能否所有的角色的变量名都是一样的,但调用的时候又能区分开分别是谁?
当然可以,我们只需要把上面的变量改成字典的格式就可以啦。
roles = {
1:{'name':'Alex',
'role':'terrorist',
'weapon':'AK47',
'life_value': 100,
'money': 15000,
},
2:{'name':'Jack',
'role':'police',
'weapon':'B22',
'life_value': 100,
'money': 15000,
},
3:{'name':'Rain',
'role':'terrorist',
'weapon':'C33',
'life_value': 100,
'money': 15000,
},
4:{'name':'Eirc',
'role':'police',
'weapon':'B51',
'life_value': 100,
'money': 15000,
},
}
print(roles[1]) #Alex
print(roles[2]) #Jack
很好,这个以后调用这些角色时只需要roles1,roles2就可以啦,角色的基本属性设计完了后,我们接下来为每个角色开发以下几个功能:
被打中后就会掉血的功能
开枪功能
换×××
买枪
跑、走、跳、下蹲等动作
保护人质(仅适用于警察)
不能杀同伴
。。。
我们可以把每个功能写成一个函数,类似如下:
def shot(by_who):
#开了枪后要减×××数
pass
def Got_shot(who):
#中枪后要减血
who[‘life_value’] -= 10
pass
def buy_gun(who,gun_name):
#检查钱够不够,买了枪后要扣钱
pass
so far so good, 继续按照这个思路设计,再完善一下代码,游戏的简单版就出来了,但是在往下走之前,我们来看看上面的这种代码写法有没有问题,至少从上面的代码设计中,我看到以下几点缺陷:
1.每个角色定义的属性名称是一样的,但这种命名规则是我们自己约定的,从程序上来讲,并没有进行属性合法性检测,也就是说role 1定义的代表武器的属性是weapon, role 2 ,3,4也是一样的,不过如果我在新增一个角色时不小心把weapon 写成了wepon , 这个程序本身是检测 不到的
这个我们可以通过建立统一模板来解决,不需要一个一个的来建立,就不容易出错。
2.terrorist 和police这2个角色有些功能是不同的,比如police是不能杀人质的,但是terrorist可能,随着这个游戏开发的更复杂,我们会发现这2个角色后续有更多的不同之处, 但现在的这种写法,我们是没办法 把这2个角色适用的功能区分开来的,也就是说,每个角色都可以直接调用任意功能,没有任何限制。
3.我们在上面定义了got_shot()后要减血,也就是说减血这个动作是应该通过被击中这个事件来引起的,我们调用get_shot(),got_shot()这个函数再调用每个角色里的life_value变量来减血。 但其实我不通过got_shot(),直接调用角色roles[role_id][‘life_value’] 减血也可以呀,但是如果这样调用的话,那可以就是简单粗暴啦,因为减血之前其它还应该判断此角色是否穿了防弹衣等,如果穿了的话,伤害值肯定要减少,got_shot()函数里就做了这样的检测,你这里直接绕过的话,程序就乱了。 因此这里应该设计 成除了通过got_shot(),其它的方式是没有办法给角色减血的,不过在上面的程序设计里,是没有办法实现的。
4.现在需要给所有角色添加一个可以穿防弹衣的功能,那很显然你得在每个角色里放一个属性来存储此角色是否穿 了防弹衣,那就要更改每个角色的代码,给添加一个新属性,这样太low了,不符合代码可复用的原则
上面这4点问题如果不解决,以后肯定会引出更大的坑,有同学说了,解决也不复杂呀,直接在每个功能调用时做一下角色判断啥就好了,没错,你要非得这么霸王硬上弓的搞也肯定是可以实现的,那你自己就开发相应的代码来对上面提到的问题进行处理好啦。 但这些问题其实能过OOP就可以很简单的解决。
之前的代码改成用OOP中的“类”来实现的话如下:
class Role(object):
def __init__(self,name,role,weapon,life_value=100,money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
#以上代码是属性
def shot(self):
print("shooting...")
def got_shot(self):
print("ah...,I got shot...")
def buy_gun(self,gun_name):
print("just bought %s" %gun_name)
#以上代码是功能
r1 = Role('Alex','police','AK47') #生成一个角色
r2 = Role('Jack','terrorist','B22') #生成一个角色
#以上代码是角色
先不考虑语法细节,相比靠函数拼凑出来的写法,上面用面向对象中的类来写最直接的改进有以下2点:
代码量少了近一半
角色和它所具有的功能可以一目了然看出来
在真正开始分解上面代码含义之之前,我们现来了解一些类的基本定义
类的语法
class Dog(object):
print("hello,I am a dog!") #这里相当于一个功能
d = Dog() #实例化这个类,
#此时的d就是类Dog的实例化对象
#实例化,其实就是以Dog类为模版,在内存里开辟一块空间,存上数据,赋值成一个变量名
执行结果:
hello, i am a dog.
上面的代码其实有问题,想给狗起名字传不进去。
class Dog(object):
def sayhi(self):
print("hello,I am a dog" ) #功能不应该直接放在class下,应该定义在函数中
d = Dog() #第一条狗
d2 = Dog() #第二条狗
d.sayhi()
d2.sayhi()
执行结果:
hello,I am a dog
hello,I am a dog
#可以看到根本分不清是哪一条狗的信息
class Dog(object):
def __init__(self,name):
self.name = name
def sayhi(self):
print("hello,I am a dog , my name is" name ) #在当前函数中没有name这个参数和变量,而且也无法到__init__这个函数中去调用,name属于局部参数
d = Dog("maomao")
d2 = Dog("lele")
d.sayhi()
d2.sayhi()
执行结果:
Traceback (most recent call last):
File "E:/python/练习代码/003.py", line 14, in <module>
d.sayhi()
File "E:/Python/练习代码/003.py", line 8, in sayhi
print("hello,I am a dog , my name is" ,name)
NameError: name 'name' is not defined #报错name找不到
鸟属于一个类,而百灵鸟属于鸟的一个子类,鸟是百灵鸟的超类(父类)
class Person:
def setName(self,name):
self.abc = name
def getName(self):
return self.abc
def greet(self):
print ("hello ,world! I'm %s." % self.abc)
foo = Person() #foo是Person()的对象,foo会将自己这个对象作为第一参数传入函数中,也就是传给self; 实例化后产生的对象(foo)叫 实例,所以self就是实例本身
bar = Person()
foo.setName('Luke') #将"Luke"当做第二参数传入参数中,也就是传给name;定义self.abc(相当于foo.abc)这个新变量==name("Luke");然后看到其他函数也都继承了self这个参数,也就是说self.abc可以被该类中的其他任何函数(方法)继承使用。
bar.setName('Amy') #bar.setName('Amy')==Person.setName('Amy')
foo.greet() #可以看做Person.greet(foo)
bar.greet()
执行结果:
hello ,world! I'm Luke.
hello ,world! I'm Amy.
class Dog(object):
def __init__(self,name): #构造函数,构造方法==初始化方法,__init__是创建类时必写的内容,实例传递参数需要传给构造函数。 这样才能被其他类的方法所调用。
self.abc = name
def sayhi(self): #类的方法,在创建任何一个方法时,self都会被自动创建。
print("hello,I am a dog , my name is" ,self.abc)
d = Dog("maomao") # 这里相当于调用了 def __init__(self,name),将d传给self,将"maomao"传给name
d2 = Dog("lele")
d.sayhi() #相当于Dog.sayhi()
d2.sayhi()
class Dog(object):
def __init__(self,name,dog_type):
self.name = name
self.type = dog_type
def sayhi(self):
print("hello,I am a dog, my name is ",self.name)
d = Dog('LiChuang',"京巴") #先去实例化,把Dog这个模板变成具体的对象,d就成为了实例化后的对象
d.sayhi() #在调用这个对象中的sayhi功能
为什么有init? 为什么有self? 此时的你一脸蒙逼,相信不画个图,你的智商是理解不了的!
画图之前, 你先注释掉这两句
# d = Dog('LiChuang', "京巴")
# d.sayhi()
print(Dog)
没实例直接打印Dog输出如下
<class '__main__.Dog'>
这代表什么?代表 即使不实例化,这个Dog类本身也是已经存在内存里的对不对, yes, cool,那实例化时,会产生什么化学反应呢?
class Dog(object):
def __init__(self,name):
self.abc = name
def sayhi(self):
print("hello,I am a dog , my name is" ,self.abc)
def eat(self,food):
print ("%s is eating %s" %(self.abc,food)) #这里的food在本函数中可以直接调用,只有定义了self.food=food这个变量,才可以使用self.food方式
d = Dog("maomao")
d2 = Dog("lele")
d.sayhi()
d2.sayhi()
d.eat('baozi')
执行结果:
hello,I am a dog , my name is maomao
hello,I am a dog , my name is lele
maomao is eating baozi
私有属性
class Role(object):
def __init__(self,name,role,weapon,life_value=100,money=15000):
self.name = name #这个name相当于属性或者叫成员变量
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
self.__heart="NORMal" #私有属性:对外是不可见的,是不能被访问的
#以上代码是属性
def shot(self): #定义其中一个方法,或者叫动态属性
print("shooting...")
def got_shot(self):
print("ah...,I got shot...")
self.__heart = "Die" #私有属性可以被内部访问
print (self.__heart) #在内部访问私有属性
def get_heart(self): #对外部提供只读访问接口
return self.__heart #让私有属性可以访问,但不可以修改
def buy_gun(self,gun_name):
print("just bought %s" %gun_name)
#以上代码是功能
r1 = Role('Alex','police','AK47') #生成一个角色
r2 = Role('Jack','terrorist','B22') #生成一个角色
#以上代码是角色
print (r1.name)
print (r1.__heart)
执行结果:
Alex
Traceback (most recent call last):
File "E:/python/代码练习/002.py", line 27, in <module>
print (r1.__heart)
AttributeError: 'Role' object has no attribute '__heart'
#私有属性无法被外部访问
print (r1.name)
r1.got_shot()
执行结果:
Alex
ah...,I got shot...
Die #可以看到是可以被内部(got_shot)所访问的
print (r1.get_heart())
执行结果:
Normal #此时这个私有属性可以被访问,但不能被修改
print( r1._Role__heart) #强制访问私有属性
执行结果:
Normal
访问got_shot方法中的私有属性:
r1.got_shot() #运行了r1.got_shot方法,这里__heart就是"Die"了
print( r1._Role__heart) #在去取值__heart就是,取值的就是got_shot中的__heart了
执行结果:
ah...,I got shot...
Die
Die
公有属性
所有实例化类的对象都可以访问的属性,叫做公有属性
class Role(object):
nationality = "USA" #公有属性,在class下面直接定义的变量就是公有属性
def __init__(self, name, role, weapon, life_value=100, money=15000):
self.name = name #成员属性,指定了具体参数,参数不同就表示是不同的成员
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
self.__heart = "Normal"
# 以上代码是属性
def shot(self): #这个函数也属于公有属性,不管有多少个实例化对象,都是到class下去调用这个函数(方法)
print("shooting...")
def got_shot(self):
print("ah...,I got shot...")
self.__heart = "Die"
def buy_gun(self, gun_name):
print("just bought %s" % gun_name)
# 以上代码是功能
r1 = Role('Alex', '警察', 'AK47') #生成一个角色
r2 = Role('dashan', '警犬', 'B22') #生成一个角色
print( r1.weapon)
执行结果:
AK47 #可以看到这个AK47武器只能被r1这个实例化后的对象访问,不能被r2访问,所以就不满足公有属性的条件; 这个AK47属于成员属性,只属r1;r2要访问r1的成员属性也可以,但必须经过r1这个对象来访问
print( r1.nationality)
print( r2.nationality)
#r1和r2这两个对象都可以访问nationnality这个公有属性
Role.nationality = 'JP' 修改类的公有属性
print( r1.nationality) #r1会现在当前局部去找nationality这个变量,如果找不到就会去超类(父类)中去找,这里说明r1是引用这个公有属性就是超类的nationality
print( r2.nationality)
执行结果:
JP #可以看到r1和r2的公有属性都被修改了,在类下会修改所有对象的公有属性
JP
Role.nationality = 'JP'
print( r1.nationality)
print( r2.nationality)
r1.nationality = 'CN' #这里是r1新创建的一个变量叫nationality,虽然和超类中的nationality同名,但是它们两个之间没有任何关系; 此时局部已经有一个叫nationality的变量了,就不会到超类中(类似不会去全局中)找nationality变量了。 这个局部的nationality不是公有属性,只有class下的nationality才是。
print( r1.nationality)
print( r2.nationality)
执行结果:
JP
JP
CN #可以看到这里被修改了
JP
#下面这几个代码用于把公有属性的方法 传到外部
def shot2(abc):
print (abc)
print ('in the shot2')
# print (self)
r1.shot = shot2 #相当于将shot2代替r1.shot了
r1.shot(r1.nationality) #所以这里相当于直接调用shot2这个函数
shot2(r1.nationality) #这里相当于直接
r1.shot(r1.nationality) 和shot2(r1.nationality)执行结果:
CN
in the shot2
CN
in the shot2
好了,明白 了类的基本定义,接下来我们一起分解一下上面的代码分别 是什么意思
class Role(object): #定义一个类, class是定义类的语法,Role是类名,(object)是新式类的写法,必须这样写,以后再讲为什么
def __init__(self,name,role,weapon,life_value=100,money=15000): #初始化函数,在生成一个角色时要初始化的一些属性就填写在这里
self.name = name #__init__中的第一个参数self,和这里的self都 是什么意思? 看下面解释
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
上面的这个init()叫做初始化方法(或构造方法), 在类被调用时,这个方法(虽然它是函数形式,但在类中就不叫函数了,叫方法)会自动执行,进行一些初始化的动作,所以我们这里写的init(self,name,role,weapon,life_value=100,money=15000)就是要在创建一个角色时给它设置这些属性,那么这第一个参数self是干毛用的呢?
初始化一个角色,就需要调用这个类一次:
r1 = Role('Alex','police','AK47’) #生成一个角色 , 会自动把参数传给Role下面的__init__(...)方法
r2 = Role('Jack','terrorist','B22’) #生成一个角色
我们看到,上面的创建角色时,我们并没有给init传值,程序也没未报错,是因为,类在调用它自己的init(…)时自己帮你给self参数赋值了,
r1 = Role('Alex','police','AK47’) #此时self 相当于 r1 , Role(r1,'Alex','police','AK47’)
r2 = Role('Jack','terrorist','B22’)#此时self 相当于 r2, Role(r2,'Jack','terrorist','B22’)
为什么这样子?你拉着我说你有些犹豫,怎么会这样子?
你执行r1 = Role('Alex','police','AK47’) 时,python的解释器其实干了两件事:
在内存中开辟一块空间指向r1这个变量名
调用Role这个类并执行其中的init(…)方法,相当于Role.init(r1,'Alex','police',’AK47’),这么做是为什么呢? 是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,是为了把'Alex','police',’AK47’这3个值跟刚开辟的r1关联起来,重要的事情说3次, 因为关联起来后,你就可以直接r1.name, r1.weapon 这样来调用啦。所以,为实现这种关联,在调用init方法时,就必须把r1这个变量也传进去,否则init不知道要把那3个参数跟谁关联呀。
明白了么哥?所以这个init(…)方法里的,self.name = name , self.role = role 等等的意思就是要把这几个值 存到r1的内存空间里。
如果还不明白的话,哥,去测试一下智商吧, 应该不会超过70,哈哈。
为了暴露自己的智商,此时你假装懂了,但又问, init(…)我懂了,但后面的那几个函数,噢 不对,后面那几个方法 为什么也还需要self参数么? 不是在初始化角色的时候 ,就已经把角色的属性跟r1绑定好了么?
good question, 先来看一下上面类中的一个buy_gun的方法:
def buy_gun(self,gun_name):
print(“%s has just bought %s” %(self.name,gun_name) )
上面这个方法通过类调用的话要写成如下:
r1 = Role('Alex','police','AK47')
r1.buy_gun("B21”) #python 会自动帮你转成 Role.buy_gun(r1,”B21")
执行结果
Alex has just bought B21
依然没给self传值 ,但Python还是会自动的帮你把r1 赋值给self这个参数, 为什么呢? 因为,你在buy_gun(..)方法中可能要访问r1的一些其它属性呀, 比如这里就访问 了r1的名字,怎么访问呢?你得告诉这个方法呀,于是就把r1传给了这个self参数,然后在buy_gun里调用 self.name 就相当于调用r1.name 啦,如果还想知道r1的生命值 有多少,直接写成self.life_value就可以了。 说白了就是在调用类中的一个方法时,你得告诉人家你是谁。
好啦, 总结一下2点:
上面的这个r1 = Role('Alex','police','AK47’)动作,叫做类的“实例化”, 就是把一个虚拟的抽象的类,通过这个动作,变成了一个具体的对象了, 这个对象就叫做实例
刚才定义的这个类体现了面向对象的第一个基本特性,封装,其实就是使用构造方法将内容封装到某个具体对象中,然后通过对象直接或者self间接获取被封装的内容
类的析构方法
class Role(object):
nationality = "USA"
def __init__(self, name, role, weapon, life_value=100, money=15000):
self.name = name
self.role = role
self.weapon = weapon
self.life_value = life_value
self.money = money
self.__heart = "Normal"
# 以上代码是属性
def shot(self):
print("shooting...")
def got_shot(self):
print("ah...,I got shot...")
self.__heart = "Die"
def __del__(self): #析构函数(析构方法)
print ("del.....run...")
#__del__在对象(r1)销毁的时候被调用,当对象不再被使用时,__del__方法运行
r1 = Role('Alex', '警察', 'AK47') # 生成一个角色
执行结果:
del.....run...
#代码只做了一个实例化,没有主动去调用del这个方法依然自动执行了
析构函数是用来释放内存空间的,比如一个全局变量被引用后那么除非这个程序执行完成,那么它就会一直占用内存空间; 相关的引用被执行完成后依然会占用内存空间,如果引用过多的话可能你得内存就不够用了,所以需要将接下来不会再次调用,或者使用不到的引用占用的内存空间给释放出来,所以就用到了析构函数,它就是当程序执行完成后会自动调用该虚构函数,然后将内存释放出来。
# 再次强调,析构函数只有在实例(对象)被销毁的时候才会调用!!! 这个析构函数你是否写都会被执行,因为默认有一个析构函数,只不过自己写的话相当于将析构函数给重构了。
封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。(一般就是指能被继承的都比较简单,所以才更容易被继承,继承的子类就是比简单要复杂一些,所以叫做一般到特殊的过程)
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
多级继承:父类>>子类>>子类.......
多重继承:同时继承多个类
继承概念的实现方式主要有2类:实现继承、接口继承。
Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(如:子类继承父类的说话功能,但是会重置这个功能说不通的语言);
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。
抽象类仅定义将由子类创建的一般属性和方法。
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
继承示例:
class Person(object):
#当不需要传参数的时候,可以不创建构造函数
def talk(self):
print ("person is talking...1")
class BlackPerson(Person): #继承Person这个父类
pass
b = BlackPerson()
b.talk() #继承父类,会将其方法也继承
执行结果:
person is talking...1
class Person(object):
def talk(self):
print ("person is talking...1")
class BlackPerson(Person):
#继承后,自己也可以额外的创建其他方法
def walk(self):
print ("is walking......")
b = BlackPerson()
b.talk()
b.walk()
执行结果:
person is talking...1
is walking......
class Person(object):
def talk(self):
print ("person is talking...1")
class BlackPerson(Person):
def talk(self): #自己写一个talk方法
print ("bala..bala.bala")
def walk(self):
print ("is walking......")
b = BlackPerson()
b.talk()
b.walk()
执行结果:
bala..bala.bala #如果本地局部有talk这个方法,就用本地的,没有则用继承的talk方法。
is walking......
class Person(object):
def __init__(self,name,age): #创建构造函数
self.name = name
self.age = age
def talk(self):
print ("person is talking...1")
class BlackPerson(Person):
#子类 没有写构造函数,但是会继承父类的构造函数
def talk(self):
print ("bala..bala.bala")
def walk(self):
print ("is walking......")
b = BlackPerson()
b.talk()
b.walk()
执行结果:
Traceback (most recent call last):
File "E:/python/代码练习/002.py", line 21, in <module>
b = BlackPerson()
TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
#可以看到报错了,这是因为子类继承了父类的构造函数,继承的构造函数需要传参数
b = BlackPerson("smith",30) #修改该代码,传入参数给继承过来的构造函数中的形参
执行结果:
bala..bala.bala
is walking......
##代码中并没有打印参数,所以不会显示
class Person(object):
def __init__(self,name,age): #self==b,name和age等于子类(BlackPerson)的name和age
self.name = name
self.age = age
self.power = "Normal" #self.power这里并没有传入参数
def talk(self):
print ("person is talking...1")
class WhitePerson(Person): #先创建一个白人函数方法备用
pass
class BlackPerson(Person): #继承Person父类
#现在需要给黑人函数增加一个独有的功能“力量”,所以就需要创建构造函数;
#但是因为需要保持继承父类的构造函数功能,这里不能直接重构构造函数,否则就将继承过来的构造函数给覆盖了(局部优先引用),这样父类的功能(name、age)就无法使用了; 所以最后需要不但能够正常继承父类的功能,自己还可以扩展;需要先继承,在重构。
#class BlackPerson(Person)这里已经相当于继承了父类的构造函数,如果子类在创建__init__构造函数的话就是重构 构造函数
def __init__(self,name,age,strength): #重构 构造函数(重构后默认就就不继承了)
#当实例化BlackPerson后候会将b传给子类的self,将参数传给子类形参;
Person.__init__(self,name,age) #调用父类构造函数(同时继承)
#因为重置了构造函数,所以需要调用父类的构造函数,调用了父类的构造函数后,相同名称的属性就会被父类覆盖;在子类中新建且父类构造函数中没有的,此时子类就实现了继承,而且子类同时可以建立新的属性。
#继承构造函数,会调用Person的init方法,init方法要求有self、参数name和age。
print (self.name,self.age,self.power) #这里print的都是Person这个父类的变量
self.strength = strength #设置自己的扩展功能
print (self.strength)
def talk(self): #此处创建talk这个方法,就不会在继承父类的talk方法,这里相当于将talk方法也给重置了
Person.talk(self) #既然不能继承了,却可以调用,我们这里调用一下父类中的talk方法; 不过这样没有什么意义,一般都是重构构造函数,很少有重构方法的。
print ("bala..bala.bala")
def walk(self):
print ("is walking......")
b = BlackPerson("smith",30,"strong") #实例化后的BlackPerson会继承Person
b.talk()
b.walk()
执行结果:
smith 30 Normal
#虽然重构了构造函数,但调用了父类的构造函数,所以相同名称的属性就被覆盖了(smith和30),而self.power并没有重复,所以依然还是从父类继承过来的结果("Normal")
strong
person is talking...1
bala..bala.bala
is walking......
#写一个学校成员的代码,学生和老师都属于成员,每个加进学校的成员都需要注册
class SchoolMember(object): #类的命名首字母要大写,这是命名规范
members = 0 # 初始学校人数为0,公有属性,老师和学生都可以访问
def __init__(self ,name ,age):
self.name = name
self.age = age
self.enroll() # 成员注册,用于被子类继承
def enroll(self):
'''注册'''
print ("just enrolled a new school member [%s]" % self.name)
SchoolMember.members +=1 #调用公有属性(这里不是继承过来的)
#这里不能使用self.member += 1,否则只会在实例中去相加了;不同的实例会在各自的实例中相加,这样打印的时候肯定就不是所有成员总数了
def tell(self):
'''print成员信息'''
print ('-------info:%s--------'%self.name)
for k,v in self.__dict__.items():
print ('\t',k,v)
print ('-------end:%s--------')
def __del__(self):
print ('开出了[%s]...' % self.name)
SchoolMember.members -= 1
class Teacher(SchoolMember):
'''讲师子类'''
def __init__(self,name,age,salary,course):
'''course教课'''
SchoolMember.__init__(self,name,age)
self.salary = salary
self.course = course
def teaching(self):
'''讲课的功能'''
print ('Teacher [%s] is teaching [%s]' %(self.name,self.course))
class Student(SchoolMember):
'''学生子类'''
def __init__(self,name,age,course,tuition):
'''tuition学费'''
SchoolMember.__init__(self,name,age)
self.course = course
self.tuition = tuition
self.amount = 0 #amount交学费的功能,默认0元
def pay_tuition(self,amount):
'''交学费的功能'''
print ("student [%s] has just paied [%s]" %(self.name,amount))
self.amount += amount
t1 = Teacher("Wusir",28,3000,"Python")
s1 = Student("HaiTao",38,"PYS16",30000)
s2 = Student("LiChuang",12,"PYS16",11000)
print (SchoolMember.members)
del s2 #使用del函数会直接调用到s2父类中的析构函数__del__; (del s2就会触发Student继承了SchoolMember下的def __del__(self),然后执行SchoolMember.members -= 1)
print (SchoolMember.members)
执行结果:
just enrolled a new school member [Wusir]
just enrolled a new school member [HaiTao]
just enrolled a new school member [LiChuang]
3
开出了[LiChuang]... #del s2时会直接跳到父类中的__del__(因为Student继承了)
2
#下面两个被删除是因为在程序结束的时候会自动调用析构函数,如果使用debug一直执行的话就不会出现下面两个信息了
开出了[Wusir]...
开出了[HaiTao]...
print (t1.__dict__)
执行结果:
{'name': 'Wusir', 'age': 28, 'salary': 3000, 'course': 'Python'}
可以看到,可以将t1的形参和实际参数通过字典的方式打印出来
t1.tell() #直接调用t1实例中的tell()方法
s1.tell()
s2.tell()
执行结果:
-------info:Wusir--------
name Wusir
age 28
salary 3000
course Python
-------end:%s--------
-------info:HaiTao--------
name HaiTao
age 38
course PYS16
tuition 30000
amount 0
-------end:%s--------
-------info:LiChuang--------
name LiChuang
age 12
course PYS16
tuition 11000
amount 0
-------end:%s--------
SchoolMember.__init__(self, name, age) #经典类写法
super(Teacher,self).__init__(name,age) #新式类写法,也是用来继承父类的,效果一样
多继承
class SchoolMember(object):
members = 0
def __init__(self ,name, age):
self.name = name
self.age = age
self.enroll()
def enroll(self):
'''注册'''
print("just enrolled a new school member [%s]" % self.name)
SchoolMember.members += 1
def tell(self):
'''print成员信息'''
print('-------info:%s--------' % self.name)
for k, v in self.__dict__.items():
print('\t', k, v)
print('-------end:%s--------')
def __del__(self):
print('开出了[%s]...' % self.name)
SchoolMember.members -= 1
class Second(object): #新建一个父类
'''第二个父类'''
def test(self):
print ('test......................')
class Teacher(SchoolMember,Second): #继承新的父类
'''讲师子类'''
def __init__(self, name, age, salary, course):
'''course教课'''
super(Teacher,self).__init__(name,age) #使用的是新式类
self.salary = salary
self.course = course
def teaching(self):
'''讲课的功能'''
print('Teacher [%s] is teaching [%s]' % (self.name, self.course))
class Student(SchoolMember):
'''学生子类'''
def __init__(self, name, age, course, tuition):
'''tuition学费'''
SchoolMember.__init__(self, name, age)
self.course = course
self.tuition = tuition
self.amount = 0 # amount交学费的功能,默认0元
def pay_tuition(self, amount):
'''交学费的功能'''
print("student [%s] has just paied [%s]" % (self.name, amount))
self.amount += amount
t1 = Teacher("Wusir", 28, 3000, "Python")
t1.test() #调用集成父类中的方法
s1 = Student("HaiTao", 38, "PYS16", 30000)
s2 = Student("LiChuang", 12, "PYS16", 11000)
print(SchoolMember.members)
del s2
print(SchoolMember.members)
执行结果:
just enrolled a new school member [Wusir]
test...................... #成功执行新父类中的test方法; 继承多个父类这种方式实际中用的较少。
just enrolled a new school member [HaiTao]
just enrolled a new school member [LiChuang]
3
开出了[LiChuang]...
2
开出了[Wusir]...
开出了[HaiTao]...
#### 新式类 VS 经典类
class Person(object): #新式类; ,
class Person: #经典类
SchoolMember.__init__(self, name, age) #经典类写法
super(Teacher,self).__init__(name,age) #新式类写法,也是用来继承父类的,效果一样
现在大家一般都写新式类,新式类中的object是一个基类,通过Ctrl+object点进去可以看到如下:
这个object本身就是个基类,下面定义了很多方法,当更新python版本,如果在object加入了新的方法,我们默认就会继承过来。
class A(object):
def __init__(self):
self.n = 'A'
class B(A): #继承A
def __init__(self):
self.n = 'B'
class C(A): #继承A
def __init__(self):
self.n = 'C'
class D(B,C): #继承B和C
def __init__(self):
self.n = 'D'
d = D()
print (d.n)
执行结果:
D #每一个class都定义了self.n,肯定是局部的优先级最高,所以结果是D
当前逻辑关系
class A(object):
def __init__(self):
self.n = 'A'
class B(A): #继承A
def __init__(self):
self.n = 'B'
class C(A): #继承A
def __init__(self):
self.n = 'C'
class D(B,C): #继承B和C
pass
#def __init__(self):
#self.n = 'D'
d = D()
print (d.n)
执行结果:
B #从左到右先继承了B中的self.n,与C中的self.n冲突的话,就不会再去继承C的self.n了。
class A(object):
def __init__(self):
self.n = 'A'
class B(A):
pass
# def __init__(self):
# self.n = 'B'
class C(A):
def __init__(self):
self.n = 'C'
class D(B,C):
pass
# def __init__(self):
# self.n = 'D'
d = D()
print (d.n)
执行结果:
C #从左到右,B去掉了,就会继承C; 如果此时还有E、F什么的,左面找不到的话,只要右边存在就会一直向右查找,这叫做广度查询
总结继承顺序:D、B、C、A
class A: #修改为经典类,然后复现上面代码的情况
def __init__(self):
self.n = 'A'
class B(A):
pass
# def __init__(self):
# self.n = 'B'
class C(A):
def __init__(self):
self.n = 'C'
class D(B,C):
pass
# def __init__(self):
# self.n = 'D'
d = D()
print (d.n)
执行结果:
C #可以看到执行结果依然是C,好像看似经典类和新式类没什么区别,下面我们换到Python2的环境使用这一段代码测试
#依然是上面的代码
class A:
def __init__(self):
self.n = 'A'
class B(A):
pass
# def __init__(self):
# self.n = 'B'
class C(A):
def __init__(self):
self.n = 'C'
class D(B,C):
pass
# def __init__(self):
# self.n = 'D'
d = D()
print (d.n)
可以看到在linux系统中用python 2来执行该代码,结果却是A,没有进行广度(横着)查询,而是进行了深度查找(竖着查找)。
class A:
pass
# def __init__(self):
# self.n = 'A'
class B(A):
pass
# def __init__(self):
# self.n = 'B'
class C(A):
def __init__(self):
self.n = 'C'
class D(B,C):
pass
# def __init__(self):
# self.n = 'D'
d = D()
print (d.n)
可以看到将D、B、A都注释掉以后,结果是C
在python3中,无论是经典类还是新式类,结果都广度查找;在python2中经典类是深度查找,新式类是广度查找。
最后建议:
class创建方法和继承方式都用新式类;
创建class用新式类,可以广度查找,这样查找时会更优。
继承时使用super(新式类),可以同时继承多重父类,不像经典类,必须指定父类的名称(继承多少个父类,就要指定几个)才能继承;当多继承时要注意的是子类会从左到右的广度查找顺序来继承,左边的父类如果没有找到则向右去找(广度查找),这个广度查找只是先去找构造函数和对应的属性,否则就去找下一个父类的构造函数和属性; 当继承多个父类的时候,每个父类都有参数的话,只能继承其中一个父类的参数,假如A父类有1、2两个参数,B父类有3这一个参数,那么1、2、3这个三个参数不能同时被继承,所以在写代码的时候,最好是写只继承一个父类即可!!!!!!!!!!
如果一定要继承多个父类的话,请按照下面举例来写代码:
class AA(object): #父类AA
def __init__(self,A1,A2):
self.A1 = A1
self.A2 = A2
class BB(object): #父类BB
def __init__(self,B1,B2):
self.B1 = B1
self.B2 = B2
class ZiLei(AA):
def __init__(self,A1,A2,name1,BB_obj): #先重构
super(ZiLei,self).__init__(A1,A2) #在继承
self.BB = BB_obj #这里直接将BB这个类传进来即可,通过这种方式关联其他父类的内容
print (self.BB.B1) #在self.BB后面再通过 点 来调用相应的内容
print (self.BB.B2)
BB_obj = BB('b111','b222') #实例化BB这个类
zilei = ZiLei('a111','a222','name1111',BB_obj) #将BB_obj这个实力传入
执行结果:
b111
b222
多态
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。(举例多态:比如都会说话,但是说的语言不同,这就是通过了不同的方式表现出来的,所以叫多态)
Pyhon 很多语法都是支持多态的,比如 len(),sorted(), 你给len传字符串就返回字符串的长度,传列表就返回列表长度。
在python中是不能直接支持多态的,需要间接的实现多态; 直接就是需要从父类调用子类中的方法,可是python实现不了,Java可以。
class Animal(object):
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
#raise是主动使其发送一个错误到执行后的屏幕身上
class Cat(Animal):
def talk(self):
print('%s: 喵喵喵!' %self.name)
class Dog(Animal):
def talk(self):
print('%s: 汪!汪!汪!' % self.name)
c1 = Cat('小白')
d1 = Dog('小黑')
c1.talk()
d1.talk()
执行结果:
小白: 喵喵喵!
小黑: 汪!汪!汪!
#我们在执行的时候指定了c1.talk和d1.talk方法才会去执行,这里需要指定两个接口,而我们不想通过指定c1或d1才会执行两个接口; 只想通过一个接口来实现方法的执行,只是在执行的时加入不同的形态(c1就是猫叫,加入d1就是狗叫),来实现一个接口,多种形态。
class Animal(object):
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def talk(self):
print('%s: 喵喵喵!' %self.name)
class Dog(Animal):
def talk(self):
print('%s: 汪!汪!汪!' % self.name)
def func(obj): # 一个接口,多种形态
obj.talk()
c1 = Cat('小白')
d1 = Dog('小黑')
func(c1) #将c1传给obj参数,然后在执行obj.talk(),其实相当于变相执行c1.talk(),只不过可以利用这种方法实现一个借口,多种形态; 这只是一个这种的方法,并不是真正的多态实现方法。
func(d1)
class Animal(object):
def __init__(self, name): # Constructor of the class
self.name = name
def talk(self): # Abstract method, defined by convention only
raise NotImplementedError("Subclass must implement abstract method")
class Cat(Animal):
def talk(self):
print('%s: 喵喵喵!' %self.name)
class Dog(Animal):
def talk(self):
print('%s: 汪!汪!汪!' % self.name)
def func(obj): # 一个接口,多种形态
obj.talk()
c1 = Cat('小白')
d1 = Dog('小黑')
Animal.talk(c1)
Animal.talk(d1)
执行结果:
Traceback (most recent call last):
File "E:/Python/练习代码/001.py", line 29, in <module>
Animal.talk(c1)
File "E:/Python/练习代码/001.py", line 7, in talk
raise NotImplementedError("Subclass must implement abstract method")
NotImplementedError: Subclass must implement abstract method
#可以看到并没有执行成功,并报错。
这是因为当使用Animal.talk(c1)时,在子类中talk这个方法已经被重写了,重写后只能通过c1.talk()这种方式调用了
面向对象补充
1、 什么是面向对象编程?
- 之前在使用函数的时候,直接调用函数就会执行里面代码实现想要的功能。
- 而面向对象是通过类 + 对象实现函数的调用。
2、 什么是类、什么是对象,它们之间又有什么关系?
- class 类:
def 函数1():
pass
def 函数2():
pass
obj = 类() #obj是对象,对象就是将类实例化的过程
obj.函数1() #在面向对象中只不能直接调用函数,需要通过对象来调用函数(方法)
每创建一个对象,都会存在内存中,对象越多内存开销越大;所以说从调用、使用的角度来看,直接调用函数是更方便的,而面向对象反而会更复杂。 在实际的工作当中面向对象(面向对象编程)能实现的功能,通过直接使用函数(函数式编程)都能实现。
有时候,通过函数式编程实现会比较麻烦,
而使用面向对象简单、快速的就能实现。
3、 什么时候适用面向对象?
- 通过函数编程:
通过函数时编程创建可以连接服务器、上传文件、执行命令、关闭连接的功能。
def upload(): #该函数只能上传文件
连接服务器
上传文件
关闭
def cmd(): #该函数只能执行命令
连接服务器
执行命令
关闭
def cmd():
连接服务器 #该函数既能上传文件又能执行命令
上传文件
执行命令
关闭
可以看到通过函数时编程,每做一个动作就需要连接服务器,然后关闭服务器,这样不断的进行了数次连接断开操作。
- 通过类来编程
class ssh:
def __init__(self,host,port,username,passWord):
self.host = ...
def connection(self):
# 去创建连接
self.conn = 和服务器创建的连接对象()
def close(self):
# 关闭
self.conn.关闭
def upload(self):
self.conn 使用这个连接上传文件
def cmd(self):
self.conn 使用这个连接执行命令
obj = SSH(参数) #修改这里的host(IP)和port参数就可以连接其他SSH 服务器,所以面向对象的改造型比较强
obj.connection()
obj.cmd()
obj.upload()
obj.cmd()
obj.upload()
obj.cmd()
obj.upload()
obj.close()
#我们可以使用一个连接,做多次操作,而不需要反复的许建立连接和关闭
-
应用场景
1、可扩展/复用:面向对象中可以建立公共的功能,然后我们可以新建方法,新的方法都可以来调用这个功能;有多个新的方法要调用这个功能的话就可以实现复用,然后新的方法自己也可以单独创建自己的功能实现扩展。
2、模板:比如创建一个人的模板,有眼睛能看东西,有嘴巴能说话等等,然后根据这个模板创建不同的人,不同的人眼睛可能颜色不同,然后嘴巴说的语言不同等等,但不管你新创造的人是什么样,都会具备眼睛、嘴巴这些功能。
3、 解决重复传参数
def f1(host,port,pwd,arg):
pass
def f2(host,port,pwd,arg1,arg2):
pass
def f3(host,port,pwd,arg1,arg2,arg3):
pass
f1(1,2,3,4)
f2(1,2,3,4,5)
f3(1,2,3,4,5,6)
上面的代码可以看到在调用函数传参数的时候,也是传重复的内容到不同的函数中。
class FOO:
def __init__(self,host,port,pwd):
self.host = host
self.port = port
self.pwd = pwd
def f1(self,arg1):
self.host
self.port
self.pwd
pass
def f2(self,arg1,arg2):
self.host
self.port
self.pwd
pass
def f3(self,arg1,arg2,arg3):
self.host
self.port
self.pwd
pass
obj = FOO(1,1,1)
obj.f1(2)
obj,f2(3,4)
obj,f3(5,6,7)
上面的代码中在调用代码时可以看到,传参数时可以避免传入重复的参数到类的方法中,在类的方法中直接通过self.xxx就可以调用了。
4、self
self就是一个形参
self将对象当做参数传入
class FOO: #创建类,这个类在内存中创建一块空间
def __init__(self,name):
self.NAME = name #当实例化后,相当于将对象传给了self这个形参,然后self.NAME相当于obj1.NAME,这个self.NAME属性在obj1的内存空间中体现
def bar(self): #类中的方法和类关联,在类的内存空间中体现
pass
obj1 = FOO('alex') #实例化的对象,这个对象会在内存中单独创建一块obj1空间
obj1.bar()
obj2 = FOO('eric') #独立创建一个obj2的内存空间
obj2.bar()
#目前为止有3个内存空间FOO、obj1、obj2
class FOO:
def __init__(self,name,count):
self.NAME = name #这里可以叫普通字段或普通属性
self.COUNT = count
self.Country = '中国' #这个属性会被赋予每一个对象,中国有32个省,如要针对每一个省写一个对象,那么就相当于有32个self.Country属性被保存到每一个对象中了
def bar(self):
print (self.NAME)
print (self.COUNT)
print (self.Country)
obj1 = FOO('河南',12345)
obj1.bar()
obj2 = FOO('山东',131313)
obj2.bar()
class FOO:
country = '中国' #公有属性在内存中属于class,不属于对象
#在class下面创建一个变量叫做静态字段或公有属性,这个公有属性只会在class下面保存1份(32个省也是1份),class下面的每一个方法都可以调用该公有属性;
#使用场景:当每一个方法都会用到同一个属性的时候,就可以使用这个公有属性。
def __init__(self,name,count):
self.NAME = name
self.COUNT = count
def bar(self):
print (self.NAME)
print (self.COUNT)
obj1 = FOO('河南',12345)
obj1.bar()
obj2 = FOO('山东',131313)
obj2.bar()
5、封装
类中封装了:字段(公有属性)、方法
对象中封装了:普通字段的值(就是通过对象传进来的参数值)
class F1:
def __init__(self,n):
self.N = n
print ('F1')
class F2:
def __init__(self,arg1):
self.a = arg1
print ('F2')
class F3:
def __init__(self,arg2):
self.b = arg2
print ('F3')
o1 = F1('alex')
o2 = F2(o1) #o2这个对象封装了o1这个对象
o3 = F3(o2) #o3这个对象封装了o2这个对象
#o3.b=o2
#o2.a=o1
#o3.b.a=o1
#03.b.a.N=o1.N
#o3.b.a.N=alex
print (o3.b.a.N)
#o3.b相当于o2
#o3.b.a相当于o1
执行结果:
F1
F2
F3
alex
6、 继承
class F1:
def __init__(self,n):
print ('F1')
def a1(self):
print ('F1:a1')
class F2(F1):
def __init__(self):
print ('F2')
def a1(self):
print ('F2:a1')
class F3(F2):
def __init__(self):
print ('F3')
def a1(self):
print ('F3:a1')
obj = F3()
obj.a1()
执行结果:
F3
F3:a1
#局部优先,所以取值F3类中的a1优先,如果没有才会继承
class F1:
def __init__(self,n):
print ('F1')
def a1(self):
print ('F1:a1')
def a2(self):
print ('F1:a2')
class F2(F1):
def __init__(self):
print ('F2')
def a1(self):
self.a2()
print ('F2:a1')
def a2(self):
print ('F2:a2')
class F3(F2):
def __init__(self):
print ('F3')
def a1(self):
print ('F3:a1')
def a2(self): #此时self是obj这个对象,对象实例化的是F3,那么a2也就是class F3中的方法a2
print ('F3:a2')
obj = F3()
obj.a2()
7、多态
多种形态(这里没有补充)
8、
普通字段:普通属性,保存在对象中
静态字段:公有属性,保存在类中
方法:保存在类中,被对象调用(通过对象来访问方法)
静态方法:
class F1:
@staticmethod
def a1(): #在类中的函数正常必须有self,但是用了@staticmethod后就可以让该类和类里面的函数当做一个正常的函数来使用
print ('alex')
F1() #这里无需创建对象,直接调用类即可
F1.a1()
#以上代码,其实效果和函数一模一样,没什么特别的,只是有这么一种写法而已。
相关文章