教师妹学python之七:面向对象编程
目录
- Python的面向对象编程是什么?
- 类的定义
-
- 类与实例
- 定义一个类
- 实例化对象
-
- 类和实例属性
- 实例方法
- 测验
- 类继承
-
- 狗公园的例子
- 父类与子类
- 扩展父类的功能
- 测验
- 结论
面向对象编程(OOP)是一种通过将相关属性和行为绑定到单个对象中来构造程序的方法。从概念上讲,对象就像系统的组成部分,可以将程序想像成工厂的流水线,在流水线的每个步骤中,系统组件都会处理一些材料,最终将原材料转换为成品。
一个对象包含数据(例如流水线上每个步骤的原始材料)、行为(例如每个流水线组件可以执行的动作)。
本教程将介绍:
- 创建一个类
- 使用类创建新对象
- 具有类继承的模型系统
Python的面向对象编程是什么?
面向对象的编程是一种编程范例,它提供了一种结构化程序的方法,以便将属性和行为捆绑到单个对象中。
例如,对象可能代表一个人的属性(如姓名,年龄和地址)和行为(如走路,说话,呼吸和运行)。
换句话说,面向对象的编程是一种对具体的,现实世界中的事物(例如汽车)以及事物之间的关系(例如公司与员工,学生和教师等等)进行建模的方法。OOP将现实世界的实体建模为软件对象,这些对象具有与之关联的一些数据并且可以执行某些功能。
另一个常见的编程范例是面向过程编程,它像配方一样构造程序,因为它以功能和代码块的形式提供了一组步骤,这些步骤按顺序完成任务。
类的定义
基础数据结构(例如数字、字符串和列表)旨在表示简单的信息,例如苹果的价格、一首诗的名称或你喜欢的颜色。如果你想代表更复杂的东西怎么办?
例如,假设你要掌握员工动态。你需要存储每个员工的一些基本信息,例如姓名、年龄、职位以及开始工作年份。
一种方法是将每个员工表示为一个列表:
kirk = ["James Kirk", 34, "Captain", 2265] spock = ["Spock", 35, "Science Officer", 2254] mccoy = ["Leonard McCoy", "Chief Medical Officer", 2266]
但这种方法存在诸多问题。
首先,它会使大型代码文件更难管理。如果kirk[0]
在kirk
声明列表的位置之外引用几行,你是否还记得带有index的元素0
是员工的姓名?
其次,如果不是每个员工在列表中都有相同数量的元素,则可能会引入错误。在mccoy
的列表中缺少年龄信息,因此mccoy[1]
将返回"Chief Medical Officer"
而不是McCoy的年龄。
使此类代码更易于管理和维护的一种好方法是使用类。
类与实例
类用于创建用户定义的数据结构。类可以定义函数,函数可以利用实例中的数据执行一定的行为。
创建一个Dog
类,该类存储有关单个狗的特征和行为信息。实例是从类构建的并包含实际数据的对象,Dog
类的实例是一条真实的狗,名字像迈尔斯(Miles),已经四岁了。
换句话说,课程就像表格一样,实例就像已经填写了信息的表格,就像许多人可以用自己的独特信息填写相同的表格一样,可以从一个类中创建许多实例。
定义一个类
所有类定义均以class
关键字开头,后跟类名和冒号。在类定义下缩进的任何代码均被视为类主体的一部分。
这是一个Dog
类的示例:
class Dog: pass
Dog
类的主体由一个语句组成:pass
关键字。pass
通常用作占位符,指示代码最终将到达何处。它允许你在不引发Python错误的情况下运行此代码。
注意: Python类名称是按照惯例用大写字母表示法编写的。
所有Dog
对象必须具有的属性在.__init__()
方法中定义。每次创建新Dog
对象时,.__init__()
通过分配对象属性的值来设置对象的初始状态。即.__init__()
可以初始化该类的每个新实例。
让我们使用.__init__()
为Dog类增加.name
和.age
属性:
class Dog: def __init__(self, name, age): self.name = name self.age = age
请注意,.__init__()
方法缩进了四个空格。该方法的主体缩进了八个空格,这种缩进至关重要,它告诉Python .__init__()
方法属于Dog
该类。
在.__init__()
的主体中,有两个使用self
变量的语句:
self.name = name
创建一个名为name的属性,并为其分配name
参数的值。self.age = age
创建一个名为age的属性,age
并为其分配age
参数的值。
在.__init__()
中创建的属性称为实例属性。实例属性的值特定于类的特定实例。所有Dog
对象都有名称和年龄,但是name
和age
属性的值将根据Dog
实例而有所不同。
另一方面,类属性是对于所有类实例具有相同值的属性。当然你也可以在.__init__()
之外定义类的属性。
例如,以下Dog
类具有一个名为species
的类属性,值为"Canis familiaris"
:
class Dog: # Class attribute species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age
类属性直接在类名称的第一行下方定义,并以四个空格缩进,必须对其初始化。创建类的实例时,将自动创建类属性并将其分配给它们的初始值。
实例化对象
打开IDLE的交互式窗口,然后键入以下内容:
class Dog: pass
Dog
类没有属性或方法的新类。
创建类的对象称为实例化对象。你可以通过键入Dog
类的名称来实例化一个新对象:
>>> Dog() <__main__.Dog object at 0x106702d30>
现在,有了一个新Dog
对象0x106702d30
。这个看起来很有趣的字母和数字字符串是一个内存地址,用于指示Dog
对象在计算机内存中的存储位置。
现在实例化第二个Dog
对象:
>>> Dog() <__main__.Dog object at 0x0004ccc90>
新Dog
实例位于其他内存地址,那是因为它是一个全新的实例,并且与实例化的第一个Dog
对象完全不同,要以另一种方式查看此信息,请输入以下内容:
>>> a = Dog() >>> b = Dog() >>> a == b False
在此代码中,将创建两个新Dog
对象,并将它们分配给变量a
和b
。比较a
、b
使用==
运算符时,结果为False
。即使a
和b
都是Dog
类的实例,它们也代表内存中的两个不同的对象。
类和实例属性
现在创建一个新Dog
类,其类属性为.species
,两个实例属性为.name
和.age
:
>>> class Dog: ... species = "Canis familiaris" ... def __init__(self, name, age): ... self.name = name ... self.age = age
对Dog类实例化一个对象,你需要提供name
和age
属性值。如果不这样做,Python会引发一个TypeError
:
>>> Dog() Traceback (most recent call last): File "<pyshell#6>", line 1, in <module> Dog() TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
要将参数值传递给name
和age
参数:
>>> buddy = Dog("Buddy", 9) >>> miles = Dog("Miles", 4)
这将创建两个新Dog
实例-一个实例用于名为Buddy的9岁狗,另一个实例是名为Miles的4岁狗。
Dog
类的.__init__()
方法有三个参数,那么为什么只传递给它的两个参数呢?
实例化Dog
对象时,Python会创建一个新实例并将其传递给的第一个参数.__init__()
。这实际上删除了self
参数,因此只需要name
andage
参数即可。
创建Dog
实例后,可以使用‘.’访问它们的实例属性:
>>> buddy.name 'Buddy' >>> buddy.age 9 >>> miles.name 'Miles' >>> miles.age 4
可以通过以下方式访问类属性:
>>> buddy.species 'Canis familiaris'
使用类组织数据的最大优势之一是可以确保实例具有你期望的属性。所有Dog
实例都具有.species
,.name
和.age
属性,因此可以放心使用这些属性,因为它们将始终返回值。
尽管保证属性存在,但是也可以动态更改它们的值:
>>> buddy.age = 10 >>> buddy.age 10 >>> miles.species = "Felis silvestris" >>> miles.species 'Felis silvestris'
在此示例中,buddy
对象的.age
属性值更改为10
,将miles
对象的species属性值更改为"Felis silvestris"
。
默认情况下自定义对象是可变的。如果可以动态更改对象,则该对象是可变的。例如,列表和字典是可变的,但字符串和元组是不可变的。
实例方法
实例方法是在类内部定义的函数,只能从该类的实例中调用。就像.__init__()
实例方法的第一个参数始终是self
。
在IDLE中打开一个新的编辑器窗口,然后输入Dog
类:
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age # Instance method def description(self): return f"{self.name} is {self.age} years old" # Another instance method def speak(self, sound): return f"{self.name} says {sound}"
Dog
类具有两个实例方法:
.description()
返回显示狗的名字和年龄。.speak()
有一个sound参数,并返回一个字符串,中包含狗的名字和狗发出的声音。
将修改后的Dog
类保存到一个名为dog.py
的文件中,然后F5运行该程序,然后输入以下内容以查看实例方法的运行情况:
>>> miles = Dog("Miles", 4) >>> miles.description() 'Miles is 4 years old' >>> miles.speak("Woof Woof") 'Miles says Woof Woof' >>> miles.speak("Bow Wow") 'Miles says Bow Wow'
在上述Dog
类中,.description()
返回一个字符串,其中包含有关Dog
类实例对象的信息miles
。
创建list
对象时,可以print()
用来显示列表的字符串:
>>> names = ["Fletcher", "David", "Dan"] >>> print(names) ['Fletcher', 'David', 'Dan']
让我们看看print()
使用miles
对象时会发生什么:
>>> print(miles) <__main__.Dog object at 0x00aeff70>
当print(miles)
收到一条看起来很神秘的消息时,会告诉你这miles
是Dog
内存地址中的一个对象0x00aeff70
。你可以通过定义特殊的实例方法来更新打印的内容.__str__()
。
在编辑器窗口中,将Dog
类的.description()
方法名称更新为.__str__()
:
class Dog: # Leave other parts of Dog class as-is # Replace .description() with __str__() def __str__(self): return f"{self.name} is {self.age} years old"
保存文件,然后按F5。现在使用时print(miles)
,你将获得输出:
>>> miles = Dog("Miles", 4) >>> print(miles) 'Miles is 4 years old'
.__init__()
和.__str__()
称之为构造函数,因为它们以双下划线开头和结尾。你可以使用许多构造函数来自定义Python中的类。尽管对于一本入门级的Python教程来说,这个话题太高级了,但是了解构造函数是掌握Python中的面向对象程序设计的重要组成部分。
小测验
练习:创建汽车课程显示隐藏
创建Car类,具有两个属性: .color,它以字符串形式存储汽车颜色的名称 .mileage,它以整数形式存储汽车行驶的英里数 然后实例化两个Car对象-行驶20,000英里的蓝色汽车和行驶30,000英里的红色汽车-并打印出它们的颜色和行驶里程。输出应如下所示: The blue car has 20,000 miles. The red car has 30,000 miles.
解决方案:
首先,创建一个Car
具有.color
和.mileage
实例属性的类:
class Car: def __init__(self, color, mileage): self.color = color self.mileage = mileage
color
和mileage
参数分配给.__init__()
函数参数self.color
和self.mileage
。
现在,你可以创建两个Car
实例:
blue_car = Car(color="blue", mileage=20_000) red_car = Car(color="red", mileage=30_000)
该blue_car
实例由值传递创建"blue"
的color
参数,并20_000
为mileage
参数。同样,red_car
使用"red"
和创建值30_000
。
要打印每个Car
对象的颜色和里程,可以循环显示tuple
包含两个对象的:
for car in (blue_car, red_car): print(f"The {car.color} car has {car.mileage:,} miles")
类继承
继承是一个类继承另一个类的属性和方法的过程,新形成的类称为子类,子类派生自的类称为父类。
子类可以扩展父类的属性和方法。换句话说,子类继承了父级的所有属性和方法,但也可以指定自己唯一的属性和方法。尽管这样的类推并不恰当,但是你可以想到对象继承有点像遗传继承。
你可能是从母亲那里继承了头发的颜色,这是你与生俱来的属性。假设你决定将头发染成紫色。假设你的母亲没有紫色头发,那么你刚刚覆盖了从母亲那里继承的头发颜色属性。
从某种意义上说,你还从你的父母那里继承了语言。如果你的父母说英语,那么你也会说英语。现在,假设你决定学习第二种语言,例如德语。在这种情况下,你扩展了语言属性,因为你具备了父母没有的属性。
狗公园的例子
假装你在狗公园里。公园里有许多不同品种的狗,它们都有各种各样的狗行为。
现在需要使用Python类对狗公园进行建模。如果使用上一节中编写的Dog类(按名称和年龄来区分狗),则不能满足按品种来区分狗。
当然你可以为Dog类添加.breed
属性:
class Dog: species = "Canis familiaris" def __init__(self, name, age, breed): self.name = name self.age = age self.breed = breed
现在,你可以通过在交互式窗口中实例化一堆不同的狗来对狗公园进行建模:
>>> miles = Dog("Miles", 4, "Jack Russell Terrier") >>> buddy = Dog("Buddy", 9, "Dachshund") >>> jack = Dog("Jack", 3, "Bulldog") >>> jim = Dog("Jim", 5, "Bulldog")
每个品种的狗都有略有不同的行为。例如,斗牛犬叫声听起来很像woof,而腊肠犬的叫声更高,听起来更像是yap。
仅使用Dog
类,每次在实例上调用它时,都必须为Dog类.speak()
提供一个字符串:
>>> buddy.speak("Yap") 'Buddy says Yap' >>> jim.speak("Woof") 'Jim says Woof' >>> jack.speak("Woof") 'Jack says Woof'
父类与子类
让我们为上述三个品种中的每个品种创建一个子类:Jack Russell Terrier,Dachshund和Bulldog。以下是Dog
类的完整定义:
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name} is {self.age} years old" def speak(self, sound): return f"{self.name} says {sound}"
请记住,要创建子类,请使用其自己的名称创建新类,然后将父类的名称放在括号中。将以下内容添加到dog.py
文件中,以创建Dog
类的三个新的子类:
class JackRussellTerrier(Dog): pass class Dachshund(Dog): pass class Bulldog(Dog): pass
按F5以保存并运行文件。定义了子类后,现在可以在交互式窗口中实例化某些特定品种的狗:
>>> miles = JackRussellTerrier("Miles", 4) >>> buddy = Dachshund("Buddy", 9) >>> jack = Bulldog("Jack", 3) >>> jim = Bulldog("Jim", 5)
子类的实例继承了父类的所有属性和方法:
>>> miles.species 'Canis familiaris' >>> buddy.name 'Buddy' >>> print(jack) Jack is 3 years old >>> jim.speak("Woof") 'Jim says Woof'
要确定给定对象属于哪个类,可以使用内置的type()
:
>>> type(miles) <class '__main__.JackRussellTerrier'>
如果要确定是否miles
也是Dog
该类的实例怎么办?可以使用内置的方法执行此操作isinstance()
:
>>> isinstance(miles, Dog) True
注意,isinstance()
带有两个参数,一个对象和一个类。在上面的示例中,isinstance()
检查miles
是否是Dog
类的实例,然后返回True
。
miles
,buddy
,jack
和jim
对象都是Dog
实例,但miles
不是Bulldog
的实例,而jack
不是Dachshund
的实例:
>>> isinstance(miles, Bulldog) False >>> isinstance(jack, Dachshund) False
扩展父类的功能
由于不同品种的狗的吠声略有不同,因此需要为其各自.speak()
方法的参数提供默认值。为此,你需要.speak()
在每个类定义中覆盖。
要覆盖父类定义的方法,请在子类上定义一个具有相同名称的方法。
class JackRussellTerrier(Dog): def speak(self, sound="Arf"): return f"{self.name} says {sound}"
现在.speak()
在JackRussellTerrier
类中定义了默认参数,sound
将其设置为"Arf"
。
dog.py
使用新的JackRussellTerrier
类进行更新,然后按F5保存并运行文件。
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf'
有时狗会发出不同的吠叫,因此,如果Miles生气咆哮时,仍然可以用.speak()
不同的声音:
>>> miles.speak("Grrr") 'Miles says Grrr'
关于类继承要记住的是,对父类的更改会自动传播到子类。
例如,改变Dog
类.speak()
的返回值:
class Dog: # Leave other attributes and methods as they are # Change the string returned by .speak() def speak(self, sound): return f"{self.name} barks: {sound}"
保存文件,然后按F5。现在,当你创建一个Bulldog
名为的新实例时jim
,jim.speak()
将返回新字符串:
>>> jim = Bulldog("Jim", 5) >>> jim.speak("Woof") 'Jim barks: Woof'
但是,调用.speak()
一个JackRussellTerrier
实例不会显示输出的新风格:
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles says Arf'
可以使用super()
方法从子类的方法内部访问父类:
class JackRussellTerrier(Dog): def speak(self, sound="Arf"): return super().speak(sound)
当你调用super().speak(sound)
,Python搜索父类Dog
的.speak()
方法。
>>> miles = JackRussellTerrier("Miles", 4) >>> miles.speak() 'Miles barks: Arf'
现在,当你调用时miles.speak()
,你将看到Dog
类中新格式的输出。
注意:在以上示例中,类层次结构非常简单。JackRussellTerrier
类有一个父类Dog
。在实际示例中,类层次结构可能会变得非常复杂。
super()
不仅可以在父类中搜索方法或属性,还可以做更多的事情。它遍历整个类层次结构以找到匹配的方法或属性。
小测验
练习:
创建一个GoldenRetriever
从Dog
该类继承的类。给出默认值为的sound
参数。对父类使用以下代码:GoldenRetriever.speak()
"Bark"
Dog
class Dog: species = "Canis familiaris" def __init__(self, name, age): self.name = name self.age = age def __str__(self): return f"{self.name} is {self.age} years old" def speak(self, sound): return f"{self.name} says {sound}"
解决方案:
创建一个名为GoldenRetriever
的Dog
类,并覆盖Dog类的.speak()
方法:
class GoldenRetriever(Dog): def speak(self, sound="Bark"): return super().speak(sound)
GoldenRetriever.speak()
的sound参数默认值为"Bark"
,然后使用super()
用来调用父类的.speak()
方法,为GoldenRetriever
类的.speak()
方法传递相同的sound
参数。
总结
本教程学习内容:
- 定义一个class
- 实例化类中的对象
- 使用属性和方法定义对象的属性和行为
- 使用继承从父类创建子类
- 使用以下方法引用父类上的方法
super()
- 使用以下命令检查对象是否从另一个类继承
isinstance()