面向对象

55 阅读13分钟

类可以看做是一个模版,或者图纸,系统根据类的定义来造出对象。我们要造一个汽车,怎么样造?类就是这个图纸,规定了汽车的详细信息,然后根据图纸将汽车造出来。python中类叫class,对象叫object,或者实例instance
将一个事物分解成状态和行为两部分,比如学生类,状态就是他的身份信息(姓名,学习,年龄...),行为(学习,写作业,玩...),将状态和行为组合到一起,这就是类。 在类中每个对象都会共享这个类的行为,但是状态是不共享的,比如每个学生都能学习,写作业,玩;但是每个学生的身份信息是不一致。

image.png

定义:
image.png

class Student:  
    
    # 构成方法:用于初始化对象的,self表示是哪一个对象,类似于指针,Java中的this
    def __init__(self, name, score):  
        self.name = name  
        self.score = score  
  
    def say_score(self):  
        print(f"{self.name}的分数是:{self.score}")  
  
s1 = Student("hhc", 149)  
s1.say_score()

__ init __ 与 __ new __

init:是用于初始化对象的,一般在通过类创建对象时,都会传递参数进去,初始化当前对象的相关属性。第一个参数必须是self,指向创建的对象。如果我们不定义 init 方法,系统会提供一个默认的 init 方法。如果我们定义了带参的 init 方法,系统不创建默认的 init 方法

def __init__(self, name, score):  
    self.name = name # 实例属性  
    self.score = score

new:用于创建对象的,创建对象后self就指向的这个对象,如何再调用init方法。

实例属性

实例属性是从属于实例对象的属性,也称为“实例变量”。实例属性有三种定义方式

  1. init函数中(常见)
  2. 其他实例函数中 (实例属性存在则修改值,不存在则添加)
  3. 对象.实例属性 = 值 (实例属性存在则修改值,不存在则添加)
class Student:  
  
    def __init__(self, name, score):  
        # 方式一  
        self.name = name  
        self.score = score  
  
    def say_score(self):  
        # 方式二  
        self.age = 21  
        print(f"{self.name}的分数是:{self.score}")  
  
s1 = Student("hhc", 149)  
s1.say_score()  
print(s1.age)  
# 方式三  
s1.salary = 3000  

实例方法

image.png
方法调用:对象.方法名([ 实参列表 ])

要点:定义实例方法时,第一个参数必须为 self 。和前面一样, self 指当前的实例对象。调用实例方法时,不需要也不能给 self 传参。 self 由解释器自动传参

对象.方法名(参数列表) 本质上就是 => Student.方法名(对象,参数列表)

class Student:  
  
    def __init__(self, name, score):  
        # 方式一  
        self.name = name  
        self.score = score  
  
    # 实例方法  
    def say_score(self):  
        # 方式二  
        self.age = 21  
        print(f"{self.name}的分数是:{self.score}")  

s1 = Student("hhc", 149)  
# 调用实例方法  
s1.say_score() # hhc的分数是:149  
Student.say_score(s1) # hhc的分数是:149  
print(s1.age) # 21  
# 方式三  
s1.salary = 3000  
  
print(dir(s1))  
print(s1.__dict__) # {'name': 'hhc', 'score': 149, 'age': 21, 'salary': 3000}  
print(isinstance(s1,Student)) # True

类对象

我们在前面讲的类定义格式中,class 类名:实际上,当解释器执行 class 语句时,就会创建一个类对象


class Student:  
    pass  
  
print(Student)       # <class '__main__.Student'>  
print(type(Student)) # <class 'type'>  
print(id(Student))   # 2000328626512  
  
s1 = Student  
s2 = s1()  
print(s2)            # <__main__.Student object at 0x000001D1BCEE77F0>  
print(type(s2))      # <class '__main__.Student'>  
print(id(s2))        # 2000329537520

我们可以看到实际上生成了一个变量名就是类名 Student 的对象。我们通过赋值给新变量 Stu2 ,也能实现相关的调用。说明,确实创建了“类对象”。

类属性

前面我们说实例属性是不共享的,而这个类属性是全部实例对象共享的。它是定义在类里面,函数外面的变量
image.png
在类中或者类的外面,我们可以通过: 类名.类变量名 来读写
在类外面,不要通过:对象.类变量名 来写,因为这样会创建一个实例变量,初始值为类变量的值,当进行修改时,改的是实例变量,而不是类属性

class Student:  
    company = "mjj"  
    count = 0  
  
    def __init__(self, name, age):  
        self.name = name  
        self.age = age  
        Student.count += 1  
  
    def say_score(self):  
        print(f"company:{Student.company}")  
        print(f"{self.name}的年龄是:{self.age}")  

s1 = Student('hhc', 21)  
s2 = Student("cg", 22)  
print(s1.count) # 2  
# 创建了一个实例变量count,初始值为2,进行+1操作,count = 3  
s2.count += 1  
# Student.count += 1  
# s1 没有count实例变量,访问的是类属性  
print(s1.count) # 2  
# s2 有count这个实例变量,所以访问的是自己的实例变量,为3  
print(s2.count) # 3  
print(s2.company) # mjj  
print(f"共f{Student.count}个对象") # 2

类方法

类方法是从属于“类对象”的方法。类方法通过装饰器 @classmethod 来定义,格式如下:

image.png

要点如下:

  1. @classmethod 必须位于方法上面一行
  2. 第一个 cls 必须有; cls 指的就是“类对象”本身
  3. 调用类方法格式: 类名.类方法名(参数列表)。参数列表中,不需要也不能给 cls 传值
  4. 类方法中访问实例属性和实例方法会导致错误
  5. 子类继承父类方法时,传入 cls 是子类对象,而非父类对象(⚠️讲完继承再说)
class Student:  
    company = "mjj"  
  
    @classmethod  
    def printCompany(cls):  
        print(cls.company)  
  
s1 = Student()  
Student.printCompany() # mjj  
s1.printCompany() # mjj

静态方法

Python中允许定义与“类对象”无关的方法,称为“静态方法”。 “静态方法”和在模块中定义普通函数没有区别,只不过“静态方法”放到了“类的名字空间里面”,需要通过“类调用”。静态方法中访问实例属性和实例方法会导致错误

image.png

class Student:  
    @staticmethod  
    def add(num1,num2):  
        print(num1+num2)  
  
Student.add(30,45) # 75  
Student.add("hhc","llxq") # hhcllxq

__ del__方法 (析构函数)和垃圾回收机制

__ del__() 称为“析构方法”,用于实现对象被销毁时所需的操作。当对象没有被引用时,由垃圾回收器调用__ del__()

class Student:  
    def __del__(self):  
        print(f"{id(self)}被销毁")  
  
s1 = Student()  
s2 = Student()  
s3 = Student()  
s4 = s3  
del s2  
del s3  
print("程序结束")  
  
"""  
2145245607824被销毁  
程序结束  
2145245608112被销毁  
2145245607920被销毁  
"""

__ call__ 方法和可调用对象

Python 中,凡是可以将 () 直接应用到自身并执行,都称为可调用对象。对象不是可执行对象,即对象()这种方式是错误的,如果想让对象变成可执行对象,可以给类添加call函数,让对象变成可执行对象,其本质就是执行call方法

class Student:  
 
    def __call__(self, *args):  
        print(*args) 
  
s1 = Student()  
s1("hhh","llxq")    # hhh llxq  

方法的动态性

方法的动态性即在类外部,我们可以动态的为类添加新的方法,或者动态 的修改类的已有的方法。通过类名.方法1=方法2 对已存在的方法1进行修改,对未存在的方法1进行添加,但是方法2中必须有个形参,用来提供self的传递。本质上就是修改引用

class Student:  
  
    def read(self):  
        print("read")  
  
def play(s):  
    print("play")  
  
def sleep(slef):  
    print("sleep")  
 
# 将read的引用修改为play的引用
Student.read = play  
# 新增一个sleep变量,指向sleep函数
Student.sleep = sleep  
  
s1 = Student()  
s1.read() # play  
s1.sleep() # sleep

上面的代码可以看到,使用play方法替代了read方法,Student类新增了sleep方法

私有属性和私有方法

python对类的成员没有严格的访问控制,和常量一样,我们只是约定某种形式的变量是私有的:

  1. 两个下划线开头的属性是私有的(private)。其他为公共的(public)
  2. 类内部可以访问私有属性(方法)
  3. 类外部不能直接访问私有属性(方法),但是可以通过 _ 类名__私有属性(方法)名 访问私有属性(方法)
class Student:  
    __name = "hhc"  
  
    def __init__(self, name, age):  
        self.name = name  
        self.__age = age  
  
    def getAge(self):  
        print(self.__age)  
  
    def __getName(self):  
        print(self.__name)  
        print(self.__age)  
  
s1 = Student("hhc",21)  
# print(s1.__name) # AttributeError: 'Student' object has no attribute '__name'  
#s1.__getName() # AttributeError: 'Student' object has no attribute '__getName'  
  
print(s1._Student__name) # hhc  
print(s1._Student__getName()) # hhc 21 None(没有return语句,默认返回None)  
print(s1.getAge()) # 21 None  
  
#print(s1.__age) # AttributeError: 'Student' object has no attribute '__age'  
print(s1._Student__age) # 21  
print(s1.getAge()) # 21 None
print(dir(s1)) # '_Student__age', '_Student__getName', '_Student__name'

property装饰器

@property可以将一个方法的调用方式变成“属性调用”。@property主要用于帮助我们处理属性的读操作、写操作。对于某一个属性,我们可以直接通过:emp1.salary = 30000。如上的操作读操作、写操作。但是,这种做法不安全。比如,我需要限制薪水必须为1-10000的数字。这时候,我们就需要通过使用装饰器 @property来处理。

class Employee:  
  
def __init__(self, name, salary):  
self.name = name  
self.__salary = salary  
  
@property  
def money(self):  
print(f"{self.__salary}")  
  
@money.setter  
def salary(self,salary):  
self.__salary = salary  
  
e1 = Employee("hhc",6200)  
e1.salary # 6200  
e1.salary = 20000  
e1.salary # 20000

None

None 是一个特殊的常量,表示变量没有指向任何对象。在Python中, None 本身实际上也是对象,有自己的类型 NoneType 。你可以将 None 赋值给任何变量,但我们不能创建 NoneType 类型的对象。None和其他类型进行比较,永远返回的是False.

a = None  
b = None  
print(type(a)) # <class 'NoneType'>  
print(type(b)) # <class 'NoneType'>  
print(type(None)) # <class 'NoneType'>  
  
print(id(a)) # 140724952571584  
print(id(b)) # 140724952571584  
print(id(None)) # 140724952571584  
  
print(a == None) # True  
print(a == False) # False

对象的三大特性

封装

隐藏对象的属性和实现细节,只对外提供必要的方法。相当于 将“细节封装起来”,只对外暴露“相关调用方法”。 通过前面学习的“私有属性、私有方法”的方式,实现“封装”。

继承

继承让我们更加容易实现类 的扩展。实现代码的重用,如果一个新类继承自一个设计好的类,就直接具备了已有类的特 征,就大大降低了工作难度。已有的类,我们称为“父类或者基 类”,新的类,我们称为“子类或者派生类”。一个类如果没有显式的继承某个类,那么它默认继承的是object类,可以说object是所有类的父类。
image.png

image.png

子类是继承了父类非构造函数的所有行为和状态,包括私有的。
对于构造函数:

  1. 如果子类没有重写构造函数,则默认使用父类的构造函数
  2. 如果子类实现了构造函数,则使用自己的构造函数
  3. 如果想要使用父类的构造函数,可以使用super()关键字,也可以使用 父类名.init(self, 参数列表)
class Persion:  
  
    def __init__(self, name, age):  
    print("父类的init...")  
    self.name = name  
    self.age = age  
  
class Student(Persion):  
  
    def __init__(self, name, age, score):  
    print("子类的init...")  
    # Persion.__init__(self,name,age)  
    super(Student,self).__init__(name,age)  
    # self.name = name  
    # self.age = age  
    self.score = score  
  
  
s1 = Student("hhc", 21, 90)  
print(s1.name, s1.age, s1.score)

方法重写

子类继承了父类的方法,当子类和父类有相同的方法,则就是说子类对父类的方法进行了重写,使用该方法时,默认使用子类实现的方法,如果想要使用父类的方法可以使用suoer().方法名() or 父类.方法名(self)

class Persion:  
  
    def __init__(self, name, age):  
        self.name = name  
        self.age = age  
  
    def print_age(self):  
        print(f"父类方法:age={self.age}")  
  
    def print_name(self):  
        print(f"父:name={self.name}")  
    
class Student(Persion):  
  
    def __init__(self, name, age, score):  
        # Persion.__init__(self,name,age)  
        super(Student,self).__init__(name,age)  
        self.score = score  

    def print_score(self):  
        print(f"子:score={self.score}")  
  
    def print_age(self):  
        # 使用父类的方法  
        # super().print_age()  
        Persion.print_age(self)  
        print(f"子:score={self.age}")  
        
s1 = Student("hhc", 21, 90)  
print(s1.name, s1.age, s1.score)  
s1.print_score()  
s1.print_age()

查看类的继承层次结构

通过类的方法 mro() 或者类的属性 mro 可以输出这个类的继承层次 结构。

class A:pass  
class B(A):pass  
class C(B):pass  
  
print(A.mro()) # [<class '__main__.A'>, <class 'object'>]  
print(B.mro()) # [<class '__main__.B'>, <class '__main__.A'>, <class 'object'>]  
print(C.mro()) # [<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]  
  
print(A.__mro__) # (<class '__main__.A'>, <class 'object'>)  
print(B.__mro__) # (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>)  
print(C.__mro__) # (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

image.png

重写 __ str__()方法

object有一个 __ str__() 方法,用于返回一个对于“对象的描述”。内置函数str(对象),调用的就是__ str__()
__ str__()经常用于 print() 方法,帮助我们查看对象的信息。 __ str__() 可以重写


class Persion:  
  
    def __init__(self, name, age):  
        self.name = name  
        self.age = age  
  
    def __str__(self):  
        print("重写str方法")  
        return f"{self.name}的年龄为{self.age}"  
  
p = Persion("hhc", 21)  
print(p) # hhc的年龄为21  
print(str(p)) # hhc的年龄为21

多重继承

image.png
Python支持多重继承,一个子类可以有多个“直接父类”。这样,就 具备了“多个父类”的特点。但是由于,这样会被“类的整体层次”搞的 异常复杂,尽量避免使用。

super获取父类的定义

在子类中,如果想要获得父类的方法时,我们可以通过super()来做。super()代表父类的定义,不是父类对象。super(子类名称,self).init(参数列表)

class Persion:  
  
    def __init__(self, name, age):  
        print("父__init__")  
        self.name = name  
        self.age = age  

    def say_age(self):  
        print(f"父say_age:{self.age}")  
  
class Student(Persion):  
  
    def __init__(self, name, age, score):  
        # 调用父类的构造方法  
        super(Student,self).__init__(name, age)  
        self.score = score  
  
    # 重写父类方法  
    def say_age(self):  
        # super().say_age()  
        Persion.say_age(self)  
        print("子say_age")  
  
s = Student("hhc", 21, 90)  
s.say_age()

多态

多态(polymorphism)是指同一个方法调用由于对象不同可能会 产生不同的行为。
比如:现实生活中,同一个方法,具体实现会完全不同。 比 如:同样是调用人“吃饭”的方法,中国人用筷子吃饭,英国人用 刀叉吃饭,印度人用手吃饭。 注意点:

  1. 多态是方法的多态
  2. 多态的前提是必须继承和方法的重写

class Animal:  
    def shut(self):  
        print("Animal叫了一下")  
  
class Dog(Animal):  
    def shut(self):  
        print("Dog汪汪叫")  
  
class Cat(Animal):  
    def shut(self):  
        print("Cat喵喵叫")  
  
def animalShut(animal):  
    animal.shut()  
  
animalShut(Dog())  
animalShut(Cat())

特殊方法和运算符重载

Python的运算符实际上是通过调用对象的特殊方法实现的。
image.png image.png

a = 30  
b = 50  
print(a+b) # 80  
print(a.__add__(b)) # 80  
  
class Person:  
  
    def __init__(self, name):  
        self.name = name  
  
    def __add__(self, other):  
        if isinstance(other, Person):  
            return f"{self.name}-{other.name}"  
        else:  
            return "不是同类,不能相加"  
  
    def __mul__(self, other):  
        if isinstance(other, int):  
        return self.name*other  
    else:  
        return "不是同类,不能相乘"  
  
p1 = Person("hhc")  
p2 = Person("lxq")  
  
print(p1+p2) # hhc-lxq  
  
print(p1+2) # 不是同类,不能相加  
  
print(p2*4) # lxqlxqlxqlxq

特殊属性

Python对象中包含了很多双下划线开始和结束的属性,这些是特殊 属性,有特殊用法。这里我们列出常见的特殊属性:
image.png

组合

除了继承,组合也能实现代码的复用,它和继承的区别是,继承是is a 属于关系,比如狗属于动物,学生属于人类;组合是has a 有关系,比如电脑有鼠标,显示器...

class Computer:  
  
    def __init__(self, cup, screen):  
        self.cup = cup  
        self.screen = screen  
  
class Cpu:  
  
    def calculate(self):  
        print("计算...")  
  
class Screen:  
  
    def show(self):  
        print("显示器显示")  
  
c = Computer(Cpu(),Screen())  
c.cup.calculate() # 计算...  
c.screen.show() # 显示器显示

设计模式

工厂模式

工厂模式实现了创建者和调用者的分离,使用专门的工厂类将选择实现类、创建对象进行统一的管理和控制。

class CarFactory:  
    def createCar(self, brand):  
        if brand == "奔驰":  
            return Benz()  
        elif brand == "宝马":  
            return BMW()  
        elif brand == "比亚迪":  
            return BYD()  
        else:  
            return "杂牌"  
  
class Benz:  
    pass  
  
class BMW:  
    pass  
  
class BYD:  
    pass  
  
carFactory = CarFactory()  
b = carFactory.createCar("宝马")  
print(b,type(b)) # <__main__.BMW object at 0x0000014F11BCB280> <class '__main__.BMW'>  
c = carFactory.createCar("比亚迪")  
print(c,type(c)) # <__main__.BYD object at 0x0000014F11BCB250> <class '__main__.BYD'>  
d = carFactory.createCar("奔驰")  
print(d,type(d)) # <__main__.Benz object at 0x0000014F11BCB3D0> <class '__main__.Benz'>  
e = carFactory.createCar("hhc")  
print(e,type(e)) # 杂牌 <class 'str'>

单例模式

单例模式(Singleton Pattern)的核心作用是确保一个类只有一个 实例,并且提供一个访问该实例的全局访问点。

class MySingleton:  
    __obj = None  
    __init_flag = True  
  
    def __new__(cls, *args, **kwargs):  
        if cls.__obj is None:  
            cls.__obj = object.__new__(cls)  
        return cls.__obj  
  
    def __init__(self, name):  
        if self.__init_flag:  
            print("init...")  
            self.name = name  
            self.__init_flag = False  
  
  
m1 = MySingleton("hhc")  
m2 = MySingleton("lxq")  
print(m1.name,type(m1),id(m1)) # hhc <class '__main__.MySingleton'> 1847743721776  
print(m2.name,type(m2),id(m2)) # hhc <class '__main__.MySingleton'> 1847743721776