文章目录
22.1 简介:类继承
类可以从其他类中 “inherit” 方法和类变量。我们将在后续章节中看到这是如何工作的。当其他人在模块或库中定义了一个类,并且您只想重写一些事情而不必重新实现他们所做的一切时,您也会发现它很有用。
考虑我们的Tamagotchi游戏。假设我们想要制造一些不同种类的宠物,它们和其他宠物有相同的结构,但是有一些不同的属性(attributes)或者表现有点不同。
例如,假设狗宠物应该表现出与猫稍有不同的情绪状态,或者当它们饿了或者被要求去取东西的时候表现出不同的行为。
您可以通过为对应的pet类型创建一个实例变量,并通过各种方法对该实例变量进行调度来实现这一点。
from random import randrange
class Pet():
boredom_decrement = 4
hunger_decrement = 6
boredom_threshold = 5
hunger_threshold = 10
sounds = ['Mrrp']
def __init__(self, name = "Kitty", pet_type="dog"): # 新的输入参数
self.name = name
self.hunger = randrange(self.hunger_threshold)
self.boredom = randrange(self.boredom_threshold)
self.sounds = self.sounds[:] # 复制class属性,这样当我们对它进行更改时,就不会影响类中的其他宠物
self.pet_type = pet_type # 这是新添加的实例变量
def clock_tick(self):
self.boredom += 1
self.hunger += 1
def mood(self):
if self.hunger <= self.hunger_threshold and self.boredom <= self.boredom_threshold:
if self.pet_type == "dog": # 如果宠物是狗,它会用不同于猫或其他动物的方式来表达自己的情绪
return "happy"
elif self.pet_type == "cat":
return "happy, probably"
else:
return "HAPPY"
elif self.hunger > self.hunger_threshold:
if self.pet_type == "dog": # 饥饿也是一样——在这个版本的类定义中,狗和猫将以稍微不同的方式表达它们的饥饿
return "hungry, arf"
elif self.pet_type == "cat":
return "hungry, meeeeow"
else:
return "hungry"
else:
return "bored"
def __str__(self):
state = " I'm " + self.name + ". "
state += " I feel " + self.mood() + ". "
return state
def hi(self):
print(self.sounds[randrange(len(self.sounds))])
self.reduce_boredom()
def teach(self, word):
self.sounds.append(word)
self.reduce_boredom()
def feed(self):
self.reduce_hunger()
def reduce_hunger(self):
self.hunger = max(0, self.hunger - self.hunger_decrement)
def reduce_boredom(self):
self.boredom = max(0, self.boredom - self.boredom_decrement)
该代码与您在Tamagotchi部分中看到的定义Pet类的代码完全相同,只是我们添加了一些内容:
- A new input to the constructor – the
pet_type
input parameter, which defaults to"dog"
, and theself.pet_type
instance variable.if...elif...
在self.mood()
方法中,不同类型的宠物(狗、猫或任何其他类型的动物)以稍微不同的方式表达他们的情绪和饥饿。
但这不是一种优雅的方式。它掩盖了作为宠物的一部分,这是所有宠物共有的,它把作为一只狗或一只猫的独特之处隐藏在情绪方法的中间。如果你也想要一只狗以不同于猫的速度来减少无聊感,而你想要一只鸟宠物还是不同的呢?在这里,我们只实现了狗、猫和其他——但是你可以想象可能性。
如果有很多不同类型的宠物,则if...elif...elif...
代码子句会使这些方法变得冗长而复杂,这可能会造成混淆。 而且,您需要在每种方法中对于不同类型的宠物而言行为都不同的方法。 类继承将为我们提供一种更优雅的方法。
22.2. Inheriting Variables and Methods
22.2.1. Mechanics of Defining a Subclass
基本上,它通过定义一个新的类,并使用特殊的语法来显示这个新的子类从一个超类继承了什么。
CURRENT_YEAR = 2019
class Person():
def __init__(self, name, year_born):
self.name = name
self.year_born = year_born
def getAge(self):
return CURRENT_YEAR - self.year_born
def __str__(self):
return '{}({})'.format(self.name, self.getAge()) # 这里的方法注意一下
假设我们想创建一个新的类 Stundet
,
CURRENT_YEAR = 2019
class Student():
def __init__(self, name, year_born):
self.name = name
self.year_born = year_born
self.knowedge = 0 # 添加一个新的实例变量
def getAge(self):
return CURRENT_YEAR - self.year_born
def __str__(self):
return '{}({})'.format(self.name, self.getAge()) # 这里的方法注意一下
def study(self): # 添加 一个新的方法,每次调用 只是加一
self.knowedge += 1
使用继承的方法:inheritance,每个学生都是Person,
CURRENT_YEAR = 2019
class Person():
def __init__(self, name, year_born):
self.name = name
self.year_born = year_born
def getAge(self):
return CURRENT_YEAR - self.year_born
def __str__(self):
return '{}({})'.format(self.name, self.getAge()) # 这里的方法注意一下
class Student(Person): # 实现继承的方法 下面我们只需要定义与Person不同的部分
def __init__(self, name, year_born):
Person.__init__(self, name, year_born) # 调用被继承的类的实例变量
self.knowedge = 0 # 添加一个新的实例变量
# 继承 Person 类的所有方法
def study(self): # 添加 一个新的方法,每次调用 只是加一
self.knowedge += 1
And you can continue the inheritance tree.
22.2.2. How the interpreter looks up attributes
那么当您编写带有类,子类以及父类和子类的实例的程序时,Python解释器中发生了什么?
This is how the interpreter looks up attributes:
首先,它通过 by the name it’s looking for 来检查实例变量或实例方法。
注:包括从父类继承来的 实例变量 (有的例子里继承父类的实例变量不写也可以?)
如果找不到该名称的实例变量或方法,它将检查类变量。 (有关实例变量和类变量之间的区别的解释,请参见上一章。方法也是类变量)
如果找不到类变量,它将在父类中查找类变量。
如果未找到类变量,则解释器将在THAT类的父级(“祖父母”类)中查找类变量。
这个过程一直进行到到达最后一个祖先为止,此时Python将发出错误信号。
22.3. Overriding Methods
如果为一个类定义了一个方法,并且为其父类也定义了一个方法,则将调用子类的方法,而不是父类的方法。 这遵循您在上一节中看到的用于查找属性的规则。
我们可以使用相同的想法来理解覆盖方法。
因此,这就引出了一个问题,“我们什么时候应该真正继承一个超类?”
只有当你的子类应该拥有超类所拥有的一切,再加上更多或者可能加上一些小的修改,你才应该继承。让我们来看一个例子。
假设我们有两种 book:Paperbook 和 Ebook
SUPERCLASS 超类
class Book():
def __init__(self, title, author): # 每本书都有书名 和作者
self.title = title
self.author = author
def __str__(self):
return '{} by {}'.format(self.title, self.author)
# 创建一个实例
# Mybook = Book('The Odyssey', 'Homer')
子类:Paperbook 和 Ebook , both ebooks and paper books have everything that a book has plus more.
class PaperBook(Book):
def __init__(self, title, author, numPages): # 输入参数包含所有的实例变量
Book.__init__(self, title, author)
self.numPages = numPages
class Ebook(Book):
def __init__(self, title, author, size):
Book.__init__(self, title, author)
self.size = size
# 创建一个电子书实例,size 是2
# Mybook = Eook('The Odyssey', 'Homer', 2)
# 使用 size方法
# print(Mybook.size())
在此示例中,我们希望将 Paperbook
和 Ebook
创建为单独的子类,因为在这种情况下, Paperbook
和 Ebook
具有 Book
所具有的一切。 即, Paperbook
具有标题和作者, Ebook
也具有。但是, Paperbook
和 Ebook
也具有附加的内容, Paperbook
有 numPages
属性, Ebook
具有 size
属性。
所以,当你想拥有一个子类,拥有超类所拥有的一切,再加上更多,那就使用继承。当你想在一个类中包含另一个类的时候,不需要使用继承
Composition
class PaperBook(Book):
def __init__(self, title, author, numPages):
Book.__init__(self, title, author)
self.numPages = numPages
class Ebook(Book):
def __init__(self, title, author, size):
Book.__init__(self, title, author)
self.size = size
class Library():
def __init__(self):
self.books = []
def addBook(self):
self.book.append(book)
def getNumBooks(self):
return len(self.book)
好的,我想强调的重要一点是,尽管 library
和上面的 book 的类之间有一些关系(在 library
中包含一系列的书),但我们不想从book 类继承,或者我们不想让book类从 library
继承,因为不是 library
拥有book所拥有的一切,或者一本书拥有 library
所拥有的一切。It’s just that libraries contain books。所以,我没有继承书籍,而是创建了一个单独的实例变量 instance variable(self.books = []
) 来实际包含我们拥有的书籍列表。这是一个叫做composition 的例子。
22.4. Invoking 调用父类的方法
如果我们在子类的方法中使用相同的名称覆盖父类的方法,但我又想要如何调用父类的方法怎么办呢?
Say you wanted the Dog
subclass of Pet
to say “Arf! Thanks!” when the feed
method is called, as well as executing the code in the original method. Here’s the original Pet class again.
from random import randrange
# Here's the original Pet class
class Pet():
boredom_decrement = 4
hunger_decrement = 6
boredom_threshold = 5
hunger_threshold = 10
sounds = ['Mrrp']
def __init__(self, name = "Kitty"):
self.name = name
self.hunger = randrange(self.hunger_threshold)
self.boredom = randrange(self.boredom_threshold)
self.sounds = self.sounds[:] # copy the class attribute, so that when we make changes to it, we won't affect the other Pets in the class
def clock_tick(self):
self.boredom += 1
self.hunger += 1
def mood(self):
if self.hunger <= self.hunger_threshold and self.boredom <= self.boredom_threshold:
return "happy"
elif self.hunger > self.hunger_threshold:
return "hungry"
else:
return "bored"
def __str__(self):
state = " I'm " + self.name + ". "
state += " I feel " + self.mood() + ". "
# state += "Hunger %d Boredom %d Words %s" % (self.hunger, self.boredom, self.sounds)
return state
def hi(self):
print(self.sounds[randrange(len(self.sounds))])
self.reduce_boredom()
def teach(self, word):
self.sounds.append(word)
self.reduce_boredom()
def feed(self):
self.reduce_hunger()
def reduce_hunger(self):
self.hunger = max(0, self.hunger - self.hunger_decrement)
def reduce_boredom(self):
self.boredom = max(0, self.boredom - self.boredom_decrement)
下面是一个子类,它通过调用父类的 feed()
方法来 override 这个子类的 feed()
; 然后,它还会执行额外的一行代码。 请注意,调用父类方法的方法有些微妙。
我们显式引用
Pet.feed()
以获取方法/函数对象。但是,通常我们使用的调用这个方法的语法是:
<obj_instance >.methodname
,但这里Pet.feed()
没有指定调用 feed 的宠物实例,所以我需要将这个特殊的实例作为第一个参数传递,因此我们使用Pet.feed(self)
来传递这个实例
from random import randrange
class Dog(Pet):
sounds = ['Woof', 'Ruff']
def feed(self):
Pet.feed(self) # 在子类中调用父类的方法
print("Arf! Thanks!")
d1 = Dog("Astro")
d1.feed()
注意
有一种更好的方法来调用超类的方法,我们称之为
super().feed()
这很好,因为它更容易阅读,也因为 also because it puts the specification of the class thatDog
inherits from in just one place,class Dog(Pet)
。 另外,您只需参考super()
,python会负责查找Dog
的父类(super)是Pet
。
Now this also works for constructors. This technique is very often used with the __init__
method for a subclass.
假设要为子类定义了一些额外的实例变量。当您调用构造函数时,您将传递父类的所有常规(实例)参数给子类,并定义子类的额外参数。然后子类的 __init__
方法将额外的参数存储在实例变量中,并调用父类__init__
的方法将公共参数(the common parameters)存储在实例变量中,并像正常那样执行初始化。
假设我们想要创建一个名为 Bird
的 Pet
子类,我们想让它接受一个额外的参数 chirp_number
,默认值为2,则一个额外的实例变量 self.chirp_number
。然后,我们将在hi()
方法中使用它来发出多个声音。
class Bird(Pet):
sounds = ["chirp"] # 类变量
def __init__(self, name="Kitty", chirp_number=2): # 注意默认参数赋值的方法
Pet.__init__(self, name) # 调用父类的构造函数
# basically, call the SUPER().
self.chirp_number = chirp_number # 现在,还要分配新的实例变量
def hi(self):
for i in range(self.chirp_number):
print(self.sounds[randrange(len(self.sounds))])
self.reduce_boredom()
b1 = Bird('tweety', 5)
b1.teach("Polly wanna cracker")
b1.hi()
例子-练习
class Pokemon(object):
attack = 12
defense = 10
health = 15
p_type = "Normal"
def __init__(self, name, level = 5): # level 参数不指定的时候默认为 5
self.name = name
self.level = level
def train(self):
self.update()
self.attack_up()
self.defense_up()
self.health_up()
self.level = self.level + 1
# 升级
if self.level%self.evolve == 0:
return self.level, "Evolved!"
else:
return self.level
def attack_up(self):
self.attack = self.attack + self.attack_boost
return self.attack
def defense_up(self):
self.defense = self.defense + self.defense_boost
return self.defense
def health_up(self):
self.health = self.health + self.health_boost
return self.health
def update(self):
self.health_boost = 5
self.attack_boost = 3
self.defense_boost = 2
self.evolve = 10
def __str__(self):
self.update()
return "Pokemon name: {}, Type: {}, Level: {}".format(self.name, self.p_type, self.level)
# 子类 草系
class Grass_Pokemon(Pokemon):
attack = 15
defense = 14
health = 12
def update(self):
self.health_boost = 6
self.attack_boost = 2
self.defense_boost = 3
self.evolve = 12
def moves(self):
self.p_moves = ["razor leaf", "synthesis", "petal dance"]
def action(self):
info = "{} knows a lot of different moves!".format(self.name)
return info
p1 = Grass_Pokemon('Belle')
print(p1)
p1.train()
print(p1)
print(p1.action())
print(p1.attack_up())
Along with the Pokemon
parent class, we have also provided several subclasses. Write another method in the parent class that will be inherited by the subclasses. Call it opponent
. It should return which type of pokemon the current type is weak and strong against, as a tuple.
- Grass is weak against Fire and strong against Water
- Ghost is weak against Dark and strong against Psychic
- Fire is weak against Water and strong against Grass
- Flying is weak against Electric and strong against Fighting
For example, if the p_type
of the subclass is 'Grass'
, .opponent()
should return the tuple ('Fire', 'Water')
class Pokemon():
attack = 12
defense = 10
health = 15
p_type = "Normal"
def __init__(self, name,level = 5):
self.name = name
self.level = level
self.weak = "Normal"
self.strong = "Normal"
def opponent(self):
if self.p_type == 'Grass':
oppo_pair = ('Fire', 'Water')
elif self.p_type == 'Ghost':
oppo_pair = ('Dark', 'Psychic')
elif self.p_type == 'Fire':
oppo_pair = ('Water', 'Grass')
elif self.p_type == 'Flying':
oppo_pair = ('Electric', 'Fighting')
else:
oppo_pair = 'No Information'
return oppo_pair
def train(self):
self.update()
self.attack_up()
self.defense_up()
self.health_up()
self.level = self.level + 1
if self.level%self.evolve == 0:
return self.level, "Evolved!"
else:
return self.level
def attack_up(self):
self.attack = self.attack + self.attack_boost
return self.attack
def defense_up(self):
self.defense = self.defense + self.defense_boost
return self.defense
def health_up(self):
self.health = self.health + self.health_boost
return self.health
def update(self):
self.health_boost = 5
self.attack_boost = 3
self.defense_boost = 2
self.evolve = 10
def __str__(self):
self.update()
return "Pokemon name: {}, Type: {}, Level: {}".format(self.name, self.p_type, self.level)
class Grass_Pokemon(Pokemon):
attack = 15
defense = 14
health = 12
p_type = "Grass"
def update(self):
self.health_boost = 6
self.attack_boost = 2
self.defense_boost = 3
self.evolve = 12
class Ghost_Pokemon(Pokemon):
p_type = "Ghost"
def update(self):
self.health_boost = 3
self.attack_boost = 4
self.defense_boost = 3
class Fire_Pokemon(Pokemon):
p_type = "Fire"
class Flying_Pokemon(Pokemon):
p_type = "Flying"
p1 = Flying_Pokemon('Bird')
print(p1.opponent())
('Electric', 'Fighting')