【python】python面向对象——类的使用

关于什么是面向对象及相关的一些概念不在本篇的说明范围,以下是关于python中类的使用方式的说明。

目录

一,简单类的定义及使用

1. 定义只包含方法的类

2. 创建类的对象

3. 定义一个类并创建对象示例

二,方法中的self参数

三,python提供的一些与类创建/销毁相关的内置方法

1. 初始化 __init__()

2. 对象销毁前调用 __del__()

3. 自定义对象的打印内容 __str()__

四, 类的私有属性和方法

五,继承

1. 继承简单介绍

 2. 单继承和多继承

■ 方法搜索顺序 MRO(Method Resolution Order)

3. 新式类与经典类

4. 方法重写 override

■ 覆盖父类方法

■ 扩展父类方法

六,多态

七,类属性和类方法

1. 类属性

2. 类方法

3. 类中的静态方法


一,简单类的定义及使用

1. 定义只包含方法的类

语法格式

class 类名:
    
    def 方法1(self,参数列表):
        pass

    def 方法2(self,参数列表:
        pass

注意:

类名的命名规则要符合大驼峰命名法:每个单词的首字母大写;

类中的方法定义时,第一个参数必须是self;

2. 创建类的对象

语法格式

对象变量 = 类名()

3. 定义一个类并创建对象示例

定义一个类Rectangle;

定义两个方法,length()表示计算周长,area()表示计算面积;

不定义属性;

class Rectangle:

    def length(self):
        print("计算周长")

    def area(self):
        print("计算面积")

if __name__ == '__main__':
    rect = Rectangle()
    rect.length()
    rect.area()

显示结果:

计算周长
计算面积

二,方法中的self参数

方法中的self参数类似C++类中的this。类封装的方法内部,self表示当前调用方法的对象自己。

  • 对象调用方法时,不需要传递self参数;
  • 在方法内部,可以使用self.访问对象的属性或其它的方法;

如上示例,调整需要在一个函数cal()中计算面积和周长,那么可以把代码调整为:

class Rectangle:

    def length(self):
        print("计算周长")

    def area(self):
        print("计算面积")

    def cal(self):
        self.area()
        self.length()

if __name__ == '__main__':
    rect = Rectangle()
    rect.cal()

显示结果:

计算面积
计算周长

三,python提供的一些与类创建/销毁相关的内置方法

python提供了以下内置函数,在创建类对象或销毁时会被自动调用。

方法名类型作用
__new__方法用来创建对象,会被自动调用
__init__方法

对象初始化操作,会被自动调用

__del__方法对象从内存中销毁前操作,会被自动调用
__str__方法返回对象描述信息(字符串类型),可以使用print函数输出
  • 使用类名()创建对象时,python首先调用 __new__方法为对象分配空间,__new__会返回对象的引用;
  • python获得对象的引用后,将引用作为第一个参数,传递给__init__方法,为对象的属性设置初始值;
  • print(对象名)如果想打印自定义的内容,调用__str__()方法;
  • 当一个对象被从内存中销毁前,会自动调用 __del__() 方法。

__init__和__del__的应用场景:

  • __init__可以改造初始化方法,可以让创建对象更加灵活。
  • __del__ 如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__方法;

1. 新建对象 __new__()

使用 类名() 的形式创建对象时,python首先会调用new方法为对象分配空间,new方法是object基类提供的内置静态方法,主要的作用有:

  1. 在内存中为对象分配空间;
  2. 返回对象的引用;

python获得new返回的对象的引用后,将引用作为第一个参数,传递给__init__方法。

重写__new__方法的代码是固定的:

  • 重写的__new__()方法最后一定返回 return supre().__new__(cls) 

需要注意:__new__是一个静态方法,在调用时需要主动传递cls参数。

示例:单例模式设计时,用到该方法。

class MusicPlayer(object):

    instance = None

    def __new__(cls,*args,**kwargs):
        if cls.instance is None:
            cls.instance = super().__new__(cls)
        return cls.instance
    
    def __init__(self):
        print("初始化音乐播放器")

if __name__ == '__main__':
    player = MusicPlayer()
    player1 = MusicPlayer()
    print(player)
    print(player1)

显示结果:

初始化音乐播放器
初始化音乐播放器
<__main__.MusicPlayer object at 0x000001D5A65C4220>
<__main__.MusicPlayer object at 0x000001D5A65C4220>

2. 初始化 __init__()

当创建对象时,会自动执行一下操作:

  1. 创建对象(new)——为对象在内存中分配空间;
  2. 初始化方法(init)——为对象的属性设置初始值;

初始化方法就是__init__方法,__init__是对象的内置方法。

该函数不需要外部调用,创建对象时会自动创建。

个人理解 __new__+__init__()函数类似C++中类的构造函数。

专门用来定义一个类的属性。可以在__init__方法内部使用 self.属性名 = 属性的初始值 来定义属性。定义属性之后,创建的对象都拥有该属性。

如下示例:

class Rectangle:

    def __init__(self):
        print("初始化")
        self.width = 4

if __name__ == '__main__':
    rect = Rectangle()
    rect1 = Rectangle()
    print(rect.width)
    print(rect1.width)

显示结果:

初始化
初始化
4
4

■ 初始化的同时设置初始值

如果希望创建对象的同时,就设置对象的属性,可以改造__init__方法

  1. 把希望设置的属性值,定义成 __init__ 的参数
  2. 在方法内部使用self.属性 = 形参 接收外部传递的参数
  3. 在创建对象时,使用类名(属性1,属性2,...)调用

个人理解 这种方式类似于 创建一个有参数列表的构造函数。

使用示例:

class Rectangle:
    def __init__(self,width,height):
        print("初始化")
        self.width = width
        self.height = height

    def length(self):
        print("计算周长")
        return (self.width + self.height)*2

    def area(self):
        print("计算面积")
        return self.width*self.height


if __name__ == '__main__':
    rect = Rectangle(3,5)
    rect1 = Rectangle(6,7)
    print(rect.length())
    print(rect.area())
    print(rect1.length())
    print(rect1.area())

显示结果:

初始化
初始化
计算周长
16
计算面积
15
计算周长
26
计算面积
42

3. 对象销毁前调用 __del__()

当一个对象被从内存中销毁前,会自动调用__del__方法。

对象的生命周期:

  • 一个对象从创建,生命周期开始;
  • 一个对象的 __del__ 方法一旦被调用,生命周期结束;
  • 在对象的生命周期内,可以访问对象属性,或者让对象调用方法

使用示例:

class Rectangle:
    def __init__(self,width,height):
        print("初始化")
        self.width = width
        self.height = height

    def length(self):
        print("计算周长")
        return (self.width + self.height)*2

    def area(self):
        print("计算面积")
        return self.width*self.height

    def __del__(self):
        print("结束")


if __name__ == '__main__':
    rect = Rectangle(3,5)
    print(rect.length())
    del rect  # del关键字删除变量
    print(rect.area())
    
    print("="*50)

    

运行时会提示 NameError: name 'rect' is not defined,这是因为在打印面积前已经把rect对象删除了。

4. 自定义对象的打印内容 __str()__

python中,如果使用print(对象),默认情况下,会输出这个对象是由哪个类创建的,以及在内存中的地址,如下:

class Rectangle:
    def __init__(self):
        print("初始化")

    def __del__(self):
        print("结束")


if __name__ == '__main__':
    rect = Rectangle()
    print(rect)  # 输出 <__main__.Rectangle object at 0x0000025E54966BB0>

如果在开发中,希望使用 print(对象)时,能够打印自定义的内容,可以使用 __str__ 方法。但是需要注意的是 __str__ 方法必须返回一个字符串。

示例:

class Rectangle:

    def __init__(self):
        print("初始化")

    def __str__(self):
        return "这是一个矩形类"

    def __del__(self):
        print("结束")


if __name__ == '__main__':
    rect = Rectangle()
    print(rect)  # 输出 这是一个矩形类

四, 类的私有属性和方法

在定义属性或方法时,在属性名或者方法名前加两个下划线,就表示该方法或属性是私有属性或方法。如定义 self.__name,__name就是私有属性;如self.__get_length(),__get_length()就是私有方法。

class Rectangle:

    def __init__(self):
        print("初始化")
        self.__name = '矩形'

    def __str__(self):
        return "这是一个矩形类"
    
    def __length(self):
        print('计算矩形周长')

    def __del__(self):
        print("结束")


if __name__ == '__main__':
    rect = Rectangle()
    print(rect.__name)
    print(rect.__length())

运行到 print(rect.__name) 时,会报错:AttributeError: 'Rectangle' object has no attribute '__name',因为__name是私有属性,外部是不能访问的,所以会提示没有__name属性。

■ 要在外部访问私有属性和私有方法,可以使用  对象._类名__属性/方法名的方式访问,但不推荐使用该种方法。

class Rectangle:

    def __init__(self):
        print("初始化")
        self.__name = '矩形'

    def __str__(self):
        return "这是一个矩形类"
    
    def __length(self):
        print('计算矩形周长')

    def __del__(self):
        print("结束")


if __name__ == '__main__':
    rect = Rectangle()
    rect._Rectangle__length()

显示结果:

初始化
计算矩形周长
结束

五,继承

面向对象的三大特性:封装、继承、多态。

  • 封装:上面讲到的类就实现了封装的特性,根据 职责 把 属性和方法 封装到一个抽象的类中。
  • 继承:本节是关于继承的内容,继承可以实现代码的重用,相同的代码不需要重复编写。
  • 多态:下一节是关于多态的描述,多态增加代码的灵活度,不同的对象调用相同的方法,产生不同的执行结果。

1. 继承简单介绍

简单来说,继承是:子类拥有父类的所有方法和属性。如下列的类,不使用继承时,每个类中都有相同的方法名;使用继承后,子类可以使用父类的方法。

  • 子类继承自父类,可以直接使用父类中已经封装的方法;
  • 子类中可以根据职责,封装子类特有的属性和方法;
  • 继承可以传递,子类拥有父类及父类的父类中封装的所有属性和方法;

使用继承的情况下,CAnimal是CDog和CCat的父类,CDog和CCat是CAnimal的子类,CDog和CCat从CAnimal类继承;父类又称为基类,子类又称为派生类。

继承使用示例:

# 基类
class CAnimal:  
    def __init__(self):
        print("父类:Animal 初始化")
    
    def eat(self):
        print("eating...")
    
    def drink(self):
        print("drinking...")

# 派生类
class CMouse(CAnimal):
    def __init__(self):
        print("子类:CDog 初始化")
    
    def steal(self):
        print("OMG,the mouse is steal rice.")

# 派生类
class CCat(CAnimal):  
    def __init__(self):
        print("子类:CCat 初始化")
    
    def catch(self):
        print("The cat is watching the mouse and prepare catch it.")

if __name__ == "__main__":
    ani = CAnimal()
    jerry_mouse = CMouse()
    tom_cat = CCat()
    jerry_mouse.eat()  # 使用的是基类中的eat()方法
    jerry_mouse.drink()  # 使用的是基类中的drink()方法
    tom_cat.catch()

显示结果:

父类:Animal 初始化
子类:CDog 初始化
子类:CCat 初始化
eating...
drinking...
The cat is watching the mouse and prepare catch it.

■ 需要注意的是:

子类不能访问父类的私有属性和方法,父类的私有属性和方法仅可以父类的对象或方法访问

 2. 单继承和多继承

根据基类的个数可以分为单继承和多继承。只继承自一个基类称为单继承,继承自多个基类称为多继承。

单继承和多继承的语法格式

# 单继承
class 类名(父类名):
    pass

# 多继承
class 类名(父类名1,父类名2,...):
    pass

多继承时需要注意:应该避免不同的父类中存在同名的方法或属性

■ 方法搜索顺序 MRO(Method Resolution Order)

python针对类提供了内置的属性 __mro__,可以查看方法的搜索顺序,主要用于在多继承时判断方法、属性的调用路径。

  • 如果在当前类中找到方法,就直接执行,不再搜索;
  • 如果没有找到,查找下一个类中是否又对应的方法,如果找到,直接执行,不再搜索;
  • 如果找到最后一个类还没有找到对应的方法,那么程序报错;

示例:

class CAnimal:
    def __init__(self):
        print("父类:Animal 初始化")


class CMouse(CAnimal):
    def __init__(self):
        print("子类:CDog 初始化")


class CCat(CAnimal):
    def __init__(self):
        print("子类:CCat 初始化")


if __name__ == "__main__":
    ani = CAnimal()
    jerry_mouse = CMouse()
    tom_cat = CCat()

    tom_cat.catch()

显示结果:首先查找当前类CCat,然后查找基类CAnimal,最后查找object类。

(<class '__main__.CCat'>, <class '__main__.CAnimal'>, <class 'object'>)

3. 新式类与经典类

上面的查找结果中最后查找的是object类。

  • 新式类:以object为基类的类,推荐使用;
  • 经典类:不以object为基类的类,不推荐使用;

object类是python为所有对象提供的基类,提供有一些内置的属性和方法(可以使用dir()查看提供的内置属性和方法)。

  • 在python3.x中定义类时,如果没有指定父类,会默认使用object作为该类的基类;
  • 在python2.x中定义类时,如果没有指定父类,不会以obeject作为基类;

所以建议在创建类时,如果没有父类,使用统一继承自object.

class 类名(object):
    pass

4. 方法重写 override

通常情况下,子类拥有父类的所有方法和属性,可以直接使用父类中已经封装好的方法,不需要再次开发。但是当父类的方法实现不能满足子类需求时,可以对方法进行重写。

重写有两种情况:

  • 覆盖 父类的方法
  • 对父类方法进行扩展

■ 覆盖父类方法

当父类的方法实现和子类的方法实现完全不同,可以使用覆盖的方式,在子类中重新编写父类的方法的实现。覆盖的具体实现方式就是在子类中重新实现一个和父类同名的方法,在重写之后,运行时,子类只会调用子类中重写的方法,不会再调用父类封装的方法。

覆盖示例:

class CAnimal:
    def __init__(self):
        print("父类:Animal 初始化")
    
    def eat(self):
        print("eating...")

class CMouse(CAnimal):
    def __init__(self):
        print("子类:CDog 初始化")
    
    def eat(self):
        print("咯吱咯吱的吃")

class CCat(CAnimal):
    def __init__(self):
        print("子类:CCat 初始化")
    
    def eat(self):
        print("优雅的吃")

if __name__ == "__main__":
    ani = CAnimal()
    jerry_mouse = CMouse()
    tom_cat = CCat()
    ani.eat()
    jerry_mouse.eat()
    tom_cat.eat()

显示结果:

父类:Animal 初始化
子类:CDog 初始化
子类:CCat 初始化
eating...
咯吱咯吱的吃
优雅的吃

■ 扩展父类方法

如果子类的方法实现中包含父类的方法实现,或者说父类原本封装的方法实现是子类方法的一部分,此时,就可以使用扩展的方式,

  • 在子类中重写父类的方法,
  • 在需要父类实现的位置使用super().父类方法来调用父类方法的执行,
  • 其它位置编写子类特有的代码实现

super是python的一个特殊类;

super()标识使用super类创建一个对象;

常用的使用场景是在重写父类方法时,调用父类中封装的方法实现;

  • 在python2.x中如果需要调用父类的方法,还可以使用 父类名.方法(self) 的方式,但不推荐使用,因为一旦父类发生变化,方法调用位置的类名就需要修改。

使用示例:

class CAnimal:
    def __init__(self):
        print("父类:Animal 初始化")
    
    def eat(self):
        print("eating...")

class CMouse(CAnimal):
    def __init__(self):
        print("子类:CDog 初始化")
    
    def eat(self):
        super().eat()
        print("咯吱咯吱的吃")

class CCat(CAnimal):
    def __init__(self):
        print("子类:CCat 初始化")
    
    def eat(self):
        super().eat()
        print("优雅的吃")

if __name__ == "__main__":
    ani = CAnimal()
    jerry_mouse = CMouse()
    tom_cat = CCat()
    ani.eat()
    jerry_mouse.eat()
    tom_cat.eat()

显示结果:

父类:Animal 初始化
子类:CDog 初始化
子类:CCat 初始化
eating...
eating...
咯吱咯吱的吃
eating...
优雅的吃

六,多态

多态增加了代码的灵活性,不同的子类对象调用相同的父类方法,产生不同的执行结果。多态以继承和重写父类方法为前提,不会影响到类的内部设计。

如上,CDog是CHuskie和CXiaoTianQuan的基类,在子类中重写了父类的play()方法,那么不同的子类对象调用play()方法时,产生的结果也不同。

示例:

class CDog(object):
    def __init__(self,name):
        self.name = name

    def play(self):
        print(f"{self.name} 自己在玩耍")

class CXiaoTianQuan(CDog):
    def play(self):
        print("%s 在追沉香" % self.name)

class CHuskie(CDog):
    def play(self):
        print(" %s 在拆家" % self.name)

class CPerson(object):
    def __init__(self,name):
        self.name = name
    
    def play_with(self,dog:CDog):
        print("%s想和%s一起玩耍,但是,"% (self.name,dog.name))
        dog.play()

if __name__ == "__main__":
    xiaohei = CXiaoTianQuan("哮天犬")
    erha = CHuskie("哈士奇")
    lihua = CPerson('李华')
    lihua.play_with(xiaohei)
    lihua.play_with(erha)
    

 显示结果,在CPerson类中的play_with()方法中不关注是什么类型的狗对象,直接调用play()方法;程序执行时,传入不同的实参,执行不同的结果。

李华想和哮天犬一起玩耍,但是,
哮天犬 在追沉香
李华想和哈士奇一起玩耍,但是,
 哈士奇 在拆家

七,类属性和类方法

在python中,一切皆对象!!

  • class AA:AA定义的类属于类对象
  • obj1=AA():obj属于实例对象

在python中,类是一个特殊的对象——类对象。程序运行时,类会被加载到内存中,类对象在内存中只有一份,使用一个类可以创建出很多个对象实例。

类对象可以拥有自己的属性和方法:类属性、类方法。通过 类名. 的方式访问类属性或者调用类方法。该类的所有对象共享类属性和类方法。

1. 类属性

类属性是给类对象定义的属性,通常用来记录与这个类相关的特性,不会记录具体对象的特征。使用赋值语句在class关键字下方可以定义类属性,如:

class 类名(object):
    count=0  # count就是定义的类属性

如下示例,定义一个工具类,其定义如下:

类型变量说明
类属性count记录该类创建的对象的个数
实例属性name每一个对象都有一个name属性,展示实例对象本身
class Tools(object):
    # 定义类属性count,记录创建的Tools实例对象的个数
    count = 0
    def __init__(self,name):
        # 定义对象属性
        self.name = name

        # 访问类属性,每创建一个对象count就加1
        Tools.count+=1
    
if __name__ == "__main__":
    tool1 = Tools('钳子')
    tool2 = Tools('扳子')
    tool3 = Tools('剪子')

    print(f'tool1 name = {tool1.name}')  # 输出 tool1 name = 钳子
    print(f'tool2 name = {tool2.name}')  # 输出 tool2 name = 扳子
    print(f'tool3 name = {tool3.name}')  # 输出 tool3 name = 剪子
    print(Tools.count)  # 输出 3

类属性获取是向上查找机制,首先在对象内部查找对象属性,如果没有找到就会向上寻找类属性。

2. 类方法

类方法是针对类对象定义的方法,在类方法内部可以直接访问类属性或者调用其它的类方法

类方法语法格式

@classmethod
def 类方法名(cls):
    pass

说明:

  • 类方法需要用修饰器 @classmethod 标识,表示这是一个类方法;
  • 类方法的第一个参数应该是cls:
    • 由哪一个类调用的方法,方法内的cls就是哪一个类的引用;
    • cls参数和实例方法的self参数类似;
    • 也可以使用其它名字,建议使用cls;
  • 通过 类名. 调用类方法,调用方法时,不需要传递cls参数;
  • 类方法内部,可以通过 cls. 访问类属性或调用其它的类方法;

示例,对如上Tools改造,增加类方法 show_tool_count()打印该类创建的对象的个数。

class Tools(object):

    # 定义类属性count,记录创建的Tools实例对象的个数
    count = 0

    # 定义类方法
    @classmethod
    def show_tool_count(cls):
        print(f'Tools类创建的对象有{cls.count}个')


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

        # 访问类属性,每创建一个对象count就加1
        Tools.count+=1
    
if __name__ == "__main__":
    tool1 = Tools('钳子')
    tool2 = Tools('扳子')
    tool3 = Tools('剪子')

    print(f'tool1 name = {tool1.name}')  # 输出 tool1 name = 钳子
    print(f'tool2 name = {tool2.name}')  # 输出 tool2 name = 扳子
    print(f'tool3 name = {tool3.name}')  # 输出 tool3 name = 剪子
    print(Tools.count)  # 输出 3
    Tools.show_tool_count()  # 输出 Tools类创建的对象有3个

3. 类中的静态方法

开发时,如果要封装一个类的方法,该方法不需要访问 实例属性、实例方法、类属性、类方法,那么就可以把这个方法封装成一个静态方法。

静态方法语法格式

@staticmethod
def 静态方法名():
    pass

说明:

  • 静态方法需要用修饰器 @staticmethod 标识,表示这是一个静态方法;
  • 通过 类名. 调用静态方法;

示例:

class Tools(object):
    # 定义类属性count,记录创建的Tools实例对象的个数
    count = 0
    
    @classmethod
    def show_tool_count(cls):
        print(f'Tools类创建的对象有{cls.count}个')

    @staticmethod
    def run():
        print('开始工作了')


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

        # 访问类属性,每创建一个对象count就加1
        Tools.count+=1
    
if __name__ == "__main__":
    
    Tools.run()
    
    tool1 = Tools('钳子')
    tool2 = Tools('扳子')
    tool3 = Tools('剪子')

    print(f'tool1 name = {tool1.name}')  # 输出 tool1 name = 钳子
    print(f'tool2 name = {tool2.name}')  # 输出 tool2 name = 扳子
    print(f'tool3 name = {tool3.name}')  # 输出 tool3 name = 剪子
    print(Tools.count)  # 输出 3
    Tools.show_tool_count()  # 输出 Tools类创建的对象有3个

显示结果:

开始工作了
tool1 name = 钳子
tool2 name = 扳子
tool3 name = 剪子
3
Tools类创建的对象有3个

使用示例:

定义一个学生类Student,记录分数。类的属性和方法如下:

代码:

class Student(object):

    # 定义类属性
    top_score = 0
    top_name = ''

    # 定义类方法
    @classmethod
    def show_top_score(cls):
        print(f'{cls.top_name}是第一名,分数是{cls.top_score}')
    
    # 定义静态方法
    @staticmethod
    def show_help():
        print('学生成绩管理')

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

    # 定义实例方法
    def record(self,score):
        if Student.top_score < score:
            Student.top_score = score
            Student.top_name = self.name

if __name__ == '__main__':
    # 打印帮助信息
    Student.show_help()
    
    # 创建实例对象
    lihua = Student('李华')
    zhangsan = Student('张三')
    lisi = Student('李四')

    # 记录学生分数
    lihua.record(52.3)
    zhangsan.record(56.3)
    lisi.record(60)

    # 打印最高分和他的名字
    Student.show_top_score()

显示结果:

学生成绩管理
李四是第一名,分数是60

全文参考专栏面向对象部分。

  • 4
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值