Python类和对象

文章目录

    • 面向对象
      • 类的定义和使用
      • 成员方法的定义
      • 构造方法
      • 其他内置方法
        • \_\_str\_\_ 字符串方法
        • \_\_lt\_\_ 小于符号比较
        • \_\_le\_\_ 小于等于比较 \_\_eq\_\_ 等于比较
      • 封装
        • 私有成员
      • 继承
        • 复写
          • 方法1
          • 方法2
      • 类型注解
        • 变量类型注解
        • 函数(方发)类型注解
        • Union类型
      • 多态
        • 抽象类(接口)

面向对象

在C++面向过程与面向对象的章节里,我们介绍过面向过程关注的是做一件事情的需要的步骤有哪些,通过一系列函数之间的调用配合来实现解决问题,面向对象关注的是解决这一个问题参与的对象,依靠对象之间的交互来完成问题的解决

对于Python或者说很多的编程语言来说,面向对象已经是不可或缺的一部分了

对于Python来说,这个对象实际上指的是类(class),类中可以包含成员变量,成员方法,这都是属于同一个类的内容

类的定义和使用

我们可以用类去封装一系列的变量和函数,基于类去构造出对象来使用

类的定义语法

class 类名称:
    成员变量
    
    成员方法

这里的成员方法其实就是函数,但是在类的内部,我们统称为方法

类的创建(实例化)语法

对象名称 = 类名称()

在类定义的时候,实际上是不消耗内存空间的,因为并没有构建类的对象,他就像是一个设计图纸,并未造出实体,一旦我们创建类,或者是实例化一个类,就是产生了一个类的对象,就是消耗了内存的

例如,我们构建一个简单的学生类,定义一个方法让他做自我介绍

class student:
    name = None
    age = None
    gender = None
    
    def greeting(self):
        print(f"我是{self.name},我是{self.gender}生,我今年{self.age}了")

这里出现了self关键字,稍后我们会介绍

对于这个类的使用,示例如下

stu = student()
stu.name = '莫蒂'
stu.age = 14
stu.gender = '男'
stu.greeting()

类似于对模块中的变量和方法的使用,我们也可以使用其中的方法

成员方法的定义

def 方法名(self,形参,...):
    方法体

这里的self实际上是用来表示对象自身的意思,如果我们想在方法内调用类中的成员,就必须要通过self调用,在调用方法传参的时候,self是可以忽略的

构造方法

在上面我们可以看到,每次初始化成员变量的时候就要调用一个赋值语句,不够优雅,于是我们引入构造方法

例如

class student:
    name = None
    age = None
    gender = None
    
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    
    def greeting(self):
        print(f"我是{self.name},我是{self.gender}生,我今年{self.age}了")

这样创建完构造方法之后,实际上在上面的变量声明就可以省略了,而这个构造方法会在构造对象时自动调用,自动传参

stu = student('莫蒂', 14, '男')

这样对类进行构造就十分方便了

其他内置方法

上文学习的__init__ 构造方法,是Python类内置的方法之一

这些内置的类方法,各自有各自特殊的功能,这些内置方法我们称之为:魔术方法

__str__ 字符串方法

这个方法实际上是一种对类转换成字符串的方法,一种主要的用途就是可以自定义输出的内容,直接调用str就会出现内存地址

例如

class student:
    name = None
    age = None
    gender = None
    
    def __init__(self,name,age,gender):
        self.name = name
        self.age = age
        self.gender = gender
    
stu = student('莫蒂', 14, '男')
print(stu)
print(str(stu))

class student:
    name = None
    age = None
    gender = None

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

    def __str__(self):
        return f"name={self.name} gender={self.gender} age={self.age}"
stu = student('莫蒂', 14, '男')
print(stu)
print(str(stu))

__lt__ 小于符号比较

直接对两个类的比较大小是会出错的,我们可以构建这个魔术方法对类的大小进行定义,而且这个方法可以同时实现大于符号和小于符号的两种比较,类似于C++中的运算符重载

例如我们可以按照年龄大小进行比较

class student:
    name = None
    age = None
    gender = None
    
    def __lt__(self,other):
        return self.age < other.age
    
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
     

    def __str__(self):
        return f"name={self.name} gender={self.gender} age={self.age}"
__le__ 小于等于比较 __eq__ 等于比较

这里和小于符合几乎一样,不过多赘述了

封装

面向对象的简单理解就是基于模板(类)去创建实体(对象),再通过对象进行功能的开发

对于面向对象来说,他有三大主要的特性,即为封装,继承,多态

封装的概念我们没有细讲,但是其实已经使用到了

我们将各种属性(成员变量),行为(成员方法)封装到一个类中,形成了一个类似于包裹的东西,可以调用其中的部分变量和方法

在实际中,我们构建出一个类,有些内容是不希望使用者(用户)能够访问到的,这也引申出了一个概念,私有成员

私有成员

我们可以用特定的形式来约定私有成员变量和私有成员方法

例如

class student:
    name = None
    age = None
    gender = None
    
    __id = None # 私有成员变量
    
	def __func():
        

私有成员变量:变量名以__开头(2个下划线)

私有成员方法:方法名以__开头(2个下划线)

这样我们在类外就无法调用和使用私有成员了,但在成员方法内部依然可以使用

继承

之前的类的定义和使用似乎已经完美了,但是如果我们想要对类进行更新维护,添加新的功能,又不能失去原有的功能,似乎只有两种方法,一是复制粘贴重新写一个类,二是基于原来的类进行修改,但是又难以保证新写出的代码一定是完美的,尤其是当代码量及其庞大的时候,为了解决这个问题我们引入了继承这个概念

继承从字面意思来理解,就是从原有的类中,添加新的功能,那我们也称这个原有的类为父类或者基类

语法:

class 类名(父类名):
    类内容

例如我们想构建一个手机类,再对这个手机类进行更新维护

class MyPhone_1:
    ID = None
    Producer = None
    
    def CallBy4G(self):
        print('4G')
        
class MyPhone_2(MyPhone_1):
    FaceID = True
    
    def CallBy5G:
        print('5G')

这样我们就从MyPhone_1继承出了MyPhone_2

继承也分为单继承和多继承,也就是说,可以从不止一个父类那里继承来他的成员变量和成员方法,需要注意的是,私有成员不会被继承,在多继承中,如果声明有相同的变量名称,那么按照从左到右的顺序继承,先继承的保留,后继承的则不会保留

复写

我们说子类是对父类的更新,因此如果我们对父类的成员不满意,则可以对其进行复写,例如

class MyPhone_1:
    ID = None
    Producer = None
    
    def CallBy4G(self):
        print('4G')
        
class MyPhone_2(MyPhone_1):
    FaceID = True
    ID = 999
    
    def CallBy4G:
        print('4G+')
    
    def CallBy5G:
        print('5G')

一旦复写父类成员,那么类对象调用成员的时候,就会调用复写后的新成员

如果需要使用被复写的父类的成员,需要特殊的调用方式

方法1

父类名.成员

class MyPhone_1:
    ID = None
    Producer = None
    
    def CallBy4G(self):
        print('4G')
        
class MyPhone_2(MyPhone_1):
    FaceID = True
    ID = 999
    
    def CallByOld4G:
        MyPhone_1.CallBy4G()
    
    def CallBy4G:
        print('4G+')
    
    def CallBy5G:
        print('5G')
方法2

super().成员

class MyPhone_1:
    ID = None
    Producer = None
    
    def CallBy4G(self):
        print('4G')
        
class MyPhone_2(MyPhone_1):
    FaceID = True
    ID = 999
    
    def CallByOld4G:
        super().CallBy4G()
    
    def CallBy4G:
        print('4G+')
    
    def CallBy5G:
        print('5G')

需要注意的是,只能在子类内调用父类的同名成员,如果使用对象调用则会直接调用复写的成员

类型注解

当我们在使用诸如PyCharm的工具中他会自动弹出代码补全,而我们我们自己写的类或函数则不会,这是为什么,实际上是因为我们自己写的变量,PyCharm不确定这个对象是什么类型,因此不会弹出代码补全

在Python3.5之后,都支持类型注解,可以提供类型的显示说明,方面代码提示

他可以作用于变量的类型注解,函数(方法)形参、返回值的类型注解

变量类型注解

语法如下 变量: 类型

age: int = 14
name: str = '莫蒂' # 变量的类型注解

class student:
    pass

stu: student = student() # 类对象类型注解

list1: list = [1,2,3] # 容器类型注解

list2: list[int] = [1,2,3] # 容器详细注解
tuple1: tuple[str,int,bool] = ('a',1,True) # 元组需要将每一个元素都标记出来
dict1: dict[str,int] = {'a':1,'b':2} # 字典需要两个类型,一个代表key,一个代表val

在注释中进行类型注解也是符合语法的 # type: 类型

例如

age = 14 # type: int

需要注意的是,类型注解是提示性信息,并不是决定性的,即使错误也并不会进行报错

例如

age: int = 'aa'
函数(方发)类型注解

语法

def func(形参名: 类型, 形参名: 类型) -> 返回值类型:
    pass

例如

def add(x: int, y: int) -> int:
    return x+y
Union类型

这个实际上是用于定义联合类型注解的,例如

from typing import Union

dict2: dict[str, Union[str,int]] = {'a':1,'b':'b'}

这里表示的就是可能出现的类型,也很简单,在变量,函数注解中都可以使用

多态

多态指的是多种状态,即完成某个行为(调用某个函数),使用不同的对象会得到不同的状态(结果)

可以理解为,一个高级的ifelse语句

例如

class Animal:
    def speak(self):
        pass

class Dog(Animal): # 继承
    def speak(self):
        print('wang wang')

class Cat(Animal): # 继承
    def speak(self):
        print('miao miao')
        
def MakeNoise(anm: Animal): # 类型注解
    anm.speak()
    
d = Dog()
c = Cat()
    
MakeNoise(d)
MakeNoise(c)

这个过程中Animal是父类,并没有实际的定义实际的内容,只是声明,他有两个子类Dog和Cat,他们是对功能进行实际的定义,最后通过第三方来调用,以达到传入不同的类能产生不同的结果

抽象类(接口)

在上面的不少代码中,我们都使用到了pass关键字,这里表示空实现,也可以对比理解为变量的None,这种写法就叫做抽象类(接口),与之对应的还有抽象方法

由此我们就可以只定义各类方法的标准,具体实现交给别的类,例如我们规定动物(父类)可以走,跑,跳,叫,吃(方法),但是不规定他们以何种形式,以何种工具实现这种功能,直到具体到某动物(子类)的时候,我们才能知道他的实现方式,例如猫是四条腿走路,鸵鸟是两条腿走路

我们的Python类和对象就到此为止了,感谢各位的支持,如果你发现文章中有任何不严谨或者需要补充的部分,欢迎在评论区指出

你可能感兴趣的:(Python,python,开发语言,类和对象)