Python 面向对象编程

面向对象编程

面向对象(Object-Oriented)和面向过程(Procedural)是两种不同的编程范式,它们在软件设计和实现上有一些根本的区别。

  • 面向过程

面向过程编程是基于函数和过程的编程范式。在这种范式中,程序被组织为一系列的函数或过程,这些函数或过程按照一定的顺序执行,完成特定的任务。面向过程编程的特点包括:

  1. 函数和过程:程序由函数和过程组成,它们是执行特定任务的基本单元。
  2. 数据隐藏:数据和函数通常是分离的,数据隐藏在函数内部。
  3. 模块化:程序被分解为多个模块,每个模块负责完成一个特定的功能。
  4. 自顶向下设计:从整体到局部,先设计高层次的结构,再逐步细化到低层次的实现。
  • 面向对象

面向对象编程是基于对象和类的编程范式。在这种范式中,程序被组织为对象,对象是具有数据和行为的实体。面向对象编程的特点包括:

  1. 封装:将数据和操作数据的函数封装在一起,隐藏内部实现细节,对外只提供公共接口。
  2. 继承:允许一个类继承另一个类的属性和方法,实现代码的复用。
  3. 多态:允许不同类的对象对同一消息作出响应,通过方法重载和动态绑定实现。
  4. 自底向上设计:从具体的对象和类开始,逐步构建复杂的系统。
  • 区别
  1. 设计哲学:面向过程强调的是功能的分解和过程的执行,而面向对象强调的是对象的创建和交互。
  2. 数据与行为:面向过程通常将数据和函数分离,而面向对象将数据和操作数据的函数封装在一起。
  3. 代码复用:面向对象通过继承和多态实现代码复用,而面向过程则更多地依赖于函数和过程的重用。
  4. 系统构建:面向过程倾向于自顶向下设计,而面向对象倾向于自底向上设计。在实际编程中,这两种范式并不是互相排斥的,很多现代编程语言都支持这两种范式,并且可以根据具体需求灵活运用。例如,在 Python 中,你可以使用面向对象的方式编写类和对象,也可以使用面向过程的方式编写函数和过程。

面向过程的思维方式是分析综合,面向对象的思维方式是构造。 简单来说:用面向过程的方法写出来的程序是一份蛋炒饭,而用面向对象写出来的程序是一份盖浇饭。

  • 面向对象的代码结构
  1. 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

  2. **方法:**类中定义的函数。

  3. **类变量:**类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。

  4. **数据成员:**类变量或者实例变量用于处理类及其实例对象的相关的数据。

  5. **方法重写:**如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

  6. **局部变量:**定义在方法中的变量,只作用于当前实例的类。

  7. **实例变量:**在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。

  8. **继承:**即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例如,Dog是一个Animal)。

  9. **实例化:**创建一个类的实例,类的具体对象。

  10. **对象:**通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法

Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。

1.9.1 类的定义和使用

在Python中,类(Class)是面向对象编程(Object-Oriented Programming, OOP)的基础概念。类是创建对象(Object)的蓝图,它定义了对象的属性和方法。每个对象都是类的一个实例,具有类定义的属性和行为。

类是一种用户自定义的数据结构,它包含了一组属性(成员变量)和行为(成员方法)。在Python中,可以使用class关键字来定义一个类,并使用def关键字来定义类的成员方法。

实现原理:

  1. 使用class关键字定义一个类,类名作为该类的标识符。
  2. 在类中定义属性(成员变量)和行为(成员方法)。属性可以使用类属性 = 值进行定义,方法可以使用def关键字加方法名、self参数和方法体进行定义。
  3. 使用对象名 = 类名()创建一个类的实例对象。
  4. 使用.属性名或[]操作符为对象的属性赋值,或使用.方法名()调用对象的方法。

用途: 类是Python中一种高级的数据结构,它可以用来创建和封装一组相关的数据和行为。在实际应用中,可以使用类来定义对象的数据结构和行为,然后创建对象实例来使用这些属性和方法。

注意事项:

  1. 在定义类的方法时,需要使用self作为第一个参数,以便让成员方法能访问到当前类的成员变量的值。
  2. 在访问对象的属性时,可以使用.属性名[]操作符,但不能使用***操作符,因为它们是特殊的关键字参数。
  3. 在调用对象的方法时,需要在方法名后加上(),以便让Python知道是要调用这个方法。
  4. 类的属性和方法不能被其他类访问,它们是私有的。如果需要让其他类访问,可以使用@classmethod@staticmethod装饰器进行封装。

语法结构如下:

# 定义类
class 类名:
    类属性 = 值    
    def 方法名(self, 形参1, 形参2, ...):
        方法体... 

# 实例化对象
对象名 = 类名()

# 为对象属性赋值
对象.属性名 = "——"

示例:定义了一个名为User的类,并在其中定义了一些方法。这个类包含两个属性:nameage,以及三个方法:say_hellousername__init__

实现原理:

  1. nameage是属性,它们没有访问控制,可以直接通过对象名访问。__init__方法是一个特殊的方法,它在对象创建时自动被调用。在这段代码中,__init__方法没有实现任何功能,只是一个空的方法定义。
  2. say_hello方法接收一个参数self,它是一个指向当前对象的引用。这个方法会在控制台上输出一条问候消息,其中包含对象的name属性。
  3. username方法接收两个参数:selfname。这个方法会在控制台上输出一条消息,其中包含对象的name和传入的name参数。
  4. 最后,代码创建了一个User类的实例user_1,并给它的nameage属性赋了值。然后调用了say_hellousername方法,并打印了对象的属性值。

注意事项:

  1. 在 Python 中,类名、方法名和变量名通常不需要全部大写,但如果你想在代码中使用关键字或避免与Python内置函数冲突,可以使用大写。
  2. 在 Python 中,通常使用self来表示当前对象,但在某些情况下,你可能需要使用其他变量来接收函数调用的参数。例如,在定义一个带有参数的__init__方法时,你可以使用args(一个元组)或kwargs(一个字典)来接收参数。
  3. 在 Python 中,通常不需要为方法定义访问控制,因为Python是一种动态类型的语言。但是,如果你希望方法是私有的,可以在方法名前加上_
# 定义类
class User:
    name: None
    age: None

    def say_hello(self):
        print(f"hello {self.name}")

    def username(self, name):
        print(f"{self.name}的网名是{name}")
        
user_1 = User()

user_1.name = "李逸飞"
user_1.age = 18

# 调用类方法
user_1.say_hello()               # 输出结果 hello 李逸飞
user_1.username("偶尔也有风")      # 输出结果 李逸飞的网名是偶尔也有风
print(f"姓名:{user_1.name} 年龄:{user_1.age}")     # 输出结果 姓名:李逸飞 年龄:18
  • 构造方法

语法:__init__(self, 形参1, 形参2, ...)

作用:

  1. 将传入参数自动传递给__init__方法使用
  2. 创建类对象的时候,自动执行

示例:创建一个名为User的类。这个类包含两个属性nameage,以及一个构造函数__init__。当创建一个新的User对象时,它会打印出姓名和年龄。

实现原理:

  1. 定义一个名为User的类。
  2. 在类中定义一个构造函数__init__,用于初始化对象的属性。
  3. 当创建一个新的User对象时,调用__init__函数,并将nameage作为参数传递。
  4. __init__函数中,使用print函数打印出对象的姓名和年龄。

用途: 这个类可以用于表示用户信息,当需要创建一个用户对象时,只需创建一个新的User实例并传入相应的姓名和年龄即可。

注意事项:

  1. 在 Python 中,通常使用小写字母和下划线作为变量和函数名的分隔符,以提高代码的可读性。例如,User类中的nameage属性,以及__init__方法。
  2. 在 Python 中,通常使用self关键字来表示类的实例。在构造函数__init__中,需要将nameage作为参数传递给self,以便在类的其他方法中使用这些属性。
class User:
    # 可省略
    # name: None
    # age: None

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(f"姓名:{self.name} 年龄:{self.age}")

user = User("李逸飞", 20)     # 输出结果 姓名:李逸飞 年龄:20
  • 常用类内置方法(魔术方法)
方法功能
__init__构造方法: 创建类对象时设置初始化行为
__str__实现类对象转字符串的行为
__lt__用于2个类对象进行小于或大于比较
__le__用于2个类对象进行小于等于或大于等于比较
__eq__用于2个类对象进行相等比较

示例:实现一个用户类(User)。该类包含三个属性(name、age),以及三个方法(构造方法、对象输出字符串、大小比较)。

实现原理:

  1. 构造方法__init__:这个方法在创建对象时自动调用,用于初始化对象的属性。在这个例子中,我们通过__init__方法接收一个name和一个age参数,并将它们分别赋值给对象的nameage属性。
  2. 对象输出字符串__str__:这个方法返回一个对象的字符串表示形式。在这个例子中,我们通过__str__方法返回一个包含对象属性值的字符串,格式为"姓名:{self.name} 年龄:{self.age}"
  3. 大小比较__lt____le__:这些方法用于实现对象之间的比较操作。在这个例子中,我们通过__lt__方法返回一个布尔值,表示当前对象是否小于传入的对象。同样,我们通过__le__方法返回一个布尔值,表示当前对象是否小于或等于传入的对象。
  4. 相等比较__eq__:这个方法用于实现对象之间的相等比较操作。在这个例子中,我们通过__eq__方法返回一个布尔值,表示当前对象是否与传入的对象相等。
  5. 注意事项:在Python中,这些特殊方法通常不需要手动调用,因为Python解释器会自动调用它们。但是,当你需要自定义这些方法时,请确保它们的名字遵循一定的命名约定,例如__str____lt__等。
class User:
    # 构造方法
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    # 对象输出字符串    
    def __str__(self):
        return f"姓名:{self.name} 年龄:{self.age}"
    
    # 大小比较
    def __lt__(self, other):
        return self.age < other.age

    def __le__(self, other):
        return self.age <= other.age

    def __eq__(self, other):
        return self.age == other.age

user_1 = User("寇文", 19)
user_2 = User("李逸飞", 20)

print(user_1)               # 输出结果 姓名:寇文 年龄:19
print(user_1 < user_2)      # 输出结果 True
print(user_1 <= user_2)     # 输出结果 True
print(user_1 == user_2)     # 输出结果 False
1.9.2 封装
  1. 将动物的身高、体重、外貌等封装为属性(类的成员变量)
  2. 将动物的吃饭、睡觉、跑步等封装为行为(类的成员方法)

私有成员(变量和方法):不对外开放-即类对象无法使用,但在类中可以使用-即提供仅内部可使用的属性和方法。 命名定义以__开头

示例:定义一个名为User的类,类中包含私有成员变量和私有成员方法,以及一个公开的is_adult方法。私有成员变量__basic_age和私有成员方法__un_adult都是被私有成员变量和私有成员方法装饰的。

实现原理:

  1. 使用双下划线(__)装饰符定义私有成员变量和私有成员方法,使它们成为私有成员,只能在类内部访问。
  2. 使用单下划线(_)装饰器定义公开方法,使它们成为公开的成员,可以在类外部访问。
  3. is_adult方法中,通过内部调用私有成员变量和私有成员方法来实现类的外部逻辑。

用途:

  1. 可以用来控制类的成员变量的访问权限,确保类的封装性。
  2. 可以使代码更加清晰,避免直接访问私有成员变量和私有成员方法。
  3. 可以在类的外部调用公开方法,实现特定的功能。

注意事项:

  1. 私有成员变量和私有成员方法的命名不能以双下划线开头,但可以以单下划线开头。
  2. 私有成员变量和私有成员方法的访问权限受制于类的构造方法,只有通过构造方法初始化后才能访问。
  3. 公开方法的访问权限受制于单下划线,即在类外部无法直接访问,只能在类内部通过构造方法初始化后调用。
class User:
    # 私有成员变量
    __basic_age = 18

    # 构造方法
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 私有成员方法
    def __un_adult(self):
        print("未成年...")

    # 公开方法
    def is_adult(self):
        # 内部调用私有成员变量
        if self.age >= self.__basic_age:
            print("成年了...")
        else:
            # 内部调用私有成员方法
            self.__un_adult()

user = User("李逸飞", 20)
user.is_adult()     # 输出结果 成年了...
1.9.3 继承

继承:一个类从另外一个类继承它的成员变量和成员方法(不含私有)

类名是一个字符串,用于标识这个类;父类名可以是多个,用逗号分隔,表示这个类可以继承多个父类;类内容体是一个字符串,用于定义这个类的属性和方法。在Python中,Class还支持多继承,即一个类可以继承多个父类。但是,需要注意继承的顺序。如果有多个父类,那么先继承的优先级更高,后继承的会覆盖先继承的。

语法结构:

# 单继承
class 类名(父类名):
    类内容体...
    
# 多继承
class 类名(父类名1, 父类名2, ...):
    类内容体...    

示例:动物和人类的类,以及狗和猫的类。

实现原理:使用多继承(Multiple Inheritance),即一个类可以有多个父类。在这个例子中,动物类(Animal)和人类类(Person)都有跑(run)的方法,而狗类(Dog)和猫类(Cat)分别继承动物类和人类类。在实例化Cat类时,由于猫类同时继承了动物类和人类类,所以会先继承Animal类的方法run,然后覆盖为Person类的方法run。所以输出结果为:[动物] 西西 跑…。

注意事项:

  1. 构造方法(init方法)是类的特殊方法,当创建类实例时,会自动调用该方法。在这段代码中,每个类的构造方法都没有重写(即没有自定义 init 方法),所以默认调用父类的init方法。
  2. Python中,子类继承父类时,会先继承前面的父类,所以Cat类中的run方法会覆盖Animal类中的run方法。
  3. Python是动态语言, interpreter-driven language, 即解释器驱动语言。这意味着在运行时,解释器会根据代码中的执行顺序来调用方法。在这段代码中,由于Python是解释器驱动语言,所以输出结果是按照代码中的顺序执行的。
class Animal:
    name: None
    def run(self):
        print(f"[动物] {self.name} 跑...")

class Person:
    name: None
    def run(self):
        print(f"[人类] {self.name} 跑...")

class Dog(Animal):    # Dog 继承 Animal 类
    # 占位语句,用于补充语法完整性
    pass

class Cat(Animal, Person):    # Cat 继承 Animal 和 Person 类
    pass

cat = Cat()
cat.name = "西西"
cat.run()       # 输出结果 [动物] 西西 跑...
  • 方法的重写

重写:对父类的成员属性或方法重新定义。

语法:子类中定义同名的成员属性或方法即可。

方式1:

  1. 访问成员变量:使用类名访问成员变量。

  2. 调用成员方法:使用类名调用成员方法,后面跟一个括号,括号内传入所需的参数。

方式2:

  1. 访问成员变量:使用 super() 函数访问父类中的成员变量。super().成员变量

  2. 调用成员方法:使用 super() 函数调用父类中的成员方法。super().成员方法()

super() 函数可以自动追踪调用方法的类的父类,直到找到指定的父类为止。

示例:实现的继承和多态示例。

实现原理:将子类Pig继承自父类Animal,然后重写了父类Animalrun方法。在运行时,通过调用不同的方式来调用父类Animalrun方法,展示了多态的效果。

注意事项:

  1. 代码中使用了super()函数来调用父类方法。在Python 3中,所有类的方法都默认为继承自真子类(即广义上的“超类”)。因此,当一个类继承自多个类时,super()函数可以确保正确地调用父类的方法。
  2. 在Python中,实例化类时,会自动调用构造方法(如果存在)。因此,当实例化Pig类时,会自动调用构造方法,从而初始化对象的属性。
class Animal:
    name = "大狗熊"
    def run(self):
        print(f"[动物] {self.name}说: '熊就该有个熊样'")

class Pig(Animal):
    name = "熊大"
    # 重写父类方法
    def run(self):
        print(f"{Animal.name}")     # 大狗熊
        # 调用父类方式 1
        Animal.run(self)            # [动物] 熊大说: '熊就该有个熊样'
        print(f"{super().name}")    # 大狗熊
        # 调用父类方式 2
        super().run()               # [动物] 熊大说: '熊就该有个熊样'

pig = Pig()
pig.run()
1.9.4 多态

多态(polymorphism)是指同一个操作在不同的类中可以有不同的实现。在面向对象编程中,多态性的体现主要是通过继承和重写(覆盖)父类的方法来实现。当子类覆盖或重写父类的方法时,我们就获得了多态。这样可以使得同一个对象根据不同的情况执行不同的操作,提高了代码的可扩展性和灵活性。比如定义一个函数(方法),通过类型注解声明需要父类对象,但实际使用时传入子类对象工作。

  • 抽象类(接口)

抽象方法:没有具体实现的方法体(pass)

抽象类:包含抽象方法的类

作用:用于顶层设计(设计标准),以便子类做具体实现。 – 对子类的一种软性约束,要求子类必须复写(实现)父类的一些方法

示例:首先,我们定义了一个抽象类Animal,其中包含一个抽象方法say。这个抽象方法在子类中被实现。然后,我们定义了两个具体的子类DogCat,分别实现Animal类中的say方法。

在主程序中,我们定义了一个名为say的函数,它接受一个Animal类型的参数。在这个函数中,我们直接调用了传入的animal对象的say方法。这样,我们就可以根据传入的animal对象的不同,执行不同的say方法(取决于子类的实现)。

在测试代码中,我们创建了两个具体的子类实例dogcat,分别传入到say函数中,分别执行不同的say方法(打印不同的音频)。这充分表现了多态的特性:根据传入的对象不同,执行不同的操作。

# 定义抽象类(也可以叫接口) -- 含有抽象方法的类叫抽象类
class Animal:
    # 方法体为空实现(pass)的叫抽象方法
    def say(self):
        pass

class Dog(Animal):
    def say(self):
        print("汪~ 汪~ 汪~")

class Cat(Animal):
    def say(self):
        print("喵~ 喵~ 喵~")

# 多态
# 抽象的父类设计(设计标准)
def say(animal: Animal):
    animal.say()
    
# 具体的子类实现(实现标准)
dog = Dog()
say(dog)    # 汪~ 汪~ 汪~
cat = Cat()
say(cat)    # 喵~ 喵~ 喵~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>