面向对象:继承、多态、封装

类、对象:

  • 类:抽象的
  • 对象:具体的
class Person:  # 类名
    def __init__(self, name, hp, aggr, gender):
    # __init__为初始化方法
        self.name = name
        self.hp = hp
        self.aggr = aggr
        self.gender = gender

    def walk(self, n):  # 定义方法,一般情况下必须传self参数,且必须写在第一个;后面还可以传其他参数,想传多少就多少
        print("{}走了{}步".format(self.name, n))

p = Person("王者", "100", "5", "")  # 对象 = 类(参数),这个过程叫实例化
print(p.name)      # 查看属性值
print(p.__dict__)  # 查看所有属性
p.walk(10)         # 调用这个对象拥有的方法
初识类

类有静态属性和方法,类中的静态属性和方法可以被对象和类调用。

class Person(object):
    money = 0

    def work(self):
        Person.money += 100

mother = Person()
father = Person()
mother.work()
father.work()
print(Person.money)  #200
共享静态属性例子

类的组合:在一个类中以另外一个类的对象作为数据属性

class Car:
    """一次模拟汽车的简单尝试"""
    def __init__(self, make, model, year):
        self.make = make
        self.model = model
        self.year = year

class Battery:
    """一次模拟电动汽车电瓶的简单尝试"""
    def __init__(self, battery_size=70):
        """初始化电瓶的属性"""
        self.battery_size = battery_size

    def describe_battery(self):
        """打印一条描述电瓶容量的消息"""
        print("This car has a " + str(self.battery_size) + "-kWh battery.")

class ElectricCar(Car):
    """电动汽车特殊之处"""
    def __init__(self, make, model, year):
        """初始化父类的属性,再初始化电动汽车特有的属性"""
        super().__init__(make, model, year)
        self.battery = Battery()

my_tesla = ElectricCar("tesla", "model s", 2016)
my_tesla.battery.describe_battery()  #This car has a 70-kWh battery.
View Code

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

继承

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

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

  一个类可以被多个类继承
  一个类可以继承多个父类(python独有)

class A(object):  # 定义父类
    pass
class B(object):  # 定义父类
    pass
class C(A):       # 单继承,父类是A,子类是C
    pass
class D(A,B):     # 多继承(python独有),用逗号分隔开多个继承的类
    pass

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

#在python3中,没有继承父类就默认继承object
print(A.__bases__)    # (<class 'object'>,)
继承

派生

  • 父类中没有的属性,在子类有,叫做派生属性
  • 父类中没有的方法,在子类有,叫做派生方法

注意:

  子类可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),一旦重新定义了自己的属性且与父类重名,调用时就以自己为准;
  要是子类的对象调用属性,子类中有的名字就用子类的, 子类中没有才找父类的, 如果父类也没有就报错

在python3中,子类执行父类的方法也可以直接用super方法(super方法只在python3中存在)

class Base1(object):
    def func(self):
        print("Base1.func")

class Base2(object):
    def func(self):
        print("Base2.func")

class Foo(Base1, Base2):
    def func(self):
        # 方式一:根据mro的顺序执行方法
        super().func()           # Base1.func
        super(Foo, self).func()  # Base1.func
        # 方式二:主动执行父类类的方法
        Base2.func(self)         # Base2.func
        print("Foo.func")

obj = Foo()
obj.func()              # Foo.func
super(Foo, obj).func()  # Base1.func  在类外调用父类方法

print(Foo.mro())  # [<class '__main__.Foo'>, <class '__main__.Base1'>, <class '__main__.Base2'>, <class 'object'>]

继承顺序:

单继承:子类有的用子类,子类没有就用父类
多继承:
  经典类中:深度优先
  新式类中:广度优先

python2.7中: 经典类、新式类共存,新式类要继承object
python3中:    只有新式类,默认继承object

super的本质(python3才有,即只有新式类,广度优先):不是单纯找父类,而是根据调用者的节点位置的广度优先顺序来的

class A(object):
    def func(self):
        print("A")

class B(A):
    def func(self):
        super().func()
        print("B")

class C(A):
    def func(self):
        super().func()
        print("C")

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

d = D()
d.func()
print(D.mro())  # mro()方法可查看类的继承顺序
# [<class "__main__.D">, <class "__main__.B">, <class "__main__.C">, <class "__main__.A">, <class "object">]

多态

多态指的是一类事物有多种形态
动物有多种形态:人、狗、猪

import abc

class Animal(metaclass=abc.ABCMeta):  # 动物类
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal):  # 动物的形态之一:人
    def talk(self): 
        print("你好!")

class Dog(Animal):  # 动物的形态之二:狗
    def talk(self):
        print("旺旺!")

class Cat(Animal):  # 动物的形态之三:猫
    def talk(self):
        print("喵喵!")
View Code
# 类List与类Tuple都有相似的方法,那么这两个类就是鸭子类型,都可以使用len()方法,而不需要像其他语言一样告诉len()自己是什么类型
# 在强类型语言中,叫多态
# 在python中,叫鸭子类型

class List(object):
    def __len__(self):
        pass

class Tuple(object):
    def __len__(self):
        pass

def len(obj):
    return obj.__len__()

l = List()  # 或者T = Tuple()
len(l)
鸭子类型

封装

隐藏对象的属性和实现细节,仅对外提供公共访问方式。

好处:
  1.将变化隔离;
  2.便于使用;
  3.提高复用性;
  4.提高安全性。

封装原则:
  1.将不需要对外提供的内容都隐藏起来;
  2.把属性都隐藏,提供公共方法对其访问。

在python中用双下划线开头的方式将属性隐藏起来(设置成私有属性)

class Person:
    __country = "china"   # 私有静态属性

    def __init__(self, name, age):
        self.name = name
        self.__age = age  # 私有实例属性

    def __get_age(self):  # 私有方法
        return self.__age

    def func(self):
        return self.__get_age()  # 调用私有xx的正确方式

p = Person("pd", 18)
print(p.func())          # 正确使用方式
# print(p._Person__age)  # 对象._类名__属性名(不建议这样使用!!!)
私有xx

封装与扩展性

封装在于明确区分内外,使得类设计者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

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

    def get_age(self):
        return self.__age

    def set_age(self, n):
        if 0 < n and n.isdigit():
            self.__age = n
        else:
            return "年龄不合法"

p = Person("pd", 18)
print(p.get_age())     # 18
print(p.set_age(-18))  # 年龄不合法
View Code

会用到私有的这个概念的场景:

  1.隐藏一个属性,不让类的外部直接调用
  2.保护一个属性,不让属性随意被改变
  3.保护一个属性,不被子类继承

小结

接口类、抽象类:
  python中没有接口类,有抽象类,abc模块中的metaclass = ABCMeta,@abstructmethod
  本质是做代码规范用的,希望在子类中实现和父类方法名字完全一样的方法

接口类与抽象类的区别:
  在java的角度上看是有区别的
    java没有多继承,所以为了接口隔离原则,设计了接口这个概念,以便实现多继承;
    java本来就支持单继承,所以就有了抽象类。
  python既支持单继承也支持多继承,所以对于接口类和抽象类的区别就没有那么明显;python中没有内置接口类

多态与鸭子类型:   多态 --> python天生支持多态   鸭子类型 --> 不依赖父类的情况下实现两个相似的类中的同名方法