python学习之面向对象(二)

6.2 类的空间角度研究类

6.2.1 添加对象属性

【总结】对象的属性不仅可以在__init__里面添加,还可以在类的其他方法或者类的外面添加。

class A:
    address = '召唤师峡谷'
    def __init__(self, name):
        self.name = name
    def func(self,type):
        self.type = type

# 类的外部可以添加
obj = A('盖伦')
obj.age = 20
print(obj.__dict__)
# 在类的内部可以添加
obj.func('战士')
print(obj.__dict__)

6.2.2 添加类的属性

【总结】类的属性添加方式也是比较灵活的,不仅可以在类内部添加,还可以在类的外部添加。

class A:
    aaa = 'aaa'
    def __init__(self,name):
        self.name = name
    def func(self):
        A.ccc = 'ccc'

# 在类的外部可以添加
A.bbb = 'bbb'
print(A.__dict__)

# 在类的内部可以添加
A.func(1)
print(A.__dict__  )

6.2.3 对象如何查找类的属性

对象在实体化的空间内,有一个指向类地址空间的指针

对象查找顺序:先从对象空间找--> 类的空间找--> 父类空间找.....

类名查找顺序:先从本类空间找--> 父类空间.....

6.3 类与类的关系

python中类与类之间的关系:

依赖关系

组合关系

继承关系

6.3.1 依赖关系

将一个类名或者类的对象传给另一个类的方法中

模拟一个场景,淘金者要进入OnePeace,淘金者要念口号让大门打开,进入进入OnePeace后要念口号,然后门关上。

class Fossicker:
    def __init__(self,name):
        self.name = name
    def ask_open(self,se):
        print(f'{self.name}大喊:“芝麻开门”')
        re.open_door()     #调用SecretPath里的open_door方法
        print(f"{self.name}进入了{se.name}里面")

    def ask_close(self,se):
        print(f'{self.name}又说:“芝麻关门”')
        re.closs_door()     #调用SecretPath里的closs_door方法
        print(f"{self.name}把{se.name}的门关了")

class SecretPath:
    def __init__(self,name):
        self.name = name
    def open_door(self):
        print('门开了')
    def closs_door(self): 
        print('门关了')

e1 = Fossicker('阿加')
se1 = SecretPath('OnePeace')
e1.ask_open(se1)    
e1.ask_close(se1)

​ 上述场景,Fossicker的这个类中用到了SecretPath的对象,而SecretPath的对象并没有用到其他类的任何方法或是属性,我们可以说Fossicker类依赖SecretPath类,二者构成依赖关系。

6.3.2 组合关系

将一个类的对象封装到另一个类的对象的属性中

情景模拟:相亲

class Boy:
    def __init__(self,name):
        self.name = name

    def meet(self,gril):
        if gril.age <= 25:
            self.grilfriend = gril
            print(f'{self.name}跟{self.grilfriend.name}很是合得来')
            gril.boyfriend = self
        else:
            self.grilfriend = None
            print('不合适,没成')

    def have_dinner(self):
        if self.grilfriend:
            print(f"{self.name}请{self.grilfriend.name}吃大餐")
        else:
            print('算了回家吃泡面吧')

class Gril:
    def __init__(self,name,age):
        self.name = name
        self.age = age
    def shopping(self):
        print(f"{self.name}和{self.boyfriend.name}一起去购物")

gailun = Boy('盖伦')
nvjing = Gril('凯特琳',24)
gailun.meet(nvjing)
gailun.have_dinner()
nvjing.shopping()

【总结】不管是依赖关系还是组合关系,在类与类之间进行相互调用时,我们希望的是,某些情况下不要把所有的因素写死,而是获取到其他对象的整个空间内的信息,所以在传参时可以,应注意这一点

6.3.3 练习

场景模拟:模拟英雄联盟的游戏人物

  1. 创建一个 Game_role的类.
  2. 构造方法中给对象封装name,ad(攻击力),hp(血量).三个属性.
  3. 创建一个attack方法,此方法是实例化两个对象,互相攻击的功能:
    例: 实例化一个对象 盖伦,ad为10, hp为100
    实例化另个一个对象 剑豪 ad为20, hp为80
    盖伦通过attack方法攻击剑豪,此方法要完成 '谁攻击谁,谁掉了多少血, 还剩多少血'的提示功能
class GameRole:

    def __init__(self, name, ad, hp):
        self.name = name
        self.ad = ad
        self.hp = hp

    def attack(self,p1):
        p1.hp = p1.hp - self.ad
        print(f'{self.name}攻击{p1.name},{p1.name}掉了{self.ad}血,还剩{p1.hp}血')

gailun = GameRole('盖伦',10,100)
jianhao = GameRole('剑豪',20,80)
gailun.attack(jianhao)    #依赖关系的应用

升级:创建一个武器类,让对象借助武器进行攻击

class GameRole:

    def __init__(self, name, ad, hp):

        self.name = name
        self.ad = ad
        self.hp = hp
        
class Weapon:

    def __init__(self, name, ad):
        self.name = name
        self.ad = ad

    def weapon_attack(self, p1, p2):
        # print(self)
        p2.hp = p2.hp - self.ad
        print(f'{p1.name}利用{self.name}给了{p2.name}一下,{p2.name}还剩{p2.hp}血')

gailun = GameRole('盖伦', 10, 100)
jianhao = GameRole('剑豪', 20, 80)
great_sword = Weapon('黑切', 30)
spear = Weapon('长枪', 40)
great_sword.weapon_attack(gailun, jianhao)  #此时进行攻击动作的执行者是great_sword

​ 此时进行攻击动作的执行者是great_sword,而我们希望的是GameRole的对象使用武器进行攻击,下面进行优化

class GameRole:

    def __init__(self, name, ad, hp):

        self.name = name
        self.ad = ad
        self.hp = hp
    def equip_weapon(self,wea):  #
        self.weapon = wea

class Weapon:

    def __init__(self, name, ad):
        self.name = name
        self.ad = ad

    def weapon_attack(self, p1, p2):
        p2.hp = p2.hp - self.ad
        print(f'{p1.name}利用{self.name}给了{p2.name}一下,{p2.name}还剩{p2.hp}血')

gailun = GameRole('盖伦', 10, 100)
jianhao = GameRole('剑豪', 20, 80)
great_sword = Weapon('黑切', 30)
spear = Weapon('长枪', 40)
gailun.equip_weapon(great_sword)  #依赖关系
gailun.weapon.weapon_attack(gailun,jianhao)

6.4 继承

面向对象的三大特性:封装,继承,多态

6.4.1 继承的初识

B类继承A类,B就叫做A的子类,派生类,子类可以调用父类的属性和方法

class Person:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Dog:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Cat:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

​ 看上面的代码,建立了三个类,出现了大量的重复代码,利用继承对这些代码进行整合,如下:

# 继承
class Animal:
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex

class Person(Animal):
    pass

class Dog(Animal):
    pass

class Cat(Animal):
    pass

6.4.2 继承的特性

继承的优点:

  • 节省代码
  • 增强代码的耦合性
  • 代码更加规范
  • 重构父类的属性或方法

继承的缺点:

  • 降低了代码的灵活性
  • 过多的耦合性,使得在父类的常量、变量和方法被修改时,需要考虑子类的修改,甚至导致大段的代码需要重构

6.4.3 单继承

只有一个父类(基类,超类)

子类以及对象可以调用父类的属性方法
class Animal:
    live = '有生命的'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('需要进食')

class Person(Animal):
    pass

# 从类名执行父类的属性

print(Person.__dict__)
print(Person.live)
对象执行父类的一切

实体化对象首先执行__init__,自己没有就去父类找,根据__init__函数传入相应的参数

class Animal:
    live = '有生命的'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('需要进食')

class Person(Animal):
    pass

# 从对象执行父类的一切

p1 = Person('盖伦',24,'男')
print(p1.__dict__)
p1.eat()

【注意】子类以及子类的对象只能调用父类的属性和方法,不能进行操作(增删改)。

子类可以重写父类的方法

就是在子类的空间内,创建一个新的与父类函数重名的函数

class Animal:
    live = '有生命的'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('需要进食')

class Person(Animal):
    def eat(self):
        print('人要吃饭')

p1 = Person('盖伦',24,'男')
p1.eat()

【注意】如遇子类属性与父类重名的话,先从本身进行执行,父类不执行

如何执行子类方法有执行父类方法

一种是无绑定的调用父类的函数

一种是使用super自动绑顶

class Animal:
    live = '有生命的'
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def eat(self):
        print('需要进食')

class Person(Animal):
# 方法一:
    def __init__(self,name,age,sex,type):
        Animal.__init__(self,name,age,sex)  #调用父类的方法,需要手动传入self
        self.type = type
    def eat(self):
        print('人要吃饭')

p1 = Person('盖伦',24,'男','战士')
print(p1.__dict__)

# 方法二:super方法
class Person(Animal):

    def __init__(self,name,age,sex,type):
        # super(Person, self).__init__(name,age,sex)  #super第一个括号里的内容可以省略,
        super().__init__(name,age,sex)
        self.type = type
    def eat(self):
        print('人要吃饭')

p1 = Person('盖伦',24,'男','战士')
print(p1.__dict__)

# super可以继承父类的方法
class Person(Animal):

    def __init__(self,name,age,sex,type):
        # super(Person, self).__init__(name,age,sex)
        super().__init__(name,age,sex)  #super第一个括号的内容可以省略
        self.type = type
    def eat(self):
        print('人要吃饭')
        super().eat()

p1 = Person('盖伦',24,'男','战士')
p1.eat()


6.4.4 多继承

一个子类继承了多个父类,难点在于继承的顺序。

面向对象:python2.2之前,都是经典类;python2.2-2.7之间,存在两种类型,一种是经典类另一种是新式类;python3只有新式类。

经典类:基类不继承object,查询规则满足深度优先原则
新式类:基类继承object,查询规则满足mro算法,不是单纯的广度优先

mro算法

表头:列表的第一个元素

表尾:表头以外的所有元素

解题过程:

1.先从上到下,列出每个类的线性表达式.

2.列出的线性表达式中如果有多个表达式的话,需要进行化简.

3.最终的子类也使用线性表达式表示,由于有多个父类,也需要进行化简.

线性表达式有两个部分组成:第一个类名称为头部,剩余的部分称为尾部.

先从上到下求出每个类的线性表达式,期间需要化简

G = [GO]
F = F + [GO] = [FGO]
D = D + [GO] = [DGO]
B = B + [FGO] = [BFGO]
C = C + [E] = [CE]
再求最终的类的线性表达式,需要化简
A = [BFGO] + [CE] + [DGO]
AB = [FGO] + [CE] + [DGO]
ABF = [GO] + [CE] + [DGO]       
 由于第一个表达式的G是第三个表达式的尾部,所以不能抽取,则从第二个表达式的头部开始抽取C
ABFC = [GO] + [E] + [DGO]

再次从第一个表达式的头部开始抽取,依然不能抽取,则从第二个表达式抽取
ABFCE = [GO] + [DGO]

第一个表达式的头部依然不能抽取,从第二个表达式的头部开始抽取
ABFCED = [GO] + [GO]

剩下的部分相同了,就可以直接写
ABFCEDGO      # 最终的mro解析顺序

【注意】在实际项目中,查看mro顺序可以使用 类名.mro来获取类的查找顺序

6.5 封装和多态

6.5.1 封装

给对象封装属性或方法的过程

通过对象或者self获取被封装的属性方法

6.5.2 多态

python默认支持多态

python 设置变量时,不需要设置数据类型

6.5.3 鸭子类型

俗话你看起来像鸭子,那么你就是鸭子

两个相互独立的A,B两个类,但内部功能相似,python一般会将类似于A,B里面的相似功能的命名设置成相同。

举例:A,B虽然没有联系,默认使用一种规范,使用起来更方便。像是列表里的index与字符串的index,两个index没有关系,但是使用的时候比较方便。

6.6 super

super(类名,self).function() 执行本类的父类的方法

类名:多继承中,严格按照self的从属类的mro顺序执行,即执行本类名的父类里的方法;单继承中,按照父类的方法执行

class A:
    def func(self):
        print('in A')

class B(A):
    def func(self):
        print('in B')

class C(A):
    def func(self):
        print('in C')

class D(B,C):
    def func(self):
        print('in D')
        super(D,self).func()

t = D()
t.func()  
#输出
in D
in B
# 改变一下class D的super参数

class D(B,C):
    def func(self):
        print('in D')
        super(B,self).func()

t = D()
t.func()
输出
in D
in C  #按照mro顺序执行下一父类的方法

6.7 类的约束

约束是对类的约束。

场景模拟:设计一个战士和刺客的英雄类型

class Zhanshi:
    def type_role(self,name):
        self.name = name
        print(f"{self.name}是一个战士")


class Cike:
    def type_role(self, name):
        self.name = name
        print(f"{self.name}是一个刺客")

gailun = Zhanshi()
# g.type_role('战士')
jianhao = Cike()
# j.type_role('刺客')

# 设计一个规范化的接口
def type_role(obj,role):  #归一化设计
    obj.type_role(role)
type_role(gailun,'盖伦')
type_role(jianhao,'剑豪')

当增加一个法师的类型

# 在上述的基础上增加新的英雄属性
class Fashi:
    def type_role(self,name):
        self.name = name
        print(f"{self.name}是一个法师")

moganna = Fashi()
type_role(moganna,'莫甘娜')  #调用规范化接口使用

如果我要按照归一化设计的规范继续增加新的英雄类型时,那么怎么让程序设计者知道要使用这一规范呢?

目前有两种方式:1.在父类建立一种约束;2.模拟抽象类的概念,建立一种强制约束

第一种方式

在父类建议约束,主动抛出错误,非强制性

class Hero:
    def type_role(self,name):  #定义一种规范,子类要定义type_role方法
        raise Exception('请定义type_role方法')

class Zhanshi(Hero):
    def type_role(self,name):
        self.name = name
        print(f"{self.name}是一个战士")

class Cike(Hero):
    def type_role(self, name):
        self.name = name
        print(f"{self.name}是一个刺客")

class Fashi(Hero):
    def type_ro(self,name):   #没有定义type_role而是定义了一个type_ro
        self.name = name
        print(f"{self.name}是一个法师")
    pass

def type_role(obj,role):  #归一化设计
    obj.type_role(role)

gailun = Zhanshi()
jianhao = Cike()
moganna = Fashi()

type_role(moganna,'莫甘娜')  #调用接口函数时,主动报错:raise Exception('请定义type_role方法')

第二种方式

类似于抽象类的概念,定义一种强制约束,你只要是我的派生类,那么这个方法必须要有。

from abc import ABCMeta,abstractmethod

class 父类名(metaclass=ABCMeta):

​ @abstractmethod

​ def 函数名

from abc import ABCMeta,abstractmethod

class Hero(metaclass=ABCMeta):
    @abstractmethod
    def type_role(self,name):  #定义一种规范,子类要定义type_role方法
        pass

class Zhanshi(Hero):
    def type_role(self,name):
        self.name = name
        print(f"{self.name}是一个战士")

class Cike(Hero):
    def type_role(self, name):
        self.name = name
        print(f"{self.name}是一个刺客")

class Fashi(Hero):
    def type_ro(self,name):
        self.name = name
        print(f"{self.name}是一个法师")
    pass

gailun = Zhanshi()
jianhao = Cike()
moganna = Fashi()

moganna.type_ro('莫甘娜')
# 函数执行前,直接报错TypeError: Can't instantiate abstract class Fashi with abstract methods type_role
posted @ 2019-07-10 15:57  Aries-X  阅读(215)  评论(0编辑  收藏  举报