python 类与对象的详细用法


当前版本:

  • Python 3.8.4

  • 简介

        类是一种自定义的数据类型,用于创建对象。类定义了一个对象的属性(也称为数据成员)和方法(也称为成员函数)。对象是类的实例化,是具体的数据实体,可以访问类中定义的属性和方法。

            

    文章目录如下

    1. 类与对象的作用

    2. 基础用法

    2.1. 定义类

    2.2. 定义属性

    2.3. 定义方法

    3. 类的继承

    3.1. 单继承

    3.2. 多继承

    3.3. 连续继承

    3.4. 多态性

    4. 类的装饰器

    4.1. 类方法 @classmethod

    4.2. 静态方法 @staticmethod

    4.3. 类方法与静态方法的区别

    5. 应用场景

    5.1. 汽车案例

    5.2. 电视案例


            

    1. 类与对象的作用

    类与对象的作用是将数据和相关的操作封装在一起,以创建可重用和模块化的代码。

  • 类(Class):一个类是一种数据结构,用于定义对象的属性和行为。
  • 类包含属性(变量)、方法(通用方法)
  • 对象(Object):一个对象是类的实例化(即根据类创建的具体对象)。
  • 调用类的方法
  •         

    类与对象本身比较抽象,我们将其带入到现实中作比喻:

    我们每个人类需要做很多事,这些事情存在不同的类型,也有大事小事之分,所以需要通过不同的类来区分不同的事情。

    【类1】我名下有一辆汽车,那么需要对汽车封装一个类:

  • 类的属性:汽车的颜色、品牌、型号等。
  • 类的方法:汽车的功能 加速、刹车、转弯等。
  • 对象:将汽车实例化,通过这个对象去调用,而不是每次繁琐的去执行那些方法。
  • 【类2】我有一部手机,那么再对这个手机封装一个类:

  • 类的属性:手机的品牌、型号、尺寸等。
  • 类的方法:手机的功能 拍照、打电话、发送短信等。
  • 对象:将手机实例化,也是通过对象去调用这个类
  • 类的主要作用就是将大事分成不同的类,大事中的细节通过方法去定义。

            

    大致流程如下:

            

    类与函数的区别

        "函数" 的使用方法与 "类" 实际上是差不多的,一般在封装一个独立的方法时,直接使用函数就行。如果是编写一个小程序,那么这个小程序会存在大量关联性的方法,这时候函数就显得有点无助。比如封装一个购物车,购物车肯定包含 加入购物车、删除商品、结算商品,计算价格等方法,这些方法都具有一定的关联性。像一部分变量是公用的,使用函数的话只能将这些变量全局化,但全局化又会面临另一个问题,整个程序的变量非常多,肯定存在大量相同的变量(这种情况难以处理)。如果使用类来处理,类中的变量对这个类里面的方法是公用的,对其他类是隔离的,所以能够避免变量名相同的劣势。

        类也支持继承的概念,可以通过继承从其他类获取属性和方法,并进行扩展和定制。并且可以共享类的属性,即类的所有实例对象可以访问和修改类的属性。所以大功能使用类,独立功能使用函数。

            

    2. 基础用法

    2.1. 定义类

    python 使用关键字 class 来定义一个类,比如:

    class 类名(继承对象):
        代码块
  • 类名:自定义的名称,通常以大写字母开头,按照惯例采用驼峰命名法。例如:MyClass。
  • 继承对象:python允许继承另一个类,比如超类(object)或者其他类名、库等。
  • 代码块:自定义的一段代码,只作用于这个类,必须使用缩进来定义。
  •         

    定义一个最简单的类:

    # 定义一个类,继承超类(提供一些通用的方法和属性)
    class MyClass(object):    # 在python3中默认继承object,可以为空
        # 在类中编写代码
        print("这是一个类!")
    
    # 创建一个类的实例
    mc = MyClass()

            

    2.2. 定义属性

    属性实际上就是在类中定义的变量,作用于类中。属性包含:类属性、私有类属性、实例属性、方法属性。

  • 类属性:整个类共享,静态方法无法共享。
  • 私有类属性:整个类共享,静态方法无法共享,外部无法调用。
  • 实例属性:所有实例共享,类方法、静态方法无法共享。
  • 方法属性:定义在某个方法中的属性,仅该方法使用。
  • 当这些属性名相同时,在方法中调用属性的优先级为:

    方法属性 > 实例属性 > 类属性

            

    举一个简单的例子

    # 定义一个类
    class MyClass(object):
        x = 10    # 定义的属性
    
        def func(self):    # 定义的方法
            print(f"这是类中的一个方法, 属性x为:{self.x}")  # 共享类属性
    
    # 创建一个类的实例
    mc = MyClass()
    mc.func()    # 调用类中的方法

            

    注意:self 是一个特殊的参数,用于引用当前对象(实例)。能够使得类的方法可以与对象实例进行交互,访问和修改对象的属性,并调用其他方法。若不使用self参数,在方法中无法直接访问和操作对象实例的属性和方法。如下:

    class MyClass(object):
        def __init__(x):
            x = x
    
        def func():
            print(x)
    
    mc = MyClass(10)    # 实例化对象,传入一个参数
    mc.func()  # 调用其中一个方法

    无法实例化,并且 func 方法也无法调用属性x 

            

    常见3种定义属性的方法

    【方案一】直接定义属性(类属性)

    # 定义一个类
    class MyClass(object):
        x = 10    # 定义一个共享属性
    
        def func1(self):    # 定义方法1
            print(f"方法1中引用属性x:{self.x}")
    
        def func2(self):    # 定义方法2
            print(f"方法2中引用属性x:{self.x}")
    
    mc = MyClass()    # 创建一个类的实例
    mc.func1()    # 调用方法1
    mc.func2()    # 调用方法2

    在类中直接定义的属性具有类级别的作用域,可以被类的所有实例对象所共享。但如果需要为不同的实例对象定义不同的属性值,就需要在每个实例对象的构造方法中进行定义,或者使用其他方式来实现。

            

    【方案二】通过构造方法 __init__() 来定义

    类中直接定义的属性是在类的层级下直接定义的,而__init__方法是类的构造方法,在实例化对象时被调用来初始化对象的属性。

    class MyClass(object):
        def __init__(self, x):  # 初始化一个实例属性
            self.x = x  # 使用self.x引用变量x
    
    mc = MyClass(10)    # 实例化时需要传入一个参数(自定义)
  • x:是方法中的一个局部变量,只在 __init__ 中有效,用于接收传入的参数值。
  • self.x:是实例属性,与x是相互独立的,用来引用x变量,在后续作用到整个类。
  • 比如:使用两个属性都接收变量 x

    class MyClass(object):
        def __init__(self, x):  # 初始化一个实例属性
            self.x = x  # 使用self.x引用变量x
            self.y = x  # 使用self.y引用变量x
    
        def func(self):
            print(f"变量self.x的值为:{self.x}")
            print(f"变量self.y的值为:{self.y}")
    
    mc = MyClass(10)    # 实例化对象,传入一个参数
    mc.func()    # 调用方法

    这个变量同样支持默认值,写法与函数一样

    def __init__(self, x=默认值):

    如果需要多个参数则使用逗号分开

    def __init__(self, x=默认值, y, z):

            

    【方案三】通过自定义方法动态定义属性

    上面两种属性都可以作用到整个类全局,第3种方案就是在方法中定义属性

    class MyClass(object):
        def func(self, x):    # 封装一个方法
            self.x = x
            print(self.x)
    
    mc = MyClass()    # 实例化对象
    mc.func(10)  # 调用方法需要传入一个参数

    在方法中定义属性与第2种方案是类似了,同样可以定义多个参数和默认值

    def func(self, x=默认值, y, z):

            

    2.3. 定义方法

    方法实际上就是在类中定义的函数,作用于类中。

    class MyClass(object):    # 定义一个类
        def func(self, x):    # 封装一个方法
            pass

    方法间通过关键字 self 来相互调用

    class MyClass(object):    # 定义一个类
        def func1(self):      # 封装第一个方法
            print("我是方法1")
    
        def func2(self):    # 封装第二个方法
            print("我是方法2")
            self.func1()    # 调用方法1
    
    MyClass().func2()    # 调用方法2
    

            

    上面的方法都是共有方法,而在 python 中还有一个私有方法(需要以双下划线开头)

    class MyClass(object):
        def __func1(self):  # 私有方法(以双下划线开头)
            print('这是一个私有方法')
    
        def func2(self):    # 公共方法
            print('这是一个普通方法')
            self.__func1()  # 内部调用这个私有方法

    代码中定义了2个方法,一个私有,一个共有。私有方法在外部无法调用

    mc = MyClass()   # 实例化这个对象
    mc.__func1()     # 调用这个私有方法

    这种方法只能在内部调用,代码 func2 中调用了 __func1,所以直接在外部调用 func2

    mc = MyClass()   # 实例化这个对象
    mc.func2()       # 调用这个公共方法

            

    3. 类的继承

    继承是通过创建一个子类来继承另一个已经定义的类的属性和方法。流程图如下:

  • 爷爷类没有继承任何其他类,所以他只享有自己的属性和方法;
  • 爸爸类和叔叔类都继承了爷爷类,所以他们可以共享爷爷类的属性和方法,但爸爸和叔叔的属性、方法互不干涉;
  • 儿子类继承了爸爸类,他可以共享爸爸类的属性和方法;由于爸爸继承了爷爷,所以儿子也共享爷爷的属性和方法;
  • 侄儿类同时继承叔叔和爸爸,所以他可以同时共享叔叔类和爸爸类的属性和方法;由于叔叔和爸爸也继承了爷爷,所以侄儿同样继承爷爷的属性和方法;
  • 孙子类继承了儿子和侄儿,所以孙子可以共享儿子类和侄儿类的属性和方法;由于儿子和侄儿也继承了爸爸、叔叔、爷爷,所以孙子可以共享所有类的属性和方法。
  • 每个类可以使用的属性和方法如下:

    爷爷:本身
    爸爸:本身、爷爷
    叔叔:本身、爷爷
    儿子:本身、爸爸、爷爷
    侄儿:本身、爸爸、叔叔、爷爷
    孙子:本身、儿子、侄儿、爸爸、叔叔、爷爷

            

    3.1. 单继承

    定义 A、B两个类,子类可以继承父类中的所有属性和方法

    class A(object):    # 定义一个类
        x = 10
        def func_a(self):
            print("我是A类中的方法")
    
    class B(A):    # 继承A类(包括全部属性和方法)
        def func_b(self):
            print(f"我是B类中的方法,属性x为:{self.x}")
    
    B().func_b()    # 调用B类中的方法
    B().func_a()    # 调用B类中继承的方法

            

    子类重写】如果子类与父类的方法名或属性名相同,调用子类时默认使用子类的方法或属性

    class A(object):    # 定义A类
        a = 1
        def func1(self):
            print('A类')
    
    class B(A):    # 继承A类
        a = 2      # 属性与A类相同
        def func1(self):    # 方法也与A类相同
            print(f"B类,属性a为:{self.a}")  # 使用当前类的属性
    
    B().func1()    # 调用子类

            

    3.2. 多继承

    当继承多个类时,这些类的属性名或方法名相同时,默认使用继承的第一个父类

    class A(object):
        a = 1    # 父类定义属性a
    
    class B(object):
        a = 2    # 父类定义属性a
    
    class C(A, B):  # 先继承A,后继承B
        def func1(self):
            print(f"属性a的值为:{self.a}")  # 默认调用A的属性
    
    C().func1() # 调用子类C

            

    3.3. 连续继承

    当出现连续继承时,最小的子类可以使用上面全部父类的属性和方法

    class A(object):    # 定义第1个类
        def func1(self):
            print('A类')
    
    class B(A):     # 定义第2个类,继承第1个
        def func2(self):
            print('B类')
    
    class C(B):     # 定义第3个类,继承第2个
        pass
    
    C().func1()     # 使用第3个类的实例调用第1个类的方法
    C().func2()     # 使用第3个类的实例调用第2个类的方法

            

    当连续继承太多,相同的方法名也比较多,我们也可以使用 super 函数去调用上一个父类的方法

    class A(object):    # 定义第1个类
        def func(self): # 方法名与第2个类相同
            print('A类')
    
    class B(A):         # 定义第2个类
        def func(self): # 方法名与第1个类相同
            print('B类')
    
    class C(B):         # 定义第3个类
        def func(self): # 方法名与第1个和第2个类相同
            super().func()  # 调用上一级类方法,不需要指定类名
    
    C().func()      # 调用子类的方法

    注意:super 只能调用上一级,调用其他可以直接指定类名

    A.func(self)
    B.func(self)

            

    它们的继承的关系可以通过 __mro__ 查看

    class A(object):    # 继承超类
        pass
    
    class B(A):     # 继承第1个类
        pass
    
    class C(B):     # 继承第2个类
        pass
    
    print(C.__mro__)  # 查看C类的继承关系

            

    3.4. 多态性

    多态性是通过继承和方法重写来实现的,允许不同的对象对相同的方法作出不同的响应,从而增加了代码的灵活性和可扩展性。

    封装4个类,1个作为父类,另外三个作为子类,它们的方法名称是一样的

    class Animal:    # 父类
        '''动物类'''
        name = '[3号]'
    
    class Dog(Animal):    # 继承动物类
        '''小狗类'''
        def sound(self):
            print(f"{self.name} 汪汪汪!")
    
    class Cat(Animal):    # 继承动物类
        '''小猫类'''
        def sound(self):
            print(f"{self.name} 喵喵喵!")
    
    class Cow(Animal):    # 继承动物类
        '''小牛类'''
        def sound(self):
            print(f"{self.name} 哞哞哞!")

    4个类中的方法名完全一致,这时我们来封装一个函数,用来调用这些类(因为方法名是一样的,调用一个即可)

    def make_sound(x):    # 需要传入一个x参数
    '''x参数就是类名,通过传入的类名去调用方法'''
        x.sound()    # sound方法是4个类中的方法

    最后我们来实例化这几个动物类

    dog = Dog()    # 实例化小狗类
    cat = Cat()    # 实例化小猫类
    cow = Cow()    # 实例化小牛类

            

    向函数中传入小狗类

    make_sound(dog)    # 调用函数,传参为小狗类

            

    向函数中传入小猫类

    make_sound(cat)    # 调用函数,传参为小猫类

            

    向函数中传入小牛类

    make_sound(cow)    # 调用函数,传参为小牛类

            

    4. 类的装饰器

    装饰器是一个用于修改函数或类的行为的特殊函数或类,它可以在不修改原始代码的情况下,添加额外的功能或行为,提供了一种简洁的方式来修改或增强函数或类的功能。

    4.1. 类方法 @classmethod

    类方法是作用于整个类而不是实例的方法。它们可以访问和修改类的属性,提供额外的构造方法,实现多态行为,封装和管理类相关的功能,与类级别数据进行交互。类方法使用 @classmethod 装饰器进行标识,第一个参数通常被命名为 cls,表示类本身。

    我们在类中定义一个实例方法和一个类方法,看一下两者之间的调用区别:

    class MyClass(object):
        def func1(self):    # 封装一个实例方法
            print('我是实例方法')
    
        @classmethod        # 特殊标识符
        def func2(cls):     # 封装一个类方法
            print('我是类方法')

    在调用时,实例方法需要初始化实例,而类方法不需要

    m = MyClass()
    m.func1()    # 调用实例方法,需要实例化
    
    MyClass.func2()  # 调用类方法,不需要实例化

            

    为什么调用类方法不需要实例化?

    因为声明的类方法就是类的本身,形参 cls 就相当于类名,所以本身调用本身不需要实例化。实例化是用来调用实例方法的

            

    类方法使用类属性时,以 cls 方式调用

    class MyClass(object):
        x = 1    # 定义一个属性
    
        @classmethod
        def func(cls):  # 使用cls
            print(f'公共类属性a为{cls.x}')
    
    MyClass.func()    # 调用类方法

            

    注意:类方法无法调用实例属性,仅适用于类级别上的操作。

    class MyClass(object):
        def __init__(self):
            self.x = 1    # 定义实例属性
    
        @classmethod
        def func(cls):
            print(f'实例属性a为{cls.x}')  # 无法调用
    
    MyClass.func()    # 调用类方法

            

    4.2. 静态方法 @staticmethod

    静态方法既不需要传递类对象,也不需要传递实例对象(没有形参)。静态方法可以独立于类的任何实例或类级别而存在,因此它们是最为模块化、灵活和可维护的方法类型之一。

    特点

    1. 静态方法不能访问类的变量。
    2. 静态方法不需要特殊的参数,但仍然可以使用默认参数。
    3. 静态方法往往更容易维护和测试,因为它们不会改变且不依赖于类的状态。
    4. 由于静态方法可以独立于类而存在,它们也可以很方便地跨越模块、包和应用程序。

    静态方法的调用与类方法类似,不需要实例化对象

    class MyClass():
        @staticmethod   # 特殊标识符
        def func(n):    # 定义一个静态方法
            print(f"我是一个静态方法,属性n为:{n}")  # 调用属性不需要关键字
    
    MyClass.func(10)  # 调用静态方法,传入一个参数

            

    由于静态方法本身是独立的,所以它无法调用实例属性和实例方法。当然,实例方法也无法调用静态方法。

    class MyClass(object):
        def func1(self):    # 定义一个实例方法
            print('实例方法')
    
        @staticmethod
        def func2():        # 定义一个静态方法
            func1()  # 直接调用实例方法(无法调用)
            MyClass().func1()  # 通过类调用实例方法(无法调用)
    
    MyClass.func()    # 调用静态方法

            

    总结

  • 静态方法不需要实例化对象
  • 静态方法是独立存在的,无法调用其他实例方法、实例属性,也无法被继承。
  •         

    4.3. 类方法与静态方法的区别

    1、从参数的角度来看

  • 类方法第一个参数默认 cls 代表类的本身,非实例本身。
  • 静态方法不需要额外的形参,可以把它当成一个独立函数。
  •         

    2、从属性角度来看

  • 类方法可以访问类中的其他属性。
  • 静态方法不可以访问类中的其他属性。
  •         

    3、从继承角度来看

  • 类方法可以被继承和重写。
  • 静态方法无法被继承。
  •         

    4、从用途角度来看

  • 类方法通常用于操作与类有关的属性和方法,适用于通用代码。
  • 静态方法通常用于实现可维护性高的代码,适用于独立和复用的代码块。
  •         

    5. 应用场景

    5.1. 汽车案例

    定义一个汽车的类,具有品牌、型号和颜色等属性,封装汽车详细信息的方法、汽车数量的方法、检查颜色合法性的方法。

    # 封装一个表示汽车的类
    class Car(object):
        car_count = 0   # 调用类的计数器
    
        def __init__(self, brand, model, color):
            self.brand = brand  # 汽车品牌
            self.model = model  # 汽车型号
            self.color = color  # 汽车颜色
    
            Car.car_count += 1  # 每次创建一个Car对象,计数增加1
    
        def get_car_info(self):
            '''封装一个获取汽车的详细信息的实例方法'''
            return f"品牌: {self.brand}, 型号: {self.model}, 颜色: {self.color}"
    
        @classmethod
        def get_total_count(cls):
            '''封装一个获取总的汽车数量类方法'''
            return cls.car_count
    
        @staticmethod
        def is_valid_color(color):
            '''封装一个用于检查是否是合法的颜色的静态方法'''
            valid_colors = ["red", "blue", "green"]
            return color.lower() in valid_colors

    我们来调用这个类

    # 创建两个Car对象,并传入品牌、型号和颜色
    car1 = Car("Tesla", "Model 3", "red")
    car2 = Car("Toyota", "Camry", "blue")
    
    # 调用实例方法,获取汽车的详细信息
    print(car1.get_car_info())
    print(car2.get_car_info())
    
    # 调用类方法,获取总的汽车数量
    print("汽车数量为: {}台".format(Car.get_total_count()))
    
    # 调用静态方法,检查颜色是否合法
    print(Car.is_valid_color("red"))
    print(Car.is_valid_color("yellow"))

            

    5.2. 电视案例

    定义一个电视的类,封装一些打开电视、关闭电视、切换频道的方法

    # 定义一个电视类
    class TV:
        def __init__(self, brand, size):
            '''初始化方法,用于创建TV对象时的初始设置'''
            self.brand = brand  # 品牌
            self.size = size    # 尺寸
            self.is_on = False  # 电视开关(默认关闭)
            self.volume = 50    # 音量(默认50)
            self.channel = 1    # 电视频道(默认1)
    
        def turn_on(self):
            '''封装一个实例方法,打开电视'''
            self.is_on = True
    
        def turn_off(self):
            '''封装一个实例方法,关闭电视'''
            self.is_on = False
    
        def change_channel(self, channel):
            '''封装一个实例方法,切换电视频道'''
            self.channel = channel
    
        def volume_up(self):
            '''封装一个实例方法,增加音量'''
            if self.volume < 100:
                self.volume += 1
    
        def volume_down(self):
            '''封装一个实例方法,减小音量'''
            if self.volume > 0:
                self.volume -= 1

    我们来调用这个类

    # 创建一个TV对象
    tv = TV("Sony", 55)
    
    # 打开电视
    tv.turn_on()
    
    # 切换到第5个频道
    tv.change_channel(5)
    
    # 增加音量
    tv.volume_up()
    
    # 打印电视信息
    print(f"品牌: {tv.brand}, 尺寸: {tv.size}, 频道: {tv.channel}, 音量: {tv.volume}")
    
    # 关闭电视
    tv.turn_off()

    作者:一只勤劳的耗子

    物联沃分享整理
    物联沃-IOTWORD物联网 » python 类与对象的详细用法

    发表回复