当前位置: 首页 > 编程日记 > 正文

16.1、python初识面向对象(1)

初识面向对象

楔子

你现在是一家游戏公司的开发人员,现在需要你开发一款叫做<人狗大战>的游戏,你就思考呀,人狗作战,那至少需要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("egon",27,"F","Teacher")

两个角色对象生成了,狗和人还有不同的功能呀,狗会咬人,人会打狗,对不对? 怎么实现呢,。。想到了, 可以每个功能再写一个函数,想执行哪个功能,直接 调用 就可以了,对不?

def bark(d):

print("dog %s:wang.wang..wang..."%d['name'])

def walk(p):

print("person %s is walking..." %p['name'])<br><br>

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

}

return data

d1 = dog("李磊","京巴")

p1 = person("严帅",36,"F","运维")

p2 = person("egon",27,"F","Teacher")

d1['bark'](p1) #把人传给了狗的方法

你是如此的机智,这样就实现了限制人只能用人自己的功能啦。

刚刚你用的这种编程思想其实就是简单的面向对象编程,我们创造了两个模子表示游戏里所有的人和狗之后,剩下的狗叫或者人走对于这两个模子来说就不重要了。具体人he狗之间的交互就等着你去使用了。假如你和狗打起来了,这时候你是走路还是拿棍子打狗就由你自己决定了。那你的每一个决定可能都影响着你这场游戏的输赢。这也是不确定的。和我们之前写代码按部就班的走,最终都会实现我们要完成的事情不太一样了。

尽管如此,我们也只完成了这个游戏非常小的一部分。还有很多功能都没有实现。

刚才你只是阻止了两个完全 不同的角色 之前的功能混用, 但有没有可能 ,同一个种角色,但有些属性是不同的呢? 比如 ,大家都打过cs吧,cs里有警察和恐怖份子,但因为都 是人, 所以你写一个角色叫 person(), 警察和恐怖份子都 可以 互相射击,但警察不可以杀人质,恐怖分子可以,这怎么实现呢? 你想了说想,说,简单,只需要在杀人质的功能里加个判断,如果是警察,就不让杀不就ok了么。 没错, 这虽然 解决了杀人质的问题,但其实你会发现,警察和恐怖分子的区别还有很多,同时又有很多共性,如果 在每个区别处都 单独做判断,那得累死。

你想了想说, 那就直接写2个角色吧, 反正 这么多区别, 我的哥, 不能写两个角色呀,因为他们还有很多共性 , 写两个不同的角色,就代表 相同的功能 也要重写了,是不是我的哥? 。。。

好了, 话题就给你点到这, 再多说你的智商也理解不了了!

面向过程 VS 面向对象

面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。

优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。

缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。

应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。


面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。

面向对象的程序设计的

优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。

缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。

应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。

在python 中面向对象的程序设计并不是全部。

面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。

了解一些名词:类、对象、实例、实例化

类:具有相同特征的一类事物(人、狗、老虎)

对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)

实例化:类——>对象的过程(这在生活中表现的不明显,我们在后面再慢慢解释)

初识类和对象

python中一切皆为对象,类型的本质就是类,所以,不管你信不信,你已经使用了很长时间的类了

>>> dict #类型dict就是类dict<class 'dict'>

>>> d=dict(name='eva') #实例化>>> d.pop('name') #向d发一条消息,执行d的方法pop'eva'

从上面的例子来看,字典就是一类数据结构,我一说字典你就知道是那个用{}表示,里面由k-v键值对的东西,它还具有一些增删改查的方法。但是我一说字典你能知道字典里具体存了哪些内容么?不能,所以我们说对于一个类来说,它具有相同的特征属性和方法。

而具体的{'name':'eva'}这个字典,它是一个字典,可以使用字典的所有方法,并且里面有了具体的值,它就是字典的一个对象。对象就是已经实实在在存在的某一个具体的个体。


再举一个其他的例子,通俗一点,比如你现在有一个动物园,你想描述这个动物园,那么动物园里的每一种动物就是一个类,老虎、天鹅、鳄鱼、熊。他们都有相同的属性,比如身高体重出生时间和品种,还有各种动作,比如鳄鱼会游泳,天鹅会飞,老虎会跑,熊会吃。

但是这些老虎熊啥的都不是具体的某一只,而是一类动物。虽然他们都有身高体重,但是你却没有办法确定这个值是多少。如果这个时候给你一只具体的老虎,而你还没死,那你就能给他量量身高称称体重,这些数值是不是就变成具体的了?那么具体的这一只老虎就是一个具体的实例,也是一个对象。不止这一只,其实每一只具体的老虎都有自己的身高体重,那么每一只老虎都是老虎类的一个对象。

在python中,用变量表示特征,用函数表示技能,因而具有相同特征和技能的一类事物就是‘类’,对象是则是这一类事物中具体的一个。



类的相关知识

初识类

声明

def functionName(args):

'函数文档字符串'      函数体

'''

class 类名:

'类的文档字符串'

类体

'''#我们创建一个类class Data:

pass

class Person:  #定义一个人类    role = 'person'  #人的角色属性都是人    def walk(self):  #人都可以走路,也就是有一个走路方法,也叫动态属性        print("person is walking...")


类有两种作用:属性引用和实例化

属性引用(类名.属性)

class Person:  #定义一个人类    role = 'person'  #人的角色属性都是人    def walk(self):  #人都可以走路,也就是有一个走路方法        print("person is walking...")print(Person.role)  #查看人的role属性print(Person.walk)  #引用人的走路方法,注意,这里不是在调用

实例化:类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征

class Person:  #定义一个人类    role = 'person'  #人的角色属性都是人    def __init__(self,name):

self.name = name  # 每一个角色都有自己的昵称;

def walk(self):  #人都可以走路,也就是有一个走路方法        print("person is walking...")print(Person.role)  #查看人的role属性print(Person.walk)  #引用人的走路方法,注意,这里不是在调用

实例化的过程就是类——>对象的过程

原本我们只有一个Person类,在这个过程中,产生了一个egg对象,有自己具体的名字、攻击力和生命值。

语法:对象名 = 类名(参数)

egg = Person('egon')  #类名()就等于在执行Person.__init__()

#执行完__init__()就会返回一个对象。这个对象类似一个字典,存着属于这个人本身的一些属性和方法。

查看属性&调用方法

print(egg.name)    #查看属性直接 对象名.属性名print(egg.walk())  #调用方法,对象名.方法名()

关于self

self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字,但是正常人都不会这么做。

因为你瞎改别人就不认识

类属性的补充

一:我们定义的类的属性到底存到哪里了?有两种方式查看

dir(类名):查出的是一个名字列表

类名.__dict__:查出的是一个字典,key为属性名,value为属性值

二:特殊的类属性

类名.__name__# 类的名字(字符串)类名.__doc__# 类的文档字符串类名.__base__# 类的第一个父类(在讲继承时会讲)类名.__bases__# 类所有父类构成的元组(在讲继承时会讲)类名.__dict__# 类的字典属性类名.__module__# 类定义所在的模块类名.__class__# 实例对应的类(仅新式类中)


对象的相关知识

回到咱们的人狗大战:现在我们需要对我们的类做出一点点改变

人类除了可以走路之外,还应该具备一些攻击技能。

class Person:  # 定义一个人类    role = 'person'  # 人的角色属性都是人    def __init__(self, name, aggressivity, life_value):

self.name = name  # 每一个角色都有自己的昵称;        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;        self.life_value = life_value  # 每一个角色都有自己的生命值;    def attack(self,dog):

# 人可以攻击狗,这里的狗也是一个对象。        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降        dog.life_value -= self.aggressivity

对象是关于类而实际存在的一个例子,即实例

对象/实例只有一种作用:属性引用

egg = Person('egon',10,1000)print(egg.name)print(egg.aggressivity)print(egg.life_value)

当然了,你也可以引用一个方法,因为方法也是一个属性,只不过是一个类似函数的属性,我们也管它叫动态属性。

引用动态属性并不是执行这个方法,要想调用方法和调用函数是一样的,都需要在后面加上括号

print(egg.attack)

我知道在类里说,你可能还有好多地方不能理解。那我们就用函数来解释一下这个类呀,对象呀到底是个啥,你偷偷的用这个理解就好了,不要告诉别人

def Person(*args,**kwargs):

self = {}

def attack(self,dog):

dog['life_value'] -= self['aggressivity']

def __init__(name,aggressivity,life_value):

self['name'] = name

self['aggressivity'] = aggressivity

self['life_value'] = life_value

self['attack'] = attack

__init__(*args,**kwargs)

return self

egg = Person('egon',78,10)print(egg['name'])

面向对象小结——定义及调用的固定模式

class 类名:

def __init__(self,参数1,参数2):

self.对象的属性1 = 参数1

self.对象的属性2 = 参数2

def 方法名(self):pass    def 方法名2(self):pass对象名 = 类名(1,2)  #对象就是实例,代表一个具体的东西                  #类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法                  #括号里传参数,参数不需要传self,其他与init中的形参一一对应                  #结果返回一个对象对象名.对象的属性1  #查看对象的属性,直接用 对象名.属性名 即可对象名.方法名()    #调用类中的方法,直接用 对象名.方法名() 即可

练习一:在终端输出如下信息

小明,10岁,男,上山去砍柴

小明,10岁,男,开车去东北

小明,10岁,男,最爱大保健

老李,90岁,男,上山去砍柴

老李,90岁,男,开车去东北

老李,90岁,男,最爱大保健

老张…


对象之间的交互

现在我们已经有一个人类了,通过给人类一些具体的属性我们就可以拿到一个实实在在的人。

现在我们要再创建一个狗类,狗就不能打人了,只能咬人,所以我们给狗一个bite方法。

有了狗类,我们还要实例化一只实实在在的狗出来。

然后人和狗就可以打架了。现在我们就来让他们打一架吧!

创建一个狗类

class Dog:  # 定义一个狗类    role = 'dog'  # 狗的角色属性都是狗    def __init__(self, name, breed, aggressivity, life_value):

self.name = name  # 每一只狗都有自己的昵称;        self.breed = breed  # 每一只狗都有自己的品种;        self.aggressivity = aggressivity  # 每一只狗都有自己的攻击力;        self.life_value = life_value  # 每一只狗都有自己的生命值;    def bite(self,people):

# 狗可以咬人,这里的狗也是一个对象。        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降

dog.life_value -= self.aggressivit



实例化一只实实在在的二哈

ha2 = Dog('二愣子','哈士奇',10,1000)  #创造了一只实实在在的狗ha2


交互 egon打ha2一下

print(ha2.life_value)        #看看ha2的生命值egg.attack(ha2)              #egg打了ha2一下print(ha2.life_value)        #ha2掉了10点血

完整的代码

class Person:  # 定义一个人类    role = 'person'  # 人的角色属性都是人    def __init__(self, name, aggressivity, life_value):

self.name = name  # 每一个角色都有自己的昵称;        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;        self.life_value = life_value  # 每一个角色都有自己的生命值;    def attack(self,dog):

# 人可以攻击狗,这里的狗也是一个对象。        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降        dog.life_value -= self.aggressivityclass Dog:  # 定义一个狗类    role = 'dog'  # 狗的角色属性都是狗    def __init__(self, name, breed, aggressivity, life_value):

self.name = name  # 每一只狗都有自己的昵称;        self.breed = breed  # 每一只狗都有自己的品种;        self.aggressivity = aggressivity  # 每一只狗都有自己的攻击力;        self.life_value = life_value  # 每一只狗都有自己的生命值;    def bite(self,people):

# 狗可以咬人,这里的狗也是一个对象。        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降        people.life_value -= self.aggressivity

egg = Person('egon',10,1000)  #创造了一个实实在在的人eggha2 = Dog('二愣子','哈士奇',10,1000)  #创造了一只实实在在的狗ha2print(ha2.life_value)        #看看ha2的生命值egg.attack(ha2)              #egg打了ha2一下print(ha2.life_value)        #ha2掉了10点血

from math import piclass Circle:

'''

定义了一个圆形类;

提供计算面积(area)和周长(perimeter)的方法

'''    def __init__(self,radius):

self.radius = radius

def area(self):

return pi * self.radius * self.radius

def perimeter(self):

return 2 * pi *self.radius

circle =  Circle(10) #实例化一个圆area1 = circle.area() #计算圆面积per1 = circle.perimeter() #计算圆周长print(area1,per1) #打印圆面积和周长


类命名空间与对象、实例的命名空间

创建一个类就会创建一个类的名称空间,用来存储类中定义的所有名字,这些名字称为类的属性

而类有两种属性:静态属性和动态属性

静态属性就是直接在类中定义的变量

动态属性就是定义在类中的方法

其中类的数据属性是共享给所有对象的

>>>id(egg.role)

4341594072

>>>id(Person.role)

4341594072


而类的动态属性是绑定到所有对象的

>>>egg.attack

<bound method Person.attack of <__main__.Person object at 0x101285860>>

>>>Person.attack

<function Person.attack at 0x10127abf8>

创建一个对象/实例就会创建一个对象/实例的名称空间,存放对象/实例的名字,称为对象/实例的属性

在obj.name会先从obj自己的名称空间里找name,找不到则去类中找,类也找不到就找父类...最后都找不到就抛出异常

面向对象的组合用法

软件重用的重要方式除了继承之外还有另外一种方式,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

class Weapon:

def prick(self, obj):  # 这是该装备的主动技能,扎死对方        obj.life_value -= 500  # 假设攻击力是500class Person:  # 定义一个人类    role = 'person'  # 人的角色属性都是人    def __init__(self, name):

self.name = name  # 每一个角色都有自己的昵称;        self.weapon = Weapon()  # 给角色绑定一个武器;

egg = Person('egon')

egg.weapon.prick() #egg组合了一个武器的对象,可以直接egg.weapon来使用组合类中的所有方法

圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。

这个时候,我们就首先实现一个圆形类,计算一个圆的周长和面积。然后在"环形类"中组合圆形的实例作为自己的属性来用

from math import piclass Circle:

'''

定义了一个圆形类;

提供计算面积(area)和周长(perimeter)的方法

'''    def __init__(self,radius):

self.radius = radius

def area(self):

return pi * self.radius * self.radius

def perimeter(self):

return 2 * pi *self.radius

circle =  Circle(10) #实例化一个圆area1 = circle.area() #计算圆面积per1 = circle.perimeter() #计算圆周长print(area1,per1) #打印圆面积和周长class Ring:

'''

定义了一个圆环类

提供圆环的面积和周长的方法

'''    def __init__(self,radius_outside,radius_inside):

self.outsid_circle = Circle(radius_outside)

self.inside_circle = Circle(radius_inside)

def area(self):

return self.outsid_circle.area() - self.inside_circle.area()

def perimeter(self):

return  self.outsid_circle.perimeter() + self.inside_circle.perimeter()

ring = Ring(10,5) #实例化一个环形print(ring.perimeter()) #计算环形的周长print(ring.area()) #计算环形的面积


用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python课程

class BirthDate:

def __init__(self,year,month,day):

self.year=year

self.month=month

self.day=dayclass Couse:

def __init__(self,name,price,period):

self.name=name

self.price=price

self.period=periodclass Teacher:

def __init__(self,name,gender,birth,course):

self.name=name

self.gender=gender

self.birth=birth

self.course=course

def teach(self):

print('teaching')

p1=Teacher('egon','male',

BirthDate('1995','1','27'),

Couse('python','28000','4 months')

)

print(p1.birth.year,p1.birth.month,p1.birth.day)

print(p1.course.name,p1.course.price,p1.course.period)

'''

运行结果:

1 27

python 28000 4 months

'''

当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好


初识面向对象小结

定义一个人类

class Person:  # 定义一个人类    role = 'person'  # 人的角色属性都是人    def __init__(self, name, aggressivity, life_value, money):

self.name = name  # 每一个角色都有自己的昵称;        self.aggressivity = aggressivity  # 每一个角色都有自己的攻击力;        self.life_value = life_value  # 每一个角色都有自己的生命值;        self.money = money

def attack(self,dog):

# 人可以攻击狗,这里的狗也是一个对象。        # 人攻击狗,那么狗的生命值就会根据人的攻击力而下降

dog.life_value -= self.aggressivity


定义一个狗类

class Dog:  # 定义一个狗类    role = 'dog'  # 狗的角色属性都是狗    def __init__(self, name, breed, aggressivity, life_value):

self.name = name  # 每一只狗都有自己的昵称;        self.breed = breed  # 每一只狗都有自己的品种;        self.aggressivity = aggressivity  # 每一只狗都有自己的攻击力;        self.life_value = life_value  # 每一只狗都有自己的生命值;    def bite(self,people):

# 狗可以咬人,这里的狗也是一个对象。        # 狗咬人,那么人的生命值就会根据狗的攻击力而下降        people.life_value -= self.aggressivity


接下来,又创建一个新的兵器类。

class Weapon:

def __init__(self,name, price, aggrev, life_value):

self.name = name

self.price = price

self.aggrev = aggrev

self.life_value = life_value

def update(self, obj):  #obj就是要带这个装备的人        obj.money -= self.price  # 用这个武器的人花钱买所以对应的钱要减少        obj.aggressivity += self.aggrev  # 带上这个装备可以让人增加攻击        obj.life_value += self.life_value  # 带上这个装备可以让人增加生命值    def prick(self, obj):  # 这是该装备的主动技能,扎死对方        obj.life_value -= 500  # 假设攻击力是500


测试交互

lance = Weapon('长矛',200,6,100)

egg = Person('egon',10,1000,600)  #创造了一个实实在在的人eggha2 = Dog('二愣子','哈士奇',10,1000)  #创造了一只实实在在的狗ha2#egg独自力战"二愣子"深感吃力,决定穷毕生积蓄买一把武器if egg.money > lance.price: #如果egg的钱比装备的价格多,可以买一把长矛    lance.update(egg) #egg花钱买了一个长矛防身,且自身属性得到了提高    egg.weapon = lance #egg装备上了长矛print(egg.money,egg.life_value,egg.aggressivity)print(ha2.life_value)

egg.attack(ha2)  #egg打了ha2一下print(ha2.life_value)

egg.weapon.prick(ha2) #发动武器技能print(ha2.life_value) #ha2不敌狡猾的人类用武器取胜,血槽空了一半

按照这种思路一点一点的设计类和对象,最终你完全可以实现一个对战类游戏。


面向对象的三大特性


继承

什么是继承

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类


python中类的继承分为:单继承和多继承

class ParentClass1: #定义父类    passclass ParentClass2: #定义父类    passclass SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass    passclass SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类    pass

查看继承

>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类(<class '__main__.ParentClass1'>,)

>>> SubClass2.__bases__(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)

提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。

>>> ParentClass1.__bases__(<class 'object'>,)

>>> ParentClass2.__bases__(<class 'object'>,)


继承与抽象(先抽象再继承)

抽象即抽取类似或者说比较像的部分。

抽象分成两个层次:

1.将奥巴马和梅西这俩对象比较像的部分抽取成类;

2.将人,猪,狗这三个类比较像的部分抽取成父类。

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

img_423773e287ba6acc3700545bc8a4ed6f.png


继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

img_c2c37538bf5a2889e186addf545a3e7e.png


继承与重用性

==========================第一部分

例如

猫可以:吃、喝、爬树

狗可以:吃、喝、看家

如果我们要分别为猫和狗创建一个类,那么就需要为 猫 和 狗 实现他们所有的功能,伪代码如下:#猫和狗有大量相同的内容class 猫:

def 吃(self):

# do something    def 喝(self):

# do something    def 爬树(self):

# do somethingclass 狗:

def 吃(self):

# do something    def 喝(self):

# do something    def 看家(self):

#do something==========================第二部分

上述代码不难看出,吃、喝是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次。如果使用 继承 的思想,如下实现:

动物:吃、喝

猫:爬树(猫继承动物的功能)

狗:看家(狗继承动物的功能)

伪代码如下:class 动物:

def 吃(self):

# do something    def 喝(self):

# do something# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类class 猫(动物):

def 爬树(self):

print '喵喵叫'# 在类后面括号中写入另外一个类名,表示当前类继承另外一个类class 狗(动物):

def 看家(self):

print '汪汪叫'==========================第三部分#继承的代码实现class Animal:

def eat(self):

print("%s 吃 " %self.name)

def drink(self):

print ("%s 喝 " %self.name)class Cat(Animal):

def __init__(self, name):

self.name = name

self.breed = '猫'    def climb(self):

print('爬树')class Dog(Animal):

def __init__(self, name):

self.name = name

self.breed='狗'    def look_after_house(self):

print('汪汪叫')# ######### 执行 #########c1 = Cat('小白家的小黑猫')

c1.eat()

c2 = Cat('小黑的小白猫')

c2.drink()

d1 = Dog('胖子家的小瘦狗')

d1.eat()


在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时

我们不可能从头开始写一个类B,这就用到了类的继承的概念。

通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

class Animal:

'''

人和狗都是动物,所以创造一个Animal基类

'''    def __init__(self, name, aggressivity, life_value):

self.name = name  # 人和狗都有自己的昵称;        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;        self.life_value = life_value  # 人和狗都有自己的生命值;    def eat(self):

print('%s is eating'%self.name)class Dog(Animal):

passclass Person(Animal):

passegg = Person('egon',10,1000)

ha2 = Dog('二愣子',50,1000)

egg.eat()

ha2.eat()

提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

派生

当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

class Animal:

'''

人和狗都是动物,所以创造一个Animal基类

'''    def __init__(self, name, aggressivity, life_value):

self.name = name  # 人和狗都有自己的昵称;        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;        self.life_value = life_value  # 人和狗都有自己的生命值;    def eat(self):

print('%s is eating'%self.name)class Dog(Animal):

'''

狗类,继承Animal类

'''    def bite(self, people):

'''

派生:狗有咬人的技能

:param people:

'''        people.life_value -= self.aggressivityclass Person(Animal):

'''

人类,继承Animal

'''    def attack(self, dog):

'''

派生:人有攻击的技能

:param dog:

'''        dog.life_value -= self.aggressivity

egg = Person('egon',10,1000)

ha2 = Dog('二愣子',50,1000)print(ha2.life_value)print(egg.attack(ha2))print(ha2.life_value)

注意:像ha2.life_value之类的属性引用,会先从实例中找life_value然后去类中找,然后再去父类中找...直到最顶级的父类。


在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值.

在python3中,子类执行父类的方法也可以直接用super方法.

class A:

def hahaha(self):

print('A')class B(A):

def hahaha(self):

super().hahaha()

#super(B,self).hahaha()        #A.hahaha(self)        print('B')

a = A()

b = B()

b.hahaha()

super(B,b).hahaha()


class Animal:

'''

人和狗都是动物,所以创造一个Animal基类

'''    def __init__(self, name, aggressivity, life_value):

self.name = name  # 人和狗都有自己的昵称;        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;        self.life_value = life_value  # 人和狗都有自己的生命值;    def eat(self):

print('%s is eating'%self.name)class Dog(Animal):

'''

狗类,继承Animal类

'''    def __init__(self,name,breed,aggressivity,life_value):

super().__init__(name, aggressivity, life_value) #执行父类Animal的init方法        self.breed = breed  #派生出了新的属性    def bite(self, people):

'''

派生出了新的技能:狗有咬人的技能

:param people:

'''        people.life_value -= self.aggressivity

def eat(self):

# Animal.eat(self)        #super().eat()        print('from Dog')class Person(Animal):

'''

人类,继承Animal

'''    def __init__(self,name,aggressivity, life_value,money):

#Animal.__init__(self, name, aggressivity, life_value)        #super(Person, self).__init__(name, aggressivity, life_value)        super().__init__(name,aggressivity, life_value)  #执行父类的init方法        self.money = money  #派生出了新的属性    def attack(self, dog):

'''

派生出了新的技能:人有攻击的技能

:param dog:

'''        dog.life_value -= self.aggressivity

def eat(self):

#super().eat()        Animal.eat(self)

print('from Person')

egg = Person('egon',10,1000,600)

ha2 = Dog('二愣子','哈士奇',10,1000)print(egg.name)print(ha2.name)

egg.eat()


通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师

>>> class Teacher:

...    def __init__(self,name,gender):

...        self.name=name

...        self.gender=gender

...    def teach(self):

...        print('teaching')

...

>>>

>>> class Professor(Teacher):

...    pass...

>>> p1=Professor('egon','male')

>>> p1.teach()

teaching

抽象类与接口类

接口类

继承有两种用途:

一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)

二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能


class Alipay:

'''

支付宝支付

'''    def pay(self,money):

print('支付宝支付了%s元'%money)class Applepay:

'''

apple pay支付

'''    def pay(self,money):

print('apple pay支付了%s元'%money)def pay(payment,money):

'''

支付函数,总体负责支付

对应支付的对象和要支付的金额

'''    payment.pay(money)

p = Alipay()

pay(p,200)


开发中容易出现的问题

class Alipay:

'''

支付宝支付

'''    def pay(self,money):

print('支付宝支付了%s元'%money)class Applepay:

'''

apple pay支付

'''    def pay(self,money):

print('apple pay支付了%s元'%money)class Wechatpay:

def fuqian(self,money):

'''

实现了pay的功能,但是名字不一样

'''        print('微信支付了%s元'%money)def pay(payment,money):

'''

支付函数,总体负责支付

对应支付的对象和要支付的金额

'''    payment.pay(money)

p = Wechatpay()

pay(p,200)  #执行会报错

接口初成:手动报异常:NotImplementedError来解决开发中遇到的问题

class Payment:

def pay(self):

raise NotImplementedErrorclass Wechatpay(Payment):

def fuqian(self,money):

print('微信支付了%s元'%money)

p = Wechatpay()  #这里不报错pay(p,200)      #这里报错了

借用abc模块来实现接口

from abc import ABCMeta,abstractmethodclass Payment(metaclass=ABCMeta):

@abstractmethod

def pay(self,money):

passclass Wechatpay(Payment):

def fuqian(self,money):

print('微信支付了%s元'%money)

p = Wechatpay() #不调就报错了


实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。

继承的第二种含义非常重要。它又叫“接口继承”。

接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。

归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。

依赖倒置原则:

高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该应该依赖细节;细节应该依赖抽象。换言之,要针对接口编程,而不是针对实现编程

在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块:

http://pypi.python.org/pypi/zope.interface

twisted的twisted\internet\interface.py里使用zope.interface

文档https://zopeinterface.readthedocs.io/en/latest/

设计模式:https://github.com/faif/python-patterns

接口提取了一群类共同的函数,可以把接口当做一个函数的集合。

然后让子类去实现接口中的函数。

这么做的意义在于归一化,什么叫归一化,就是只要是基于同一个接口实现的类,那么所有的这些类产生的对象在使用时,从用法上来说都一样。

归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。

比如:我们定义一个动物接口,接口里定义了有跑、吃、呼吸等接口函数,这样老鼠的类去实现了该接口,松鼠的类也去实现了该接口,由二者分别产生一只老鼠和一只松鼠送到你面前,即便是你分别不到底哪只是什么鼠你肯定知道他俩都会跑,都会吃,都能呼吸。

再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样

相关文章:

1080 Graduate Admission

1.因为这道题第一次认真想了高考录取的规则&#xff0c;对学生按照先总分再gE的原则进行从高到低排序&#xff0c;排名允许重复。再按照排名高到低对每个学生的每个志愿进行遍历&#xff0c;当一个学生处理完&#xff0c;再进行下一个。 2.由于最后是要输出学生的原始序号&…

vc++实现无进程无DLL无硬盘文件无启动项的ICMP后门后门程序

客户端 #include <winsock2.h>#include <stdio.h>#include <stdlib.h> #pragma comment(lib,"ws2_32.lib") char SendMsg[256]; /* The IP header */typedef struct iphdr {unsigned int h_len:4; //4位首部长度unsigned int version:4; //IP版本…

arm linux 启动之一:汇编启动到start_kernel

描述arm linux启动的概要过程&#xff0c;以S5PV210(Cortex A8)为例&#xff0c;本文描述第一个阶段。 一、arm linux的引导 uboot在引导arm linux&#xff08;uImage镜像&#xff09;到SDRAM之后&#xff0c;通过bootm命令对uImage镜像的64个字节头进行解释&#xff0c;获取li…

Sql Server 因为触发器问题导致数据库更新报错“在触发器执行过程中引发了错误,批处理已中止”的问题处理...

在维护一个非常旧的项目时&#xff0c;由于该项目版本已经非常老了&#xff0c;而且在客户现场运行的非常稳定&#xff0c;更要命的是本人目前没有找到该项目的代码&#xff0c;为了处理一个新的需求而且还不能修改程序代码&#xff0c;于是决定从数据库入手&#xff0c;毕竟该…

1070 Mooncake

1. 一道典型的贪心题&#xff0c;策略是尽可能地多出售单价高的月饼。 2. 开始有一个用例没有通过&#xff0c;看了参考书&#xff0c;说是质量虽然给的都是整数&#xff0c;但是为了计算不出错&#xff0c;需要声明为浮点型。改了以后果然就通过了&#xff0c;但是个中原理不…

Java数组合并,完成排序,从时间复杂度,和空间复杂度考虑

2019独角兽企业重金招聘Python工程师标准>>> 提供方法&#xff0c;直接调用&#xff0c;支持任意个数组的合并成一个数组&#xff0c;并且完成排序&#xff0c;每个数组元素个数不定。需要提供两个方法&#xff0c;分别做到时间复杂度最低、空间复杂度最低。并说明两…

WPF中Auto与*的差别

Auto 表示自己主动适应显示内容的宽度, 如自己主动适应文本的宽度,文本有多长,控件就显示多长. * 则表示按比例来分配宽度. <ColumnDefinition Width"3*" /> <ColumnDefinition Width"7*" /> 相同,行能够这样定义 <RowDefinition Height&qu…

个人电脑优化方案

2009年4月13日 文件删除--系统默认磁盘清理--批处理清除无用文件--使用优化软件如优化大师 Codeecho off echo 正在清除系统垃圾文件&#xff0c;请稍等 del /f /s /q %systemdrive%\*.tmp del /f /s /q %systemdrive%\…

1037 Magic Coupon

1. 贪心算法题&#xff0c;贪心策略&#xff1a;两组乘子相乘&#xff0c;每个数字至多用一次&#xff0c;希望得到最大的乘积。那么让A组绝对值最大的正数和B组最绝对值最大的正数相乘&#xff0c;次大的和次大的相乘……同样的让A组绝对值最大的负数和B组绝对值最大的负数相乘…

综合布线系统入门及应用(二)

一、工程材料用量估计 1、信息模块及水晶头用量统计 信息插座与工位数量1:1&#xff0c;在增加5%的余量 跳线&#xff1a;一般需要2条&#xff0c;工位信息面板到设备&#xff0c;交换机到配线架&#xff0c;每根条线2个水晶头&#xff0c;预留10%-15%。 2、线槽用量统计 根据办…

如何查看服务器有多少网站--免费工具

一台虚拟主机上到底有多少个网站或者说同一ip下有多少个域名和网站&#xff1f;这是站长们都很关心的&#xff0c;因为这样可以知道你的站到底和谁是邻居&#xff0c;有时候如果你和百度黑名单上的垃圾站在同一空间下&#xff0c;你也会受到牵连。 那么怎 ...中间左侧广告 一台…

二分法在算法题中的4种常见应用(cont.)

目录 1.查找单调序列中是否存在满足某条件的元素 2.寻找序列中第一个(最后一个)满足某条件的元素的位置 3.给定一个定义在[L,R]上的单调函数f(x)&#xff0c;求方程f(x)0的根 4.快速幂的递归和迭代求法 1.查找单调序列中是否存在满足某条件的元素 //二分区间为左闭右闭的[l…

Minimum Path Sum

Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path. Note: You can only move either down or right at any point in time. 格子取数问题&#xff0c;另dp[i][j]表示走…

Thorntail 2.2.0提供从WildFly Swarm自动迁移的特性

自6月底宣布把WildFly Swarm2018.5.0改名为Thorntail2.0.0以来&#xff0c;Red Hat在8月中旬以后的三个周里发布了Thorntail 2.1.0版本和2.2.0版本。除了许多Bug修复外&#xff0c;尤其是和MicroProfile相关的&#xff0c;新特性还包括&#xff1a;\\符合MicroProfile 1.3\通过…

Depth Bias

在dx中的depth bias要以如下形式调用 inline DWORD F2DW( float f ) { return *((DWORD*)&f); } m_pD3DDevice->SetRenderState(D3DRS_SLOPESCALEDEPTHBIAS, F2DW(1)); m_pD3DDevice->SetRenderState(D3DRS_DEPTHBIAS, F2DW(0.001)); 总之&#xff0c;奇怪的api。转载…

(C++)用upper_bound函数取代自己写的二分查找

int a[maxn];int j upper_bound(ai1,an,(long long)a[i]*p)-a; 以上代码的作用是 在a[i1]~a[n-1]找到第一个大于a[i]*p的数&#xff0c;将其下标返回给j 注意&#xff1a; 1.函数是左闭右开的 2.末尾要减去数组的坐标a 3.不加long long强制类型转换可能丢分

gsoap使用总结

WebService、soap、gsoap基本概念 WebService服务基本概念&#xff1a;就是一个应用程序&#xff0c;它向外界暴露出一个可以通过web进行调用的API&#xff0c;是分布式的服务组件。本质上就是要以标准的形式实现企业内外各个不同服务系统之间的互调和集成。 soap概念&#xff…

SQL时间相关 - SQL日期,时间比较

SQL Server 中时间比较 例子: select count(*) from table where DATEDIFF ([second], 2004-09-18 00:00:18, 2004-09-18 00:00:19) > 0 说明 select DATEDIFF(day, time1 , time2) 对应示例语句如下 select DATEDIFF(day, 2010-07-23 0:41:18, 2010-07-23 23:41:18) …

SQL Server 2008 的CDC功能

CDC(Change Data Capture)通过对事务日志的异步读取&#xff0c;记录DML操作的发生时间、类型和实际影响的数据变化&#xff0c;然后将这些数据记录到启用CDC时自动创建的表中。通过cdc相关的存储过程&#xff0c;可以获取详细的数据变化情况。由于数据变化是异步读取的&#x…

1010 Radix

目录 总结 解题过程 总结 1. 短小精悍的一道二分算法题&#xff0c;总体思路是&#xff0c;将字符串1(如果tag不是1将两个字符串调换一下即可)转化为10进制&#xff0c;再用二分法看能否找到另一个进制使得两个字符串的10进制数相等。 2. 本题的三个函数关系是binarySearch…

喜闻乐见的const int *p、int* const p、const int* const p

不废话直接代码示例&#xff1a; 1 void f(const int *p) {2 3 int b 10;4 5 *p 10; // error6 7 p &b; // fine8 9 } 10 11 void f(int* const p) { 12 13 int b 10; 14 15 *p 10; // fine 16 17 p &b; // error 18 19 } 20 21 v…

Microsoft Visual Studio 2012 添加实体数据模型

Microsoft Visual Studio 2012 添加实体数据模型 1、创建一个web项目 2、添加ADO实体数据模型&#xff0c;如下图&#xff1a; 3、选择 从数据库生成&#xff0c;然后下一步 4、新建连接&#xff0c;如下图&#xff1a; 5、填写服务器名等&#xff0c;如下图&#xff1a; 6、选…

5.1软件升级的小阳春

现在正在去白山的车上&#xff0c;刚睡醒。习惯性的拿出手机上网&#xff0c;UCWEB提醒有最新版本升级&#xff0c;使用尚邮接收邮件的时候同样提醒有信版本升级。 公司产品9.0也正式完成&#xff0c;昨天整个小组的同事开始在领地咖啡馆&#xff0c;进行新需求的确认。 4月末5…

1030 完美数列(二分解法)

1. 将整型序列从小到大排序后&#xff0c;这道题的本质是&#xff0c;对于每一个元素i&#xff0c;找出最后一个满足p*A[i]>A[j]的元素j&#xff0c;可以转化为找出第一个不满足p*A[i]>A[j]也即p*A[i]<A[j]的元素j。再用j-1。 2.LL product (LL)p*A[i];这里后面两个…

javascript变量声明 及作用域

javascript变量声明提升(hoisting) http://openwares.net/js/javascript_declaration_hoisting.html 可能要FQ一下 javascript的变量声明具有hoisting机制&#xff0c;JavaScript引擎在执行的时候&#xff0c;会把所有变量的声明都提升到当前作用域的最前面。 先看一段代码 123…

【转载】全面理解javascript的caller,callee,call,apply概念(修改版)

今天写PPlayer&#xff0c;发现有段代码引起了我的兴趣&#xff1a; var Class { create: function() { return function() { this.initialize.apply(this, arguments); } } } 这是高手写的&#xff0c;实现了创建一个类&#xff08;其实就是对象&#xff0c;函数对象&#xf…

springMVC自定义全局异常

SpringMVC通过HandlerExceptionResolver处理程序异常&#xff0c;包括Handler映射&#xff0c;数据绑定以及目标方法执行时所发生的异常。 SpringMVC中默认是没有加装载HandlerExceptionResolver&#xff0c;我们需要在SpringMVC.xml中配置 <mvc:annotation-driven /> 1、…

1030 完美数列(two pointers解法)

1. 这道题出现在二分法&#xff0c;但是特殊之处在于&#xff0c;双指针是嵌套的&#xff0c;程序看上去有些像暴力枚举&#xff0c;但其实是利用了&#xff0c;如果i<j&#xff0c;a[i]*p>a[j]&#xff0c;那么一定有k在[i,j]范围内&#xff0c;a[i]*p>a[k]&#xff…

alsa声卡切换

环境 ubuntu12.04 因为桌面版的默认装了&#xff0c;而且调声音也很方便&#xff0c;这里说一下server版下的配置&#xff0c;毕竟做开发经常还是用server版的 1.安装 apt-get install alsa-base 它会把alsa-utils也一块装了&#xff0c;这是个工具包&#xff0c;如果没装的话 …

asp.net获取网站路径

网站在服务器磁盘上的物理路径: HttpRuntime.AppDomainAppPath 虚拟程序路径: HttpRuntime.AppDomainAppVirtualPath 任何于Request/HttpContext.Current等相关的方法, 都只能在有请求上下文或者页面时使用. 即在无请求上下文时,HttpContext.Current为null. 而上面提到的方法一…