学习编程,无论那种语言,面向对象编程(object-oriented programming,OOP)是当前最有效最普遍的编写方法之一。类则是面向对象编程的最基础的知识,可以说能够深刻理解类即掌握面向对象编程。
关于编程,归根结底是通过数据处理和处理数据完成特定任务的方式。如何更好的处理数据?把外界事物、情景、场景都抽象为对象,对象作为一种数据,有自己的个性与行为,把其通常的行为归纳、封装在一起,就是类。这样,基于类创建对象,根据需要赋予对象独特的个性。
简而言之,面向对象编程,就是编写表示现实世界中的事物和情景的类,基于这些类来创建对象。故编写的类,就是要定义一批对象都具备的通用行为。基于类创建的对象都自动具备这种通用行为,而且可以根据需要赋予每个对象独特的个性(属性)。
使用面向对象编程可模拟现实情景,及其逼真地表达外部世界逻辑。基于类创建的一个或若干相同的对象,及基于不同类创建的不同的对象,亦或基于具有传承关系的类创建的不同对象,它们模拟了现实世界的复杂的关系与相处情景,如同人类人与人、人与自然之间交互,然后,实现特定的任务。
1、概述:
学习面向对象编程有助于我们从另外一扇窗户看世界,明白编写的代码:不仅仅是每一行代码的作用,还有代码背后更宏大的概念。
了解类背后的概念可培养逻辑思维能力,让我们能够通过程序理解世界及解决遇到的几乎任何问题。类还能更融洽地与合作的及更多的程序员高效合作,彼此明白所做的工作,高效共享与写作,每个人都能事半功倍。
2、编写类:
使用类几乎可以模拟任何东西。下面一段简单的几行代码模拟了一个表示小狗的简单的类。
class Dog:'''一个模拟小狗的简单的类,有名字和年龄,有蹲下和打滚两个行为。'''def __init__(self,dog_name,dog_age):self.name = dog_nameself.age = dog_agedef sit(self):'''模拟小狗收到蹲下命令的行为'''print(f'{self.name} 收到蹲下命令,正在蹲下。')def roll_over(self):'''模拟小狗收到打滚命令的行为'''print(f'{self.name} 收到打滚命令,正在打滚。')
上面的Dog类——它表示的不是特定的小狗,而是任何小狗。通过这个Dog类,可以创建有名字、年龄,还有蹲下和打滚两种行为的不同小狗(对象)。类Dog,其实就是让Python知道如何创建表示小狗的对象。通过这个Dog类,可以使用它创建表示特定小狗的实例。
(1)根据约定,在Python中,首字母大写的名称指的是类。
(2)上面代码,通过关键字class定义一个Dog类,因为是创建的全新的类,所以定义时类名字后面不用加括号。如果是定义一个子类,需要在类名字后面加括号,括号内写明要继承的类(父类)。
(3)类名字后面一个冒号,然后下一行,约定一般是一个文档字符串,对类的功能做描述,让自己未来或者合作的其他人能够理解,但不需要撰写如何具体实现的细节。
(4)__init__()方法是一个特殊的方法,每当根据类创建新的实例(对象)时,Python会自动运行它。务必确保__init__()的两边都是两个下划线,否则使用类创建实例(对象)时,没有正确执行,将可能引发难以发现的错误。
上面代码定义的Dog类将__init__()方法定义了3个形参:self、dog_name、dog_age。在这个方法中,形参self必不可少,而且必须位于其它形参的前面。
为何必须在类的方法定义中包含形参self?因为当Python调用这个方法来创建实例时,将自动传入实参self。每个与实例对象相关联的方法调用,都会自动传入实参self,该实参是一个指向实例本身的引用,让实例能够访问类中的属性和方法。比如创建Dog类的实例时,Python将调用Dog类的__init__()方法。我们将通过实参向Dog()传递名字和年龄;self则自动传递,在调用时不要我们传递。每当使用Dog类创建实例时,都只需给最后两个形参(dog_name,dog_age)提供值。
(5)在__init__()方法内定义的连个变量都有前缀self(self.name,self.age)。以self为前缀的变量可供类中的所有方法使用,类的任意实例在(对象)都可以使用它。self.name = dog_name这行代码让self.name获取形参dog_name相关联的值,并将其赋给name变量,然后name变量被关联到当前创建的实例(对象)。这样就可以通过实例来访问(使用)name变量,即通过self.name使用name变量,像这种通过实例访问的变量称为属性(attribute)。(更严格地说,是类实例属性,或类对象属性,只有类实例化后赋予给对象变量后才可以使用。)
类的__init__()方法中,必须至少有一个形参self,根据需要设计其它形参及数量(也可以没有),有多少个这样的形参,在对类进行实例化时,就要提供相应的实参。需要清楚的是,self实参不是不用传入,而是Python自动传递而已,这是Python规则。
(6)上面代码定义的Dog类定义的两个方法:sit()和roll_over(),由于执行时候不需要额外的信息,因此只有一个形参self。类的实例化对象,调用这些方法时,Python也自动传递实参self,故类的实例化对象,调用这两个方法,方法内可以使用self.name等属性及其他方法。
3、根据类创建实例
可以将类视为有关如何创建实例的说明书。例如,Dog类就是一系列的说明,让Python知道如何创建表示特定小狗的实例。或者这样理解,类就如同一副中药方子,类的实例化,就是按照方子使用药材煎制出来一副药,这幅药就具备了相应的功能供使用。
下面创建一个表示特定小狗的实例:名字叫“金刚”,年龄3岁。
# 使用Dog类创建一个特定小狗实例,名字叫“金刚”,3岁
my_dog = Dog('金刚',3)# 让小狗蹲下
my_dog.sit()
# 让小狗打滚
my_dog.roll_over()# 使用小狗的名字name属性
print(f"我的小狗的叫{my_dog.name}。")
# 使用小狗的年龄age属性
print(f"我的小狗今年{my_dog.age}岁了。")
上面代码创建了一个名字叫“金刚”,年龄3岁的小狗。
Python在解释my_doy = Dog('金刚',3)这行代码时,调用Dog类中的__init__()方法,并传入实参“金刚”和3。__init__()方法创建了一个特定小狗实例,并且使用提供的值赋值给属性name和age,然后Python把这个小狗的实例赋给变量my_dog。
通常约定:首字母大写的名称表示类,比如Dog,而类实例用小写字母的名称,比如my_dog。
(1)访问属性
要访问实例的属性,使用点号(.)。如下:
my_dog.name
(2)调用方法
根据Dog类创建实例后,就可以使用点号来调用Dog类定义的方法了。要调用方法,需要指定实例名,通过点号,调用方法。比如让叫“金刚”的小狗蹲下:
my_dog.roll_over()
(3)创建多个实例
可按需求根据类创建任意数量的实例。比如下面再创建一个your_dog的小狗实例,名字叫“花花”,年龄5岁了。
my_dog = Dog('金刚',3)your_dog = Dog('花花',5)print(f"我的小狗名字叫{my_dog.name}")
print(f"你的小狗名字叫{your_dog.name}")my_dog.sit()
your_dog.roll_over()
上面代码创建了两条小狗,分别名字“金刚”和“花花”,每条小狗都是一个独立的实例,有自己的一组属性(名字,年龄等),能执行类定义的方法。
需要注意的是:即使给第二条小狗指定同样的名字和年龄,Python也会根据Dog类创建另一个实例,根据同一个类可以创建任意数量的实例,就算完全相同的属性,只要给实例起一个不一样的名字即可。
4、使用类和实例
可以使用类来模拟现实世界的很多情景。类编写好后,更多时间将根据类创建实例(对象)上。创建类的实例后,首要的事情之一就是修改实例的属性。既可以直接修改实例的属性,也可以在类中编写方法以特定的方式修改。
下面代码编写一个表示汽车的Car类,它存储了有关汽车的信息,并提供了一个汇总这些信息的方法:
class Car:'''一个模拟汽车的简单的类'''def __init__(self,manufacturer,model,year) -> None:'''初始化描述汽车的属性'''self.manufacturer = manufacturer # 制造商self.model = model # 型号self.year = year # 生产年份self.odometer_reading = 0 # 行驶里程def get_descriptivate_info(self):'''返回格式化规范的描述性信息'''car_info = f"{self.year}生产 {self.manufacturer} {self.model}"return car_infodef read_odometer(self):'''打印一条汽车行驶里程的消息'''print(f"这辆汽车目前行驶了{self.odometer_reading}公里。")# 创建一个Car的实例,定义制造商是奥迪,型号是A4,生产年份是2024年
my_new_car = Car('audi','A4',2024)# 打印描述创建的小汽车的信息
print(my_new_car.get_descriptivate_info())# 打印小汽车目前行驶的历程信息
my_new_car.read_odometer()
上面代码定义了一个简单的表示汽车的类Car。定义的__init__()方法有4个形参,第一个是必须的形参self。此外还有3个形参:manufacturer、model、year。__init__()方法接收这些形参的值,并将它们赋值给类创建的实例的属性。在根据类Car创建实例时,需要指定其制造商、型号、生产年份。
Car类中定义了一个get_descriptivate_info()的方法,它使用实例属性(manufacturer、model、year)创建一个对汽车进行综合描述的字符串信息,表明实例对象是一个什么样的汽车。
(1)给属性指定默认值
关于属性,通常是指通过通过实例访问的变量(和类的实例关联),在类中是通过“self.属性”来使用,类实例化后,通过“实例化对象变量名.属性名”来引用。
有些属性是通过类的__init__()方法中定义形参,实例时候传入相应的实参,并在__init__()方法中定义(初始化)。
有些属性在__init__()方法中定义的形参,实例化是传入的对应的实参,在__init__()方法中为之指定默认值(初始化值),比如Car类的__init__()方法中定义的(manufacturer,model,year)形参。
有些属性在__init__()方法中直接进行定义,比如Car类中__init__()方法中定义的odometer_reading属性,并初始化值为0。根据Car类创建的新实例,其属性“行驶历程”都是0。
(2)修改属性的值
可以用三种不同的方式修改属性的值:直接通过实例修改,通过在类中定义一个方法修改,以及通过类中定义的方法中改变。
# 创建实例
my_new_car = Car('audi','A4','2024')# 修改实例对象的行驶里程属性
my_new_car.odometer_reading =23
def update_odometer(self,mileage):'''将里程表更新为指定的值'''self.odometer_reading = mileage
上面一段代码定义一个方法,更新属性odometer_reading
def increment_odometer(self,miles):'''给里程表属性增加指定的量'''self.odometer_reading += miles
上面的代码,在类中定义了一个increment_odometer()方法,给属性odometer_reading增加指定量的里程。
总结与回顾:
编写类,是为了可以基于类创建一个或任意数量的实例(对象),并赋予不同实例独特个性,但都具备相同的通常行为,实现不同的相识的任务的代码块。这是编写类的出发点也是根本点,所以编写类的对象属性与通常方法乃是最为根本要务。
(1)类:是通过抽象与归纳描述的一个具有独特个性且有相同行为的情景或者事务,比如Dog类,所有小狗都会蹲下和打滚,但它们有不同的名字、年龄(当然还可以定义颜色、品种等)。相当于一个说明书,让Python知道如何创建。
(2)实例:也叫对象,是通过类文件由Python创建并赋给一个变量,每个类都可以创建任意数量的实例,它们可以由独特的个性(类中定义的属性),但具有相同的行为(类中定义的方法)。
(3)属性:是指在类中通过“self.属性名”来定义,在类实例化后,通过“实例名.属性名”来调用及更改,是和实例后的对象关联的一个变量。
(4)方法:通常是指在类中定义的一个方法,至少有一个形参self,类实例化后,通过赋值给的变量(对象)名来调用,执行一些任务的函数代码块。
(5)self:在Python编程中,self是一个极其特殊的存在,可以说无处不在。self存在与类的定义中,是类本身的一个指引。在类的方法中,第一个形参就是self,而且必须是self,如果类中定义的方法只有一个形参,那就是self。
在类实例化时,Python会自动传入实参self(类的实例对象),故不需要在调用类的实例对象的方法传入的实参中写明(也不能写),仅需要传入其他定义的实参即可。