跳转至

类和对象

类、对象概述

面向对象就是一种代码封装的方法,就是把相关的数据和实现的函数封装到了一起,Python 的对象等于属性加方法,一个对象诞生前首先需要创建一个类 class,然后通过类去创造对象。

类的属性是写在类里面的变量,类的方法是写在类里面的函数。类的三大特定:封装、继承、多态。

注意: 创建类,类的首字母需要大写,一个类可以创建出无数个对象,当一个对象通过类创建出来之后就可以随意的修改该对象的值,同一个类创建的对象不会共享数据,也可以动态的向对象新增一个属性。可以使用dir()函数查看类的属性和方法。

Python
# 类的创建
class Test:
    a = 1
    b = "hello"

    def aa(self):
        print(123)


# 对象的创建
test = Test()
# 获取对象对应的属性和方法
print(test.a)
test.aa()
# 一个类可以创建多个对象
test1 = Test()
test2 = Test()
# 创建完成的对象可以修改属性值,以及添加新的属性值
test.a = 100
test.c = 1000
print(dir(test))  #  ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'aa', 'b', 'c']
print(dir(test1))  # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'aa', 'b']

类的封装

将类的属性和方法封装在一起,类的方法和函数的区别在于:方法需要使用对象进行访问调用,而函数则可以直接调用。

Python
# self
class C:
    def test(self):
        print(self)


aa = C()
aa.test()  # <__main__.C object at 0x1043a7ac0>
print(aa)  # <__main__.C object at 0x1043a7ac0>

# 可以发现self其实就是对象本身,python通过调用类的方法,不同对象传入的self不同可以区别不同的对象
Python
# 请设计一个 Rectangle 类,该类有 length 和 width 两个属性(分别用于指定长方形的长度和宽度),该类还有 set_length()、set_width()、get_ perimeter() 和 get_area() 四个方法(分别用于设置长方形的长和宽,以及获取长方形的周长和面积)
class Rectangle:
    length = None
    width = None

    def set_length(self):
        self.length = float(input("请输入矩形的长度:"))

    def set_width(self):
        self.width = float(input("请输入矩形的宽度:"))

    def get_perimeter(self):
        if not self.length:
            self.set_length()

        if not self.width:
            self.set_width()

        return (self.length + self.width) * 2

    def get_area(self):
        if not self.length:
            self.set_length()

        if not self.width:
            self.set_width()

        return self.length * self.width


cc = Rectangle()
# cc.set_width(10)
# cc.set_length(10)
print(cc.get_perimeter())
print(cc.get_area())

类的继承

类的继承概述

Python 的类支持继承,可以使用现有类的所有功能,并在无需从新编写代码的情况下对这些功能进行扩展,通过继承创建的新类称为子类,被继承的称为父类、基类或者超类。

注意: 如果子类继承父类,子类存在和父类相同属性或方法,子类会覆盖父类属性或方法。

Python
# 定义父类A
class A:
    a = 123
    def test(self):
        print("类A")
# 子类继承类C
class B(A):
    pass

# 使用父类属性和方法
b = B()
print(b.a)  # --> 123
b.test()    # --> 类A

# 子类存在父类相同属性或方法,会进行覆盖父类属性或方法
class C(A):
    a = 456
    def test(self):
        print("类C")

# 创建类C对象
c = C()
print(c.a)  # 456
c.test()        # 类C

类的继承 BIF(内置函数)

isinstance 函数

判断一个对象是否属于某个类。

Python
# 定义类A
class A:
    a = 123

    def test(self):
        print("类C")
# 定义类B并继承类A
class B(A):
    pass

# 创建类B对象
b = B()
# 判断对象b是否属于类B
isinstance(b, B)  # --> True
# 判断对象b是否属于类A
isinstance(b, A)  # --> True,b是B的对象,B是A的继承关系,所有同样为True

issubclass 函数

用于检测一个类是否为某个类的子类。

Python
# 定义类A
class A:
    a = 123

    def test(self):
        print("类C")
# 定义类B并继承类A
class B(A):
    pass

# 判断类B是否为类A的子类
issubclass(B, A)  # --> True
# 判断类A是否为类B的子类
issubclass(A, B)  # --> False

类的多重继承

Python 的类支持多重继承,一个子类可以继承多个父类。

Python
# 类A
class A:
    a = 123

    def test(self):
        print("类A")
# 类B
class B:
    a = 456
    b = 789

    def test(self):
        print("类B")
# 多重继承,创建类C同时继承类A和类B
class C(A, B):
    pass

# 创建类C对象
c = C()
# 使用类C属性和方法
# 当继承的多个类存在相同属性或方法,默认从左到右顺序,左边的元素或方法高于右侧,当左边没有才会去右边找
print(c.a)  # a属性类A和类B都有,默认使用左边类A --> 123
print(c.b)  # b属性类A没有类B存在,使用右边类B     --> 789
c.test()        # 方法同样相同使用左边类的方法             --> 类A

# 多重继承同样遵循继承原理,默认本身的方法和属性会覆盖父类的方法和属性
class D(A, B):
    a = 999
    def test(self):
        print("类D")

# 创建类D对象
d = D()
# 使用属性和方法
print(d.a)  # --> 999
d.test()        # --> 类D

类的组合(不同于继承)

将多个类组合在一起,形成一个新的类,不同于继承。

假如我们需要定义的几个类,拥有一些共同的属性和方法,那么我们应该考虑使用继承,讲这些共有的属性和方法设计在父类中,而特殊的属性和方法设计在子类中,这样一来解决了代码冗余的问题,二来提高了代码的可扩展性(比如定义动物为父类,猫、狗、鸡、鸭、鹅则可以定义为子类)。 假如我们需要定义的几个类之间并没有从属关系,而是同级,那么就应该考虑使用组合(比如人的手、脚、嘴巴;花园里的花、草、猫、狗)。

使用类的组合,定义的方法需要使用对应的属性同样需要使用 self 参数,这个参数实现的功能为绑定,将实例对象和类进行绑定,实例对象有很多,它共享类的方法,当调用实例对象时,此时就需要 self 参数进行区分不同的实例对象,实例对象的方法可以共享,但是每个实例对象的属性仅对本对象使用。

Python
# 定义类A
class A:
    a = 1
    def aa(self):
        print("类A")
# 定义类B
class B:
    b = 2

    def bb(self):
        print("类B")
# 定义类C
class C:
    c = 3
    def cc(self):
        print("类C")
# 定义类D将类A,B,C组合放入类D
class D:
    a = A()
    b = B()
    c = C()
    def dd(self):
        self.a.aa()
        self.b.bb()
        self.c.cc()

# 创建类D对象d
d = D()
# 使用类D组合获取的属性和方法
print(d.a.a)    # --> 1
print(d.b.b)    # --> 2
print(d.c.c)    # --> 3
d.dd()              # --> 类A    类B  类C

查看类的属性

可以使用内省的方法对类的属性进行查看,使用 __dict__ 来对类属性的查看,如果类的对象没有自己的属性,它默认拥有类的所有属性,此时如果修改类的属性的值,没有自己属性的对象对应的属性值也会发生改变。

Python
# 定义类A
class A:
    pass

# 通过类A创建对象a
a = A()
# 通过类A创建对象b
b = A()
# 分别添加属性值
a.x = 100
a.y = 150
b.x = 200
# 分别查看属性值,使用__dict__方法以字典的形式返回
print(a.__dict__)  # --> {'x': 100, 'y': 150}
print(b.__dict__)  # --> {'x': 200}
# 通过类里的方法设置对象自己的属性
# 定义类B
class B:
    test = None
    def test_func(self, i):
        self.test = i

# 通过类B创建对象a,b
a = B()
b = B()
# 通过对象的方法设置自己的属性
a.test_func(200)
b.test_func(300)
print(a.__dict__)       # {'test': 200}
print(b.__dict__)       # {'test': 300}

类的高阶用法(充当字典使用)

Python
# 定义最小的类
class A:
    pass

# 通过类A创建对象a
a = A()
# 通过类的属性充当字典
a.x = 123
a.y = "hello"
a.z = [1, 2, 3]
print(a.x)
print(a.y)
print(a.z)
Python
## 例一:
# 类中属性存在容器,如果对象不添加自己的容器,不同的对象则共同使用类中的容器
# 创建存在容器属性的类
class A:
    a = []

    def aa(self, x):
        self.a.append(x)

# 定义对象a,b
a = A()
b = A()
# 分别进行添加容器元素
a.aa(100)
b.aa(100)
# 可以看到其实访问的是同一个容器,容器指向同一个内存地址
print(a.a)      # [100, 100]
print(b.a)      # [100, 100]
# 如果想要不同对象使用不同容器,则需要在对象中新建属于自己的容器

## 例二:
# 类中定义的函数类型为:function,而通过类创建的对象使用的则是方法类型为:method
# 创建类A
class A:
    x = 100
    def aa(self):
        print("111")

# 通过类A创建对象a
a = A()
# 可以方法对应的类型一个为:function一个为:method
print(type(a.aa))       # <class 'function'>
print(type(A.aa))       # <class 'method'>

构造函数

构造函数 __init__(),只需要在类中定义 __init__() 方法就可以在实例化对象的同时实现个性化定制,

Python
# 定义包含构造函数的类A
class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def add(self):
        return self.x + self.y
    def mul(self):
        return self.x * self.y

# 通过类A创建对象a
a = A(2, 4)
print(a.add())  # 6
print(a.mul())  # 8

重写

当类的继承,如果对父类的某个方法或属性不满意,可以进行覆盖方法或者重写属性,这种行为就叫做重写。

Python
# 创建父类A,包含构造函数__init__
class A:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def add(self):
        return self.x + self.y

    def mul(self):
        return self.x * self.y

# 创建A的子类B,并对子类B进行重写
class B(A):
    def __init__(self, x, y, z):
        # 此时可以不用再写self.x = x, self.y = y,直接使用A.__init__(self, x, y)进行等价
        A.__init__(self, x, y)
        self.z = z

    def add(self):
        # 同样直接使用A类的方法进行代替
        return A.add(self) + self.z

    def mul(self):
        # 同样直接使用A类的方法进行代替
        return A.mul(self) * self.z

# 可以发现已经重写完成
b = B(1, 2, 3)
print(b.add())
print(b.mul())

钻石继承

通过 1.3.8 的重写,直接通过类名访问类里面的方法的做法称为调用未绑定的父类方法,这种做法容易产生钻石继承。

Python
# 钻石继承
# 定义父类A,包含构造函数
class A:
    def __init__(self):
        print("这个是类A")

# 定义A的子类B同样包含构造函数
class B1(A):
    def __init__(self):
        A.__init__(self)
        print("这个是类B1")

# 定义A的子类C同样包含构造函数
class B2(A):
    def __init__(self):
        A.__init__(self)
        print("这个是类B2")

# 定义同时继承B,C的类D包含构造函数
class C(B1, B2):
    def __init__(self):
        B1.__init__(self)
        B2.__init__(self)
        print("这个是类C")

# 这样就组成了钻石继承,输出发现打印了两个类A,这是由于类D继承与类B和类C,类B1和类B2都继承类A,所以执行了两次类A
c = C()
# 这个是类A
# 这个是类B1
# 这个是类A
# 这个是类B2
# 这个是类C

可以通过 super() 函数避免这种钻石继承, super() 可以在父类中搜索指定的方法并自动绑定 self 参数。

Python
# 钻石继承的避免使用super()函数
# 定义父类A,包含构造函数
class A:
    def __init__(self):
        print("这个是类A")


# 定义A的子类B同样包含构造函数
class B1(A):
    def __init__(self):
        super().__init__()
        print("这个是类B1")


# 定义A的子类C同样包含构造函数
class B2(A):
    def __init__(self):
        super().__init__()
        print("这个是类B2")


# 定义同时继承B,C的类D包含构造函数
class C(B1, B2):
    def __init__(self):
        super().__init__()
        print("这个是类C")

# 可以发现成功避免了钻石继承
c = C()
# 这个是类A
# 这个是类B2
# 这个是类B1
# 这个是类C

MRO 顺序

MRO 顺序:如果出现同名的属性或方法,Python 会有一个明确的查找覆盖顺序,这个顺序就叫做 MRO 顺序,翻译过来就叫做方法解析顺序,其中函数super()遵循的就是 MRO 顺序。

Python
# 钻石继承
# 定义父类A,包含构造函数
class A:
    def __init__(self):
        print("这个是类A")
# 定义A的子类B同样包含构造函数
class B1(A):
    def __init__(self):
        super().__init__()
        print("这个是类B1")
# 定义A的子类C同样包含构造函数
class B2(A):
    def __init__(self):
        super().__init__()
        print("这个是类B2")
# 定义同时继承B,C的类D包含构造函数
class C(B1, B2):
    def __init__(self):
        super().__init__()
        print("这个是类C")

# 查看类的MRO顺序
# 方法一,其中<class 'object'>是默认继承属性,不管有没有继承关系默认都存在
C.mro()     # [<class '__main__.C'>, <class '__main__.B1'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>]

# 方法二
C.__mro__  # [<class '__main__.C'>, <class '__main__.B1'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>]

注意: MRO 顺序遵循从左到右规则

Python
class A:
    pass
class B1(A):
    pass
class B2(A):
    pass
class C(B1, B2):
    pass
print(C.mro())  # [<class '__main__.C'>, <class '__main__.B1'>, <class '__main__.B2'>, <class '__main__.A'>, <class 'object'>]
# 可以发现顺序: C->B1->B2->A->object,完全遵循class C(B1, B2)
# 修改重写类C
class C(B2, B1):
    pass
print(C.mro())  # [<class '__main__.C'>, <class '__main__.B2'>, <class '__main__.B1'>, <class '__main__.A'>, <class 'object'>]
# 可以发现顺序:C->B2->B1->object,完全遵循class C(B2, B1)

注意: 如果使用 super() 在父类中查找方法,则需要考虑 MRO 规则,从左到右,每条都要添加 super() 不然会无法实现继承效果。

Python
# 使用错误的super()
class A:
    def __init__(self):
        print("这是类A")
class B:
    def __init__(self):
        print("这是类B")
class C(A):
    def __init__(self):
        super().__init__()
        print("这是类C")
class D(B):
    def __init__(self):
        super().__init__()
        print("这是类D")
class E(C, D):
    def __init__(self):
        super().__init__()
        print("这是类E")

# 可以发现打印效果并不是需要实现的效果
e = E()
# 这是类A
# 这是类C
# 这是类E
# 分析执行继承顺序(根据MRO):[<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.D'>, <class '__main__.B'>, <class 'object'>]
# 可以发现顺序是:E->C->A->D->B->object
# 但是在类A中没有super()所有在类A就断开了继承,所以只打印了类A类C和类E
# 只需要在类A添加super()即可实现正确继承

# 使用正确的super()
class A:
    def __init__(self):
        super().__init__()
        print("这是类A")
class B:
    def __init__(self):
        print("这是类B")
class C(A):
    def __init__(self):
        super().__init__()
        print("这是类C")
class D(B):
    def __init__(self):
        super().__init__()
        print("这是类D")
class E(C, D):
    def __init__(self):
        super().__init__()
        print("这是类E")

 # 执行正确
e = E()
# 这是类B
# 这是类D
# 这是类A
# 这是类C
# 这是类E
# 当然对于这种多个继承关系,直接使用方法名会更加简单

# 如果多重继承需要传递参数,参数也要一一对应
# 例如:
class A:
    def __init__(self, arg):
        super().__init__(arg)
        print("FishA")
class B:
    def __init__(self, arg):
        # 可以不要这个,因为这个是最后继承,下一个是默认的object
        super().__init__()
        print("FishB")
class C(A):
    def __init__(self, arg):
        super().__init__(arg)
        print("FishC")
class D(B):
    def __init__(self, arg):
        super().__init__(arg)
        print("FishD")
class E(C, D):
    def __init__(self, arg):
        super().__init__(arg)
        print("FishE")

e = E(520)

Mixin 类继承混入

利用编程语言已经的特性,针对面向对象开发中反复出现的问题而设计出来的解决方案。将学过的语法组合起来,达到更好的效果,可以将 Mixin 类理解为扩展插件,它不同于父类,和需要关联的类没有直接联系。

Python
class Displayer:
    def displayer(self, text):
        print(text)

class Logmixin:
    def log(self, text, file_name="log.txt"):
        with open(file_name, "a") as f:
            f.write(text)

    def displayer(self, text):
        super().displayer(text)
        self.log(text)

class Mysubclass(Logmixin, Displayer):
    def log(self, text):
        super().log(text, file_name="logs.txt")

mysubclass = Mysubclass()
mysubclass.displayer("This is test")

类的多态

类的多态概述

指同一个运算符、函数或对象在不同的环境下具有不同的效果。比如加号乘法还有 len() 函数等等,都是具有多态特性,类的多态是以从写的方式实现。

Python
# 示例

# 定义类A
class A:
    def __init__(self, a):
        self.a = a

    def aa(self):
        print(f"这是{self.a}")

# 定义类B
class B:
    def __init__(self, b):
        self.b = b

    def aa(self):
        print(f"这是{self.b}")

# 定义函数abc
def abc(test):
    test.aa()

# 通过类A B创建对象c1 c2
c1 = A("aaaaa")
c2 = B("bbbbb")
# 可以发现使用相同的函数abc输入不同对象执行不同的结果就是多态
abc(c1)     # --> aaaaa
abc(c2)     # --> bbbbb

鸭子类型

类的多态不关心本身到底是不是某种类型,只要满足需要的参数就可以实现多态。

Python
# 根据1.4.1示例,继续定义其他类只要满足有对应的aa属性同样可以执行

# 定义函数abc
def abc(test):
    test.aa()

# 继续定义类C
class C:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def aa(self):
        print(self.x+self.y)

# 即使和1.4.1类不同参数也不同,但同样可以使用abc函数执行类的aa方法,这个就是鸭子类型
c3 = C(12, 23)
abc(c3)     # 35