【Python入门】详解类的基础概念

文章目录

  • 前言
  • 一、创建并使用类
  • 二、使用类和实例
  • 三、继承
  • 四、导入类
  • 五、Python标准库
  • 六、类的编码风格
  • 总结

  • 前言

    面向对象编程是现在使用最多的软件编写方法之一。面向对象中最核心的就是类(class),类用来描述现实世界的事物和情景,我们基于这些类来创建实例对象。类就是这些对象共有的属性和行为的抽象。举个例子——鸟类,鸟类共有的属性如一对翅膀,双爪。共有的行为如飞翔,进食。这样当我们创建一个鸟类的实例对象时,就自动拥有这些属性和行为。

    在Python中,类是一种用户定义的数据类型,使用class关键字定义。类定义了一组相关的属性(数据成员)和方法(成员函数),用于描述一类对象的共同特征和行为。通过实例化类,可以创建具有相同特性和行为的具体对象。类支持封装、继承和多态等面向对象编程的核心概念,有助于提高代码的复用性和可维护性。

    新手读者朋友暂且先不必关注什么是封装、继承、多态。后面慢慢的结合代码会理解。


    一、创建并使用类

    创建Student类

    我们先来创建一个Student类,文件名为 student.py

    class Student:
        def __init__(self, name, age):
            self.name = name
            self.age = age
    
        def student_info(self):
            return f"Student(name={self.name}, age={self.age})"
    

    1.类的定义

    class Student:
    

    根据这个可以总结出类的定义模板为:

    class ClassName
    

    2.构造函数

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

    这个 init()方法是构造函数方法,是Python中类的最基本的默认方法,只能是这个名字,不能自行修改,前后有两个下划线是为了和普通方法做区分。我们自己定义的方法当然也可以使用下划线,但是实在是没有必要,还会引起混淆,分不清Python默认方法还是自定义方法。每个类在实列化时都会自动调用的方法,进行一些初始化的操作,如属性赋值等。构造函数中为当前的实例对象设置两个属性name和age

  • self是一个指向实例本身的引用,它是每个实例方法的第一个参数。在这个例子中,self代表创建的Student对象
  • name和age是传递给构造函数的参数,用于初始化Student对象的属性。
  • self.name = name 和 self.age = age 分别将传入的name和age值存储为对象的属性。
  • 3.自定义方法

       def student_info(self):
           return f"Student(name={self.name}, age={self.age})"
    

    这个是我们自定义的方法,返回学生信息,比较简单。

    重点解释下这个 self 参数,每个方法默认第一个参数就是self,这个是必不可少的参数(除非你不想使用任何参数,那就不必传),并且必须位于其他形参的前面。Python调用_init_()方法时,会自动传入self参数。每个与实列对象相关联的方法,都会自动传递实参self,该实参是一个指向实例本身的引用,让实列能够访问实列中的属性和方法

    这个self可以替换成其他的,比如this,但是我们写代码时不要去替换。使用self来表示当前实例的引用是默认的规则,更改反而不利于他人的阅读理解,属于画蛇添足

    根据Student类创建一个实例

    我们再创建一个test.py文件,在其中引入Student类

    from student import Student
    
    my_student = Student(name='John', age=30)
    
    print(f"My name is {my_student.name}")
    
    print(f"My age is {my_student.age}")
    

    代码 **my_student = Student(‘John’, ‘Doe’)**就是Student类实例化的方式,我们传入了两个实参,分别对应方法 _init_(self, name, age)中的后两个参数,第一个self默认自动传入。 _init_()方法会自动返回一个实例,不需要显示的return

    1.访问属性

    如 my_student.name,my_student.age

    实例名.属性名
    

    2.调用方法

    from student import Student
    
    my_student = Student(name='John', age=30)
    
    student_info = my_student.student_info()
    
    print(student_info)
    

    如 my_student.student_info(),调用了student_info()方法

    实例名.方法名
    

    3.创建多个实例

    from student import Student
    
    my_student1 = Student(name='John', age=30)
    
    my_student2 = Student(name='Luke', age=25)
    
    student_info1 = my_student1.student_info()
    
    student_info2 = my_student2.student_info()
    
    print(student_info1)
    
    print(student_info2)
    

    代码中创建了两个实例对象,分别调用了各自的实例方法 student_info


    二、使用类和实例

    创建Car类

    class Car:
        """一次模拟汽车的简单尝试"""
    
        def __init__(self, make, model, year):
            """初始化描述汽车的属性"""
            self.make = make
            self.model = model
            self.year = year
    
        def get_descriptive_name(self):
            """返回整洁的描述性信息"""
            long_name = str(self.year) + ' ' + self.make + ' ' + self.model
            return long_name.title()
    
    
    my_new_car = Car('audi', 'a4', 2016)
    
    print(my_new_car.get_descriptive_name())
    
    

    下面是对这段代码的详细解释

    类定义

  • 类名:Car
  • 文档字符串:“一次模拟汽车的简单尝试”,用于描述类的功能。
  • 初始化方法 init

  • 参数:
  • self:代表类的实例,是每个方法的第一个参数。
  • make:汽车制造商的名字。
  • model:汽车型号。
  • year:汽车生产年份。
  • 功能:
  • 初始化汽车的三个属性:制造商、型号和生产年份。
  • 这些属性通过 self.make、self.model 和 self.year 存储在实例中。
  • 方法 get_descriptive_name

  • 功能:
  • 返回一个格式化的字符串,包含汽车的所有信息。
  • 字符串格式为:“YearMakeModel”,其中 Year 是四位数字的年份,Make 和 Model 分别是制造商和型号。
  • 使用 title() 方法将字符串中的单词首字母大写。
  • 创建实例并调用方法

  • 实例化:
  • 创建了一个 Car 类的实例 my_new_car,传入了 “audi”、“a4” 和 2016 作为参数。
  • 调用方法:
  • 通过 my_new_car.get_descriptive_name() 调用了 get_descriptive_name 方法。
  • 输出结果:“2016AudiA4”,其中每个单词的首字母都被大写了。
  • 给属性指定默认值

    类中的每个属性都必须有初始值,哪怕这个值是0或空字符串。在有些情况下,如设置默认值时,在方法_init_()内指定这种初始值是可行的;如果你对某个属性这样做了,就无需包含为它提供初始值的形参。

    class Car:
        def __init__(self, make, model, year):
            """初始化描述汽车的属性"""
            self.make = make
            self.model = model
            self.year = year
            # 设置初始值里程数 = 0
            self.odometer_reading = 0
    
        def get_descriptive_name(self):
            return str(self.year) + ' ' + self.make + ' ' + self.model
        def read_odometer(self):
            """打印一条指出汽车里程的消息"""
            print("This car has " + str(self.odometer_reading) + " miles on it.")
    
    
    my_new_car = Car('audi', 'a4', 2016)
    print(my_new_car.get_descriptive_name())
    #读取里程数
    my_new_car.read_odometer()
    
    


    汽车里程odometer_reading的属性被我们指定成0

    修改属性的值

    可以以三种不同的方式修改属性的值:

    1. 直接通过实例进行修改
    2. 通过方法进行设置
    3. 通过方法进行递增(增加特定的值)​

    1.直接通过实例进行修改

    #设置里程数
    my_new_car.odometer_reading = 30
    #读取里程数
    my_new_car.read_odometer()
    

    调用读取里程数之前,先通过实例修改了属性值

    2.通过方法修改属性的值

    我们在car.py的Car类下面增加一个方法 update_odometer,用于修改里程值。
    update_odometer()在修改属性前检查指定的读数是否合理。如果新指定的里程(mileage)大于或等于原来的里程(self.odometer_reading),就将里程表读数改为新指定的里程;否则就发出警告,指出不能将里程表往回拨。

        def update_odometer(self, mileage):
            """将里程表读数设置为指定的值"""
            if mileage >= self.odometer_reading:
                self.odometer_reading = mileage
            else:
                print("you can't roll back an odometer!")
    

    接着调用方法,设置属性

    #设置里程数
    my_new_car.update_odometer(46)
    #读取里程数
    my_new_car.read_odometer()
    

    控制台打印结果如下

    This car has 46 miles on it.
    

    3.通过方法让属性值递增
    有时,我们需要对属性值进行递增操作,而不是完全替换,再提供一个递增方法

        def increment_odometer(self, miles):
            """将里程表读数增加指定的量"""
            self.odometer_reading += miles
    

    递增调用的代码如下

    #增加里程数
    my_new_car.increment_odometer(10)
    #读取里程数
    my_new_car.read_odometer()
    
    #增加里程数
    my_new_car.increment_odometer(20)
    #读取里程数
    my_new_car.read_odometer()
    
    

    打印结果如下


    上述方法实际开发中,我们需要做好权限和风险控制,防止出现意外结果


    三、继承

    继承这个概念比较容易理解,并不是Python语言独有的,是面向对象编程思想的核心概念之一。从字面上来看,就是类属性和方法的复用,创建一个类继承另外一个类,那么这个类便拥有了继承那个类的属性和方法。创建的这个类叫子类,继承的类叫父类。子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法。

    子类中的__init__()方法

    class Car:
        def __init__(self, make, model, year):
            """初始化描述汽车的属性"""
            self.make = make
            self.model = model
            self.year = year
            # 设置初始值里程数 = 0
            self.odometer_reading = 0
    
        def get_descriptive_name(self):
            return str(self.year) + ' ' + self.make + ' ' + self.model
        def read_odometer(self):
            """打印一条指出汽车里程的消息"""
            print("This car has " + str(self.odometer_reading) + " miles on it.")
    
        def update_odometer(self, mileage):
            """将里程表读数设置为指定的值"""
            if mileage >= self.odometer_reading:
                self.odometer_reading = mileage
            else:
                print("you can't roll back an odometer!")
    
        def increment_odometer(self, miles):
            """将里程表读数增加指定的量"""
            self.odometer_reading += miles
    
    
    """
    继承
    """
    class ElectricCar(Car):
        """电动汽车的独特之处"""
        def __init__(self, make, model, year):
            """初始化父类的属性"""
            super().__init__(make, model, year)
    
    
    my_electric_car = ElectricCar('tesla', 'model s', 2016)
    print(my_electric_car.get_descriptive_name())
    

    class ElectricCar(Car)这种写法就是ElectricCar类继承自Car类,创建子类时,必须引入父类的代码。笔者这里是把父类和子类写在一个模块文件里了,如果父类是在一个单独的模块中,比如car.py,则需要引入,具体如下


    子类继承父类的所有属性和方法,代码运行结果如下

    重写父类中的方法

    对于父类的方法,只要它不符合子类模拟的实物的行为,都可对其进行重写。为此,可在子类中定义一个这样的方法,即它与要重写的父类方法同名。这样,Python将不会考虑这个父类方法,而只关注你在子类中定义的相应方法。

    父类代码如下

    class Car:
        def __init__(self, make, model, year):
            """初始化描述汽车的属性"""
            self.make = make
            self.model = model
            self.year = year
            # 设置初始值里程数 = 0
            self.odometer_reading = 0
    
        def fill_gas_tank(self):
            return "跑的里程数为:"+str(self.odometer_reading)
    

    在子类所在模块中定义一个同名的方法,要求参数个数、类型、顺序都要和父类保持一致。可选参数必须放在最后,子类可以省略可选参数。

    把实例用作属性

    在 Python 中,可以将一个类的实例作为另一个类的属性。这种做法允许你将不同类的对象组合在一起,形成更复杂的数据结构。

    假设我们有两个类:Address 和 Person。我们将创建一个 Address 实例,并将其作为 Person 类的一个属性。

    定义 Address 类

    class Address:
        def __init__(self, street, city, state, zip_code):
            self.street = street
            self.city = city
            self.state = state
            self.zip_code = zip_code
    
        def display_address(self):
            return f"{self.street}, {self.city}, {self.state} {self.zip_code}"
    

    定义 Person 类

    class Person:
        def __init__(self, name, age, address):
            self.name = name
            self.age = age
            self.address = address  # 将 Address 类的实例作为属性
    
        def display_person_info(self):
            return f"Name: {self.name}, Age: {self.age}, Address: {self.address.display_address()}"
    

    调用代码示例如下

    # 创建 Address 实例
    address = Address("123 Main St", "Anytown", "CA", "12345")
    
    # 创建 Person 实例,并传入 Address 实例作为属性
    person = Person("Alice", 30, address)
    
    # 显示 Person 的信息
    print(person.display_person_info())
    
    

    以上示例中创建了一个Address类的实例,并作为属性传递给Person类

    运行结果如下


    笔者把这些代码都放在了一个模块中


    四、导入类

    实际开发场景是复杂的,我们不可能把所有逻辑都写在一个模块中,更不能写在一个类中。我们要考虑可维护性、可复用性。这就要求我们要合理的划分模块,建立类。不过这种只有经过大量的实战项目,代码练习才能具备相当的经验。目前我们只需要掌握以下内容即可。

    导入单个类

    首先新建一个address.py文件,其中代码如下

    class Address:
        def __init__(self, street, city, state, zip_code):
            self.street = street
            self.city = city
            self.state = state
            self.zip_code = zip_code
    
        def display_address(self):
            return f"{self.street}, {self.city}, {self.state} {self.zip_code}"
    

    再新建一个my_address.py文件,在这个文件中引入address.py中的Address类,实例化是一个对象my_address ,打印地址信息

    from address import Address
    
    my_address = Address("123 Main St", "Any town", "CA", "12345")
    
    address_info = my_address.display_address()
    
    print(address_info)
    

    在一个模块中存储多个类

    先建立一个player_characters.py文件,在其中新建几个不同游戏玩家角色的类Warrior、Mage、Archer

    class Warrior:
        def __init__(self, name, strength=80, health=100):
            self.name = name
            self.strength = strength
            self.health = health
    
        def attack(self):
            return f"{self.name} attacks with a strength of {self.strength}!"
    
        def get_health(self):
            return f"{self.name}'s health is {self.health}."
    
    
    class Mage:
        def __init__(self, name, intelligence=70, mana=120):
            self.name = name
            self.intelligence = intelligence
            self.mana = mana
    
        def cast_spell(self):
            return f"{self.name} casts a spell with an intelligence of {self.intelligence}!"
    
        def get_mana(self):
            return f"{self.name}'s mana is {self.mana}."
    
    
    class Archer:
        def __init__(self, name, agility=90, arrows=50):
            self.name = name
            self.agility = agility
            self.arrows = arrows
    
        def shoot_arrow(self):
            if self.arrows > 0:
                self.arrows -= 1
                return f"{self.name} shoots an arrow! Arrows left: {self.arrows}"
            else:
                return f"{self.name} has no more arrows!"
    
        def get_arrows(self):
            return f"{self.name}'s arrows: {self.arrows}."
    
    

    从一个模块中导入多个类

    再新建一个game.py文件,导入上述player_characters.py文件中的三个类,分别实例化并调用其中的方法

    from player_characters import Warrior, Mage, Archer
    
    # 创建角色实例
    warrior = Warrior("Thorin")
    
    mage = Mage("Gandalf")
    
    archer = Archer("Pergolas")
    
    # 执行角色的动作
    print(warrior.attack())
    print(mage.cast_spell())
    print(archer.shoot_arrow())
    print(archer.shoot_arrow())
    
    # 查看角色的状态
    print(warrior.get_health())
    print(mage.get_mana())
    print(archer.get_arrows())
    

    导入整个模块

    笔者这里使用了别名module,不使用也可以,直接使用模块的名称player_characters调用其中的类进行实例化

    import player_characters as module
    
    # 创建角色实例
    warrior = module.Warrior("Thorin")
    
    mage = module.Mage("Gandalf")
    
    archer = module.Archer("Pergolas")
    
    # 执行角色的动作
    print(warrior.attack())
    print(mage.cast_spell())
    print(archer.shoot_arrow())
    print(archer.shoot_arrow())
    
    # 查看角色的状态
    print(warrior.get_health())
    print(mage.get_mana())
    print(archer.get_arrows())
    

    **导入模块中的所有类 **

    导入模块中的所有内容的语法如下,本质上没有区别

    from player_characters import *
    

    不推荐使用这种导入方式,原因有以下两点

  • 首先,如果只要看一下文件开头的import语句,就能清楚地知道程序使用了哪些类,将大有裨益;但这种导入方式没有明确地指出你使用了模块中的哪些类。这种导入方式还可能引发名称方面的困惑。如果你不小心导入了一个与程序文件中其他东西同名的类,将引发难以诊断的错误。这里之所以介绍这种导入方式,是因为虽然不推荐使用这种方式,但你可能会在别人编写的代码中见到它。
  • 需要从一个模块中导入很多类时,最好导入整个模块,并使用module_name.class_name语法来访问类。这样做时,虽然文件开头并没有列出用到的所有类,但你清楚地知道在程序的哪些地方使用了导入的模块;你还避免了导入模块中的每个类可能引发的名称冲突。
  • 导入模块中少量类使用如下语法

    from player_characters import Warrior, Mage, Archer
    

    导入模块中大量类使用如下语法

    import player_characters
    

    五、Python标准库

    Python标准库是一组模块,安装的Python都包含它。就是官方提供的工具类库,基础库,帮我们封装和实现好了许多复杂又常用的数据结构。

    新建一个birthday_calculator.py的文件,这是一个提供计算距离下一个生日天数的工具类,其中使用了datetime、random两个标准库的函数

    import datetime
    import random
    
    
    def days_until_birthday(birth_date):
        """
        计算距离下一个生日还有多少天。
        
        参数:
        birth_date (str): 生日日期,格式为 'YYYY-MM-DD'。
        
        返回:
        int: 距离下一个生日的天数。
        """
        today = datetime.date.today()
        next_birthday = datetime.datetime.strptime(birth_date, '%Y-%m-%d').date().replace(year=today.year)
        if next_birthday < today:
            next_birthday = next_birthday.replace(year=today.year + 1)
        return (next_birthday - today).days
    
    
    def get_random_wish():
        """
        随机选择一条祝福语句。
        
        返回:
        str: 祝福语句。
        """
        wishes = [
            "祝你生日快乐,愿你拥有美好的一年!",
            "希望你的每一个愿望都能实现!",
            "愿你的每一天都充满阳光和笑容!",
            "愿你新的一岁更加精彩!",
            "生日快乐,愿你健康平安!"
        ]
        return random.choice(wishes)
    

    再新建一个main.py文件,代码如下

    from birthday_calculator import days_until_birthday, get_random_wish
    
    
    def main():
        print("欢迎使用生日计算器!")
        birth_date = input("请输入你的生日(格式:YYYY-MM-DD): ")
        days_until = days_until_birthday(birth_date)
        print(f"距离你的下一个生日还有 {days_until} 天!")
        wish = get_random_wish()
        print(wish)
    
    
    if __name__ == "__main__":
        main()
    
    


    六、类的编码风格

    在Python中,类的编码风格通常遵循PEP 8规范,这是Python官方推荐的编码风格指南。以下是关于类定义的一些关键编码风格要点:

    1. 类名命名:

  • 类名应该使用CapWords(首字母大写)的命名方式,例如 MyClass。
  • 2. 方法名命名:

  • 方法名应该使用小写字母加上下划线 _ 的方式,例如 my_method。
  • 3. 空行:

  • 在类定义之间以及类的外部方法之间使用两个空行。
  • 在类的方法之间使用一个空行。
  • 4. 文档字符串:

  • 类和方法都应该有文档字符串,用三引号括起来,放在类或方法的第一行。
  • 文档字符串应简洁地描述类或方法的目的和功能。
  • 5. 私有成员:

  • 如果一个方法或属性不打算被外部使用,可以使用单下划线 _ 开头来标记为“私有”。
  • 如果是绝对不希望外部访问的,可以使用双下划线 __ 开头,但这不是强制性的。
  • 6. 继承:

  • 继承时,在括号内指定父类名,例如 class Child(Parent):。
  • 7. 初始化方法:

  • 类通常会有一个初始化方法 init,用于设置实例变量。
  • 8. 方法参数:

  • 第一个参数通常是 self,代表实例本身。
  • 其他参数按照逻辑顺序排列。
  • 9. 代码缩进:

  • 使用4个空格进行缩进,而不是制表符。
  • 简单示例代码如下,我们实际开发中需要遵循这些规则,不要擅自自创规则,使得代码可读性很差。

    class MyClass:
        """这是一个简单的类示例。"""
    
        def __init__(self, name):
            """初始化方法。
    
            参数:
                name (str): 实例的名字。
            """
            self.name = name
    
        def say_hello(self):
            """打印问候消息。"""
            print(f"Hello, {self.name}!")
    

    总结

    本文详细介绍了Python中有关类的创建和使用相关代码示例,介绍了标准库和代码风格的一些公共规则。通过练习,我们应该对类这个面向对象编程的核心概念有所领悟。

    作者:编程巫师

    物联沃分享整理
    物联沃-IOTWORD物联网 » 【Python入门】详解类的基础概念

    发表回复