python进阶——类(一)面向对象、类和实例

本文最后更新于:1 年前

(一)面向对象

1. 基本概念:

面向对象编程——Object Oriented Programming(OOP),是一种程序的设计思想,以对象作为程序的基本单元,在对象中封装数据和操作数据的函数。

和把程序作为一系列函数命令集合运行的面向过程不同,面向对象把函数继续分解为子函数,简化程序设计、降低系统复杂度。

对于众多的子函数,面向对象的程序设计把计算机程序视为一组对象的集合,不同的对象具有不同的属性和功能,这些子函数就被用在不同的对象之中。进而,程序的执行就是对象之间处理交互信息、完成自身功能的过程。

2. 使用技术:

下面简要说明面向对象的常用技术(后面会详细介绍):

  • :用于描述具有相同的属性和方法的对象的集合,定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

    类把数据和功能绑定在了一起,创建新类就是创建新的对象类型,从而以类为蓝图创建该类型的新的实例,也就是对象。

  • **实例化:**创建一个类的实例,类的具体对象,实例支持维持自身状态的属性以及修改自身状态的方法。

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

  • **方法:**在类中定义的函数,每个对象都能够使用。

    类似于c++中的成员函数

  • **类变量:**类变量是一个类的所有不同对象/实例共享的变量,在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外,且通常不作为实例变量使用。

    类似于c++中的静态变量,只有一份。

  • **实例变量:**类声明时的属性用变量表示,此类变量称为实例变量,实例变量就是一个用self修饰的变量。

    实例变量可以用于区分不同的实例,因此,实例变量每个实例都各自拥有,相互独立。

    类似于c++中的非静态数据成员

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

    和c++中的数据成员概念类似。

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

  • **继承:**即一个派生类继承基类的成员和方法。继承允许多级,例如A是B的基类,B仍可以是C的基类,此时A实际上也可以算是C的基类,继承可以形成由树组成的森林。

  • **方法重写:**如果从基类继承的方法不能满足派生类的需求,可以对其进行改写,这个过程叫方法的覆盖,也称为方法的重写。

3. Python的面向对象:

Python是一门面向对象的语言,这也是设计Python语言的初衷。

Python中提供了类机制,可以定义类,而且Python的类提供了面向对象编程的所有基本功能和标准特性:类的继承机制支持多个基类,派生类可以覆盖基类中的方法,类的方法可以调用基类中的同名方法,对象可以包含任意数量和类型的数据。

类机制也支持Python的动态特性:在运行时创建,创建后可以修改。

和其他编程语言相比,Python 的类只使用了很少的新语法和语义。

  • 如果了解c++类的概念,这里为了防止混淆进行一些说明(不了解的可以跳过):

    Python中的类成员通常为公有的(public),成员函数默认均为虚函数(virtual)。

    Python的方法函数在声明时有一个显式的参数代表本对象,在调用时隐式提供,类似this指针。

    和c++不同,Python中内置类型可以用作基类以供用户扩展。

    和c++相同,算术运算符、下标等具有特殊语法的内置运算符可以为类实例而重新定义(运算符重载)。

下面正式介绍Python的类。

(二)Python中的类和实例

面向对象最重要的概念就是类和实例,所以,Python中首先必须要知道的一点:**类是创建对象的模版,对象是类的实例。**类本身不占据内存空间,实例才会占据内存空间。

1. Python的类定义和实例化:

(1)简单定义:

Python中,类通过class关键字来定义,简单的类的语法格式为:

class ClassName[(object)]: <statement-1> . . . <statement-N>

ClassName是类名,类名的命名一般情况要求是名词,使用首字母大写。

object是继承的类名,如果没有则一律使用object代替,当然对于这种情况也可以不加()来表示继承关系。更详细的内容后文介绍

<statement-i>是定义/声明语句,后文介绍。

与函数定义一样,类定义必须先执行才能生效。

进入类定义后会创建新的局部命名空间和局部作用域,对局部变量的赋值和处理也都是在此命名空间之内。

下面简单定义一个没有属性和方法的类:

1
2
class My_class:
pass
(2)类对象:

在正常离开类定义后,会创建一个类对象。

类对象支持两种操作:属性引用和实例化。

  • 属性引用使用Python中的标准属性引用语法:obj.name,obj是对象,name是属性名。

    类对象创建时存在于类命名空间中的全部名称均为有效的属性名,包括数据属性和方法。

    可以通过属性名对类属性进行赋值来修改其值。

    属性引用后文介绍例子。

  • 实例化和Python中的函数调用类似,可以将类对象视为一个不带参数的函数,该函数的返回值就是类的一个实例,以此方式来完成类的实例化。

    实例化操作创建类的新实例,将此实例后的对象分配给某个变量,此时的对象是一个空对象。

    如果想要自定义实例化对象,使其带有一定的初始状态,需要使用类定义的特殊方法__init__(),后文会详细介绍。

    类实例化后才可以使用其属性和方法——其访问方式是通过上面的属性引用完成的。

简单的类对象实例化如下:

1
2
3
class My_class(object):
pass
my_obj=My_class()
(3)属性和方法:

上面创建的类显然是不完整的,需要为类设置属性和方法来定义类的功能,从而完善一个类。

注意,属性是数据属性,和方法一起都是属性引用的内容。

  • 属性:作为数据属性,其实际是类中的变量,包括了类属性和实例属性。

    属性的添加可以在类中实现,也可以对某个实例对象单独实现。

  • 方法:实际是类中的函数,包括了类方法和实例方法。

    类中的方法对应的函数和普通函数的最大区别就是,类方法必须多出一个参数,调用方法的实例对象必须作为第一个参数被传入其中,这是为了区别一个类中的多个对象,便于解释器查找是哪一个对象调用的方法。

    在类方法的定义中,额外的第一个参数名称一般默认为self,随后再根据需要定义参数——self代表的是类的实例,而不是类。

    而在调用时,第一个额外参数self是不能指定的,类方法会隐性传入这个参数——所以,对于类方法,至少有一个参数,而如果类方法有n个参数需要传入,实际调用参数数量为n-1。

    方法定义中的类属性需要指定类名.类属性来使用,实例属性需要指定第一个参数名.实例属性名来使用。

  • 初始属性和类的构造方法:

    对于类属性,是在类中直接定义的,但是对于实例属性,由于其对每个实例来说是不同的,又需要统一名称,所以,实例属性在类中的添加一般不是创建一个个实例后一个个指定的,而是利用类的构造方法完成添加的,类的构造方法允许所有实例的相同实例属性使用相同的实例属性名。

    类的构造方法是一个特殊方法,名为__init__,该方法在类实例化时会自动调用。

    __init__方法除了self参数外可以增加任何数量的参数,这些参数被称为初始化参数,会作为值被赋给在该方法中定义的实例属性,当然也可以直接为实例属性指定值。

    __init__方法和c中的构造函数十分相似,但不像c的构造函数,它不一定是必要的。

  • 类属性的定义:

    类属性在类中直接按局部变量的定义处理即可,且对于定义后的类方法,可以直接使用类属性。

    类属性因为其共享性,允许不通过实例进行访问,直接通过类名访问。

    类属性除了用户定义的属性以外,实际上还有一部分的内置类属性,这些属性是解释器在新建类时自动创建的,用于保证类能够正常运行,上面的__init__也是内置类属性(这里是指方法名)。

    下面列举几个常见的属性:

    部分常用专有属性 说明 触发方式
    __init__ 构造初始化函数 创建实例后,赋值时使用,在__new__后
    __new__ 生成实例所需属性 创建实例时
    __class__ 实例所在的类 实例.__class__
    __str__ 实例字符串表示,可读性 print(类实例),如没实现,使用repr结果
    __repr__ 实例字符串表示,准确性 类实例 回车 或者 print(repr(类实例))
    __del__ 析构 del删除实例
    __dict__ 实例属性和值的键值对 vars(实例.__dict__)
    __doc__ 类文档,子类不继承 help(类或实例)

    另外注意,实例属性访问优先级比类属性高,可以用实例属性屏蔽掉类属性;实例无法直接用赋值的方法修改类属性,因此这实际上是给实例添加了一个同名的新实例属性。

2. 类定义及实例化和使用的例子:

首先,定义一个People类,其类属性为人数个数,其初始实例属性包括姓名、年龄、身高、体重、性别,为某些实例对象单独定义血型属性;People类的方法包括输出个人信息(不包括血型)、计算BMI、判断是否成年、输出当前人数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#类定义
class People:
# 类属性变量,共享
count = 0

# 类的初始化函数,第一个参数是self,其他参数用于接收信息,不传入可以有默认参数
def __init__(self,n=None,a=0,h=0,w=0,s="男"):
#类属性变量count+1,表示此人被创建
#需要使用People来说名变量是属于这个类的,没有就相当于局部变量
People.count+=1

#这五个均为实例属性变量
#需要使用self来说明变量是属于这个对象的,没有相当于局部变量
self.name=n
self.age=a
self.height=h
self.weight=w
self.sex=s

# 类方法,用于输出个人信息,虽然有一个参数,实际调用不需要传入参数,因为已经有一个隐式传入的参数了
def print_information(self):
print("个人信息为:")
print("姓名:",self.name)
print("年龄:",self.age)
print("身高:",self.height)
print("体重:",self.weight)
print("性别:",self.sex)
# 类方法,获得BMI
def get_BMI(self):
return self.weight/((self.height/100)*(self.height/100))

# 类方法,判断是否成年
def judge_adult(self):
if self.age>=18:
print("已成年")
return True
else:
print("未成年")
return False

# 类方法,输出当前创建的人数数量
def print_count(self):
print("当前实际人数为:",self.age)

下面通过几个例子来展示具体的运行逻辑:

1
2
3
4
5
6
7
8
9
Bob = People()           # 使用默认参数完成Bob的实例化
Bob.print_information() # 通过Bob调用方法,输出对应个人信息
# 运行结果:
# 个人信息为:
# 姓名: None
# 年龄: 0
# 身高: 0
# 体重: 0
# 性别: 男
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Tom = People("Tom",20,175,62,"男") #实例化对象Tom,姓名为Tom,20岁,175身高,62kg,性别男
Tom.print_information() # 输出Tom的个人信息
print("Tom的BMI为:",Tom.get_BMI()) #输出Tom的BMI
if Tom.judge_adult(): # 如果Tom是成年人
Tom.blood_type="A" # 将Tom的血型加入实例变量中,值为A型血
print("Tom的血型为:",Tom.blood_type) # 输出血型的实例变量值
# 运行结果:
# 个人信息为:
# 姓名: Tom
# 年龄: 20
# 身高: 175
# 体重: 62
# 性别: 男
# Tom的BMI为: 20.244897959183675
# 已成年
# Tom的血型为: A
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Jim =People("Jim",16,166,45,"女")  #实例化对象Jim,姓名为Jim,16岁,166身高,45kg,性别女
print(Jim.count) #通过实例Jim输出类属性count的值,应为3
Jim.count=4 #通过实例Jim修改类属性count的值,实际上是创建了实例属性count覆盖了类属性
print(id(Jim.count)) #输出Jim实例属性的id
print(Jim.count) #输出Jim实例属性的值,应为4
print(id(Tom.count)) #输出Tom类属性的id,应和上面的id不同
print(Tom.count) #输出Tom类属性的值,应为3
print(id(People.count)) #输出People类属性的id,不通过对象访问,应和Tom输出类属性的id相同
print(People.count) #输出People类属性的值,应为3
# 运行结果:
# 3
# 140727492866952
# 4
# 140727492866920
# 3
# 140727492866920
# 3

3. 一切皆对象:

当学习了类的基本内容之后,可以发现,不管是之前的数据、函数,还是现在的类,全都都是对象,Python中的一切皆为对象,都可以用type函数判断类型,用dir函数查看其属性和方法,用help查看其帮助文档。

对于变量,Python允许对实例变量绑定任何数据,也就是说,两个不同的实例变量,虽然都是同一个类的不同实例,但拥有的变量名称都可能不同。

不同的变量名,可以绑定到一个对象上,通过任何一个变量修改对象,会影响到所有指向该对象的所有变量。

具体的例子,请结合python基础——函数(二) - ZZHの个人博客 (07xiaohei.com)中的参数传递理解。


python进阶——类(一)面向对象、类和实例
https://github.com/xiaohei07/xiaohei07.github.io/2023/07/13/python进阶——类(一)面向对象、类和实例/
作者
07xiaohei
发布于
2023年7月13日
许可协议