面向对象的继承

不用继承创建对象

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

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

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

使用继承的方式

class Aniaml(object):
    def __init__(self,name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(f"{self.name}吃东西..")

class Dog(Aniaml):
    pass

xiaotianquan = Dog("哮天犬",5)
xiaotianquan.eat()

继承的概念子类 拥有 父类 的所有 方法属性

img

继承的有点也是显而易见的:

  1. 增加了类的耦合性(耦合性不宜多,宜精)。

  2. 减少了重复代码。

  3. 使得代码更加规范化,合理化。

继承的分类

上面的那个例子,涉及到的专业术语:

  • Dog 类是 Animal 类的子类Animal 类是 Dog 类的父类Dog 类从 Animal继承
  • Dog 类是 Animal 类的派生类Animal 类是 Dog 类的基类Dog 类从 Animal派生

继承:可以分单继承,多继承

这里需要补充一下python中类的种类(继承需要):

在python2x版本中存在两种类.:

  • ⼀个叫经典类. 在 Python 2 中,经典类是指没有显式继承自 object 的类。它们使用旧的类定义方式。
  • ⼀个叫新式类. 新式类是指显式继承自 object 或其他新式类的类。新式类在 Python 2.2 中引入,并在 Python 3 中成为默认。

单继承

对象执行父类方法

class Aniaml(object):
    type_name = '动物类'

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

    def eat(self):
        print('吃',self)

class Person(Aniaml):
    pass

class Cat(Aniaml):
    pass

class Dog(Aniaml):
    pass

print(Person.type_name)
Person.eat('东西')
print(Person.type_name)

p1 = Person('aaron','男',18)
print(p1.__dict__)
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()

执行顺序

class Aniaml(object):
    def __init__(self,name, age):
        self.name = name
        self.age = age

    def eat(self):
        print(f"{self.name}吃东西..")

class person(Aniaml):
    def eat(self):
        print('%s 用筷子吃饭' % self.name)

class Dog(Aniaml):
    pass

class Cat(Aniaml):
    pass


person1 = person('张三',18)
person1.eat()

同时执行类以及父类方法

方法一:如果想执行父类的eat()方法,这个方法并且子类中夜用,那么就在子类的方法中写上:父类.eat(对象,其他参数)

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex

    def eat(self):
        print('吃东西')

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

    def eat(self):
        Aniaml.eat(self)
        print('%s 吃饭'%self.name)
class Cat(Aniaml):
    pass

class Dog(Aniaml):
    pass

p1 = Person('aaron','男',18,'想吃东西')
p1.eat()

方法二:利用super,super().func(参数)

class Aniaml(object):
    type_name = '动物类'
    def __init__(self,name,sex,age):
            self.name = name
            self.age = age
            self.sex = sex

    def eat(self):
        print('吃东西')

class Person(Aniaml):
    def __init__(self,name,sex,age,mind):
        # super(Person,self).__init__(name,sex,age)
        super().__init__(name,sex,age)
        self.mind = mind

    def eat(self):
        super().eat()
        print('%s 吃饭'%self.name)
class Cat(Aniaml):
    pass

class Dog(Aniaml):
    pass

p1 = Person('aaron','男',18,'想吃东西')
p1.eat()

单继承练习

class Base:
    def __init__(self,num):
        self.num = num
    def func1(self):
        print(self.num)

class Foo(Base):
    pass

obj = Foo(123)
obj.func1()
# 运⾏的是Base中的func1
class Base:
    def __init__(self,num):
        self.num = num
    def func1(self):
        print(self.num)

class Foo(Base):
    def func1(self):
        print("Foo.func1",self.num)

obj = Foo(123)
obj.func1()
# 运⾏的是Foo中的func1
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
        self.func2()
    def func2(self):
        print("Base.func2")
class Foo(Base):
    def func2(self):
        print("Foo.func2")

obj = Foo(123)
obj.func1()
# func1是Base中的 func2是⼦类中的
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
        self.func2()
    def func2(self):
        print(111, self.num)
class Foo(Base):
    def func2(self):
        print(222, self.num)

lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
    obj.func2()
class Base:
    def __init__(self, num):
        self.num = num
    def func1(self):
        print(self.num)
        self.func2()
    def func2(self):
        print(111, self.num)
class Foo(Base):
    def func2(self):
        print(222, self.num)

lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
    obj.func1()

方法的重写

  • 如果在开发中,父类的方法实现子类的方法实现完全不同
  • 就可以使用 覆盖 的方式,在子类中 重新编写 父类的方法实现

具体的实现方式,就相当于在 子类中 定义了一个 和父类同名的方法并且实现

重写之后,在运行时,只会调用 子类中重写的方法,而不再会调用 父类封装的方法

对父类方法进行 扩展

  • 如果在开发中,子类的方法实现中包含父类的方法实现
    • 父类原本封装的方法实现子类方法的一部分
  • 就可以使用扩展的方式
    1. 在子类中 重写 父类的方法
    2. 在需要的位置使用 super().父类方法 来调用父类方法的执行
    3. 代码其他的位置针对子类的需求,编写 子类特有的代码实现

关于 super

  • Pythonsuper 是一个 特殊的类
  • super() 就是使用 super 类创建出来的对象
  • 最常 使用的场景就是在 重写父类方法时,调用 在父类中封装的方法实现

提示

  • 在开发时,父类名super() 两种方式不要混用
  • 如果使用 当前子类名 调用方法,会形成递归调用,出现死循环

父类的 私有属性 和 私有方法

  1. 子类对象 不能 在自己的方法内部,直接 访问 父类的 私有属性私有方法
  2. 子类对象 可以通过 父类公有方法 间接 访问到 私有属性私有方法
  • 私有属性、方法 是对象的隐私,不对外公开,外界 以及 子类 都不能直接访问
  • 私有属性、方法 通常用于做一些内部的事情

img

  • B 的对象不能直接访问 __num2 属性
  • B 的对象不能在 demo 方法内访问 __num2 属性
  • B 的对象可以在 demo 方法内,调用父类的 test 方法
  • 父类的 test 方法内部,能够访问 __num2 属性和 __test 方法
class Animal:
    def __init__(self,name):
        self.__name = name

    def __eat(self):
        print(self.__name + "Eating...")
    def eat2(self):
        self.__eat()

class Dog(Animal):
    pass

a = Dog('哮天犬')
print(a.name)
a.__eat()
a.eat2()

# AttributeError: 'Dog' object has no attribute 'name'

多继承

概念

  • 子类 可以拥有 多个父类,并且具有 所有父类属性方法
  • 例如:孩子 会继承自己 父亲母亲特性

img

语法

class 子类名(父类名1, 父类名2...)
    pass

问题的提出

  • 如果 不同的父类 中存在 同名的方法子类对象 在调用方法时,会调用 哪一个父类中的方法呢?

提示:开发时,应该尽量避免这种容易产生混淆的情况! —— 如果 父类之间 存在 同名的属性或者方法,应该 尽量避免 使用多继承

img

class shengxian:    # 神仙
    def fei(self):
        print("神仙会飞")

    def eat(self):
        print("吃人参果")

class monkey:   # 猴
    def eat(self):
        print("吃桃子")

class songwukong(shengxian,monkey): #孙悟空既是神仙也是猴
    def __init__(self):
        self.name = "孙悟空"

    def eat(self):
        print("我是齐天大圣,我不用吃东西")

swk = songwukong()
swk.eat()

经典类的多继承

class A:
    pass
class B(A):
    pass
class C(A):
    pass
class D(B, C):
    pass
class E:
    pass
class F(D, E):
    pass
class G(F, D):
    pass
class H:
    pass
class Foo(H, G):
    pass

画图

img

在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个.

类的MRO(method resolution order): Foo-> H -> G -> F -> E -> D -> B -> A -> C.

新式类的多继承

mro序列

mro是一个有序列表,在类被创建时就计算出来。

通用计算公式为:

mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )(其中Child继承自Base1, Base2)

如果继承至一个基类:class B(A) 这时B的mro序列为

mro( B ) = mro( B(A) )
= [B] + merge( mro(A) + [A] )
= [B] + merge( [A] + [A] )
= [B,A]

如果继承至多个基类:class B(A1, A2, A3 …) 这时B的mro序列

mro(B) = mro( B(A1, A2, A3 …) )
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
= ...

计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。

表头和表尾

表头:列表的第一个元素

表尾:列表中表头以外的元素集合(可以为空)

示例:列表:[A, B, C] 表头是A,表尾是B和C

列表之间的+操作

[A] + [B] = [A, B]

merge操作示例:

如计算merge( [E,O], [C,E,F,O], [C] ) 有三个列表 : ① ② ③

1 merge不为空,取出第一个列表列表①的表头E,进行判断                              
各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
2 取出列表②的表头C,进行判断
C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
3 进行下一次新的merge操作 ......
--------------------- 

img

计算mro(A)方式:

mro(A) = mro( A(B,C) )

原式= [A] + merge( mro(B),mro(C),[B,C] )

mro(B) = mro( B(D,E) )
        = [B] + merge( mro(D), mro(E), [D,E] )  # 多继承
        = [B] + merge( [D,O] , [E,O] , [D,E] )  # 单继承mro(D(O))=[D,O]
        = [B,D] + merge( [O] , [E,O]  ,  [E] )  # 拿出并删除D
        = [B,D,E] + merge([O] ,  [O])
        = [B,D,E,O]

mro(C) = mro( C(E,F) )
        = [C] + merge( mro(E), mro(F), [E,F] )
        = [C] + merge( [E,O] , [F,O] , [E,F] )
        = [C,E] + merge( [O] , [F,O]  ,  [F] )  # 跳过O,拿出并删除
        = [C,E,F] + merge([O] ,  [O])
        = [C,E,F,O]

原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
    = [A,B] + merge( [D,E,O], [C,E,F,O],   [C])
    = [A,B,D] + merge( [E,O], [C,E,F,O],   [C])  # 跳过E
    = [A,B,D,C] + merge([E,O],  [E,F,O])
    = [A,B,D,C,E] + merge([O],    [F,O])  # 跳过O
    = [A,B,D,C,E,F] + merge([O],    [O])
    = [A,B,D,C,E,F,O]

面向对象的多态

多态: 不同的 子类对象 调用相同的 父类方法,产生不同的执行结果

  • 多态 可以 增加代码的灵活度
  • 继承重写父类方法 为前提
  • 是调用方法的技巧,不会影响到类的内部设计

img

class human(object):
    def work(self):
        return "喝杯咖啡,开始工作"

class ps_job(human):
    def work(self):
        return "开始美工"

class IT_job(human):
    def work(self):
        return "开始敲代码"


def job(person):    # 多态函数
    print(person.work())

# 创建不同类型的对象
ps = ps_job()
it = IT_job()

# 调用同一个函数,表现出不同的行为
job(ps)
job(it)

案例,哮天犬

需求

  1. Dog 类中封装方法game

  2. 普通狗只是简单的玩耍

  3. 定义XiaoTianDog 继承自Dog ,并且重写game 方法

    • 哮天犬需要在天上玩耍
  4. 定义Person 类,并且封装一个和狗玩 的方法

    • 在方法内部,直接让 狗对象 调用 game 方法

img

案例小结

  • Person 类中只需要让狗对象调用game 方法,而不关心具体是什么狗
  • game 方法是在 Dog 父类中定义的

  • 在程序执行时,传入不同的 狗对象 实参,就会产生不同的执行效果

多态 更容易编写出出通用的代码,做出通用的编程,以适应需求的不断变化!

class Dog(object):

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

    def game(self):
        print("%s 蹦蹦跳跳的玩耍..." % self.name)


class XiaoTianDog(Dog):

    def game(self):
        print("%s 飞到天上去玩耍..." % self.name)


class Person(object):

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

    def game_with_dog(self, dog):

        print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name))

        # 让狗玩耍
        dog.game()


# 1. 创建一个狗对象
wangcai = Dog("旺财")
xiaotianquan = XiaoTianDog("飞天旺财")

# 2. 创建一个小明对象
xiaoming = Person("小明")

# 3. 让小明调用和狗玩的方法
xiaoming.game_with_dog(wangcai)
xiaoming.game_with_dog(xiaotianquan)

鸭子类型

python中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。

这句谚语是关于鸭子类型(Duck Typing)的一种表达方式。鸭子类型是一种动态类型的概念,它强调一个对象的特征和行为,而不是其具体的类型或继承关系。

在 Python 中,鸭子类型的概念可以简单地表述为:如果一个对象具有像鸭子一样的特征和行为,那么我们可以认为它是一个鸭子。这意味着我们关注对象是否具备特定的方法和属性,而不关心对象的具体类型。

这种思想在 Python 中经常被使用,特别是在函数参数传递和对象的使用上。如果一个函数接受一个参数,并假设该参数具有某些特定的方法或属性,那么只要传递的对象满足这些要求,它就可以正常工作,无论对象的具体类型是什么。

下面是一个简单的示例来说明鸭子类型的概念:

class Duck:
    def quack(self):
        print("嘎嘎叫!")

    def fly(self):
        print("扑哧扑哧的飞!")


class Person:
    def quack(self):
        print("我喜欢跟鸭子一样嘎嘎叫")

    def fly(self):
        print("我也喜欢跟鸭子一样飞")


def make_it_quack_and_fly(obj):
    obj.quack()
    obj.fly()


duck = Duck()
person = Person()

make_it_quack_and_fly(duck)
make_it_quack_and_fly(person)。

在上述示例中,我们定义了一个 Duck 类和一个 Person 类,它们都具有 quackfly 方法。然后,我们定义了一个函数 make_it_quack_and_fly,它接受一个参数 obj,并调用 objquackfly 方法。

我们可以看到,无论是 Duck 对象还是 Person 对象,只要它们具有 quackfly 方法,都可以作为参数传递给 make_it_quack_and_fly 函数,并成功执行相应的方法。

这正是鸭子类型的思想:如果一个对象具有像鸭子一样的特征和行为(即具有 quackfly 方法),那么我们可以将其视为鸭子,而无需关心对象的具体类型。

类的约束

写一个支付功能

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

a = Alipay()
a.pay(100)

b = QQpay()
b.pay(200)

统一一下付款方式

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

def pay(obj,money):
    obj.pay(money)


a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

如果后期添加微信支付,但是没有统一标准,换个程序员就可能写成这样

class QQpay:
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay:
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay:
    def fuqian(self,money):
        print('使用微信支付%s元' % money)

def pay(obj,money):
    print("===============")
    obj.pay(money)


a = Alipay()
b = QQpay()

pay(a,100)
pay(b,200)

c = Wechatpay()
c.fuqian(300)

所以此时我们要用到对类的约束,对类的约束有两种:

  1. 提取父类. 然后在父类中定义好⽅法. 在这个方法中什么都不⽤⼲. 就抛⼀个异常就可以了. 这样所有的⼦类都必须重写这个⽅法. 否则. 访问的时候就会报错.

  2. 使⽤元类来描述方类. 在元类中给出⼀个抽象方法. 这样子类就不得不给出抽象方法的具体实现. 也可以起到约束的效果.

  3. 先用第一种方法解决问题

class Payment:
    """
    此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。
    """
    def pay(self,money):
        raise Exception("你没有实现pay方法")

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付%s元' % money)

class Alipay(Payment):
    def pay(self,money):
        print('使用阿里支付%s元' % money)

class Wechatpay(Payment):
    def fuqian(self,money):
        print('使用微信支付%s元' % money)


def pay(obj,money):
    obj.pay(money)

a = Alipay()
b = QQpay()
c = Wechatpay()
pay(a,100)
pay(b,200)
pay(c,300)
  • 引入抽象类的概念处理
from abc import ABCMeta,abstractmethod
class Payment(metaclass=ABCMeta):    # 抽象类 接口类  规范和约束  metaclass指定的是一个元类
    @abstractmethod
    def pay(self):pass  # 抽象方法

class Alipay(Payment):
    def pay(self,money):
        print('使用支付宝支付了%s元'%money)

class QQpay(Payment):
    def pay(self,money):
        print('使用qq支付了%s元'%money)

class Wechatpay(Payment):
    # def pay(self,money):
    #     print('使用微信支付了%s元'%money)
    def recharge(self):pass

def pay(a,money):
    a.pay(money)

a = Alipay()
a.pay(100)
pay(a,100)    # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能
q = QQpay()
q.pay(100)
pay(q,100)
w = Wechatpay()
pay(w,100)   # 到用的时候才会报错



# 抽象类和接口类做的事情 :建立规范
# 制定一个类的metaclass是ABCMeta,
# 那么这个类就变成了一个抽象类(接口类)
# 这个类的主要功能就是建立一个规范

总结: 约束其实就是父类对⼦类进行约束. 子类必须要写xxx方法. 在python中约束的⽅式和⽅法有两种:

  1. 使用抽象类和抽象方法, 由于该方案来源是java和c#. 所以使用频率还是很少的

  2. 使用人为抛出异常的方案. 并且尽量抛出的是NotImplementError,这样比较专业, 而且错误比较明确.(推荐)

super()深入了解

super是严格按照类的继承顺序执行

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

    def f2(self):
        print('in A f2')


class Foo(A):
    def f1(self):
        super().f2()
        print('in A Foo')


obj = Foo()
obj.f1()
class A:
    def f1(self):
        print('in A')

class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')

class Bar(A):
    def f1(self):
        print('in Bar')

class Info(Foo,Bar):
    def f1(self):
        super().f1()
        print('in Info f1')

obj = Info()
obj.f1()

print(Info.mro())

# super()是严格按照当前类的继承顺序执行的,不会收到过程中其他类的影响
class A:
    def f1(self):
        print('in A')

class Foo(A):
    def f1(self):
        super().f1()
        print('in Foo')

class Bar(A):
    def f1(self):
        print('in Bar')

class Info(Foo,Bar):
    def f1(self):
        super(Foo,self).f1()    # 这里的意思是绕过Foo,从Foo的位置开始寻找下一个
        print('in Info f1')

obj = Info()
obj.f1()

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

  1. 封装: 把很多数据封装到⼀个对象中. 把固定功能的代码封装到⼀个代码块, 函数, 对象, 打包成模块. 这都属于封装的思想. 具体的情况具体分析. 比如. 你写了⼀个很⽜B的函数. 那这个也可以被称为封装. 在⾯向对象思想中. 是把⼀些看似⽆关紧要的内容组合到⼀起统⼀进⾏存储和使⽤. 这就是封装.

  2. 继承: ⼦类可以⾃动拥有⽗类中除了私有属性外的其他所有内容. 说⽩了, ⼉⼦可以随便⽤爹的东⻄. 但是朋友们, ⼀定要认清楚⼀个事情. 必须先有爹, 后有⼉⼦. 顺序不能乱, 在python中实现继承非常简单. 在声明类的时候, 在类名后⾯添加⼀个⼩括号,就可以完成继承关系. 那么什么情况可以使⽤继承呢? 单纯的从代码层⾯上来看. 两个类具有相同的功能或者特征的时候. 可以采⽤继承的形式. 提取⼀个⽗类, 这个⽗类中编写着两个类相同的部分. 然后两个类分别取继承这个类就可以了. 这样写的好处是我们可以避免写很多重复的功能和代码. 如果从语义中去分析的话. 会简单很多. 如果语境中出现了x是⼀种y. 这时, y是⼀种泛化的概念. x比y更加具体. 那这时x就是y的⼦类. 比如. 猫是⼀种动物. 猫继承动物. 动物能动. 猫也能动. 这时猫在创建的时候就有了动物的"动"这个属性. 再比如, ⽩骨精是⼀个妖怪. 妖怪天⽣就有⼀个比较不好的功能叫"吃⼈", ⽩骨精⼀出⽣就知道如何"吃⼈". 此时 ⽩骨精继承妖精.

  3. 多态: 同⼀个对象, 多种形态. 这个在python中其实是很不容易说明⽩的. 因为我们⼀直在⽤. 只是没有具体的说. 比如. 我们创建⼀个变量a = 10 , 我们知道此时a是整数类型. 但是我们可以通过程序让a = "hello", 这时, a⼜变成了字符串类型. 这是我们都知道的. 但是, 我要告诉你的是. 这个就是多态性. 同⼀个变量a可以是多种形态。
学前沿IT,到英格科技!本文发布时间: 2024-10-25 19:41:31

results matching ""

    No results matching ""