Наследование

Наследование – важная составляющая объектно-ориентированного программирования. Так или иначе мы уже сталкивались с ним, ведь объекты наследуют атрибуты своих классов. Однако обычно под наследованием в ООП понимается наличие классов и подклассов. Также их называют супер- или надклассами и классами, а также родительскими и дочерними классами.

Суть наследования здесь схожа с наследованием объектами от классов. Дочерние классы наследуют атрибуты родительских, а также могут переопредять атрибуты и добавлять свои.

Простое наследование методов родительского класса

В качестве примера рассмотрим разработку класса столов и его двух подклассов – кухонных и письменных столов. Все столы, независимо от своего типа, имеют длину, ширину и высоту. Пусть для письменных столов важна площадь поверхности, а для кухонных – количество посадочных мест. Общее вынесем в класс, частное – в подклассы.

Связь между родительским и дочерним классом устанавливается через дочерний: родительские классы перечисляются в скобках после его имени.

class Table:
    def __init__(self, l, w, h):
        self.lenght = l
        self.width = w
        self.height = h
 
class KitchenTable(Table):
    def setPlaces(self, p):
        self.places = p
 
class DeskTable(Table):
    def square(self):
        return self.width * self.length

В данном случае классы KitchenTable и DeskTable не имеют своих собственных конструкторов, поэтому наследуют его от родительского класса. При создании экземпляров этих столов, передавать аргументы для __init__() обязательно, иначе возникнет ошибка:

>>> from test import *
>>> t1 = KitchenTable()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init__() missing 3 required positional arguments: 'l', 'w', and 'h'
>>> t1 = KitchenTable(2, 2, 0.7)
>>> t2 = DeskTable(1.5, 0.8, 0.75)
>>> t3 = KitchenTable(1, 1.2, 0.8)

Несомненно можно создавать столы и от родительского класса Table. Однако он не будет, согласно неким родственным связям, иметь доступ к методам setPlaces() и square(). Точно также как объект класса KitchenTable не имеет доступа к единоличным атрибутам сестринского класса DeskTable.

>>> t4 = Table(1, 1, 0.5)
>>> t2.square()
1.2000000000000002
>>> t4.square()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Table' object has no attribute 'square'
>>> t3.square()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'KitchenTable' object has no attribute 'square'

В этом смысле терминология "родительский и дочерний класс" не совсем верна. Наследование в ООП – это скорее аналог систематизации и классификации наподобие той, что есть в живой природе. Все млекопитающие имеют четырехкамерное сердце, но только носороги – рог.

Полное переопределение метода надкласса

Что если в подклассе нам не подходит код метода его надкласса. Допустим, мы вводим еще один класс столов, который является дочерним по отношению к DeskTable. Пусть это будут компьютерные столы, при вычислении рабочей поверхности которых надо отнимать заданную величину. Имеет смысл внести в этот новый подкласс его собственный метод square():

class ComputerTable(DeskTable):
    def square(self, e):
        return self.width * self.length - e

При создании объекта типа ComputerTable по-прежнему требуется указывать параметры, так как интерпретатор в поисках конструктора пойдет по дереву наследования сначала в родителя, а потом в прародителя и найдет там метод __init__().

Однако когда будет вызываться метод square(), то поскольку он будет обнаружен в самом ComputerTable, то метод square() из DeskTable останется невидимым, т. е. для объектов класса ComputerTable он окажется переопределенным.

>>> from test import ComputerTable
>>> ct = ComputerTable(2, 1, 1)
>>> ct.square(0.3)
1.7

Дополнение, оно же расширение, метода

Если посмотреть на вычисление площади, то часть кода надкласса дублируется в подклассе. Этого можно избежать, если вызвать родительский метод, а потом дополнить его:

class ComputerTable(DeskTable):
    def square(self, e):
        return DeskTable.square(self) - e 

Здесь вызывается метод другого класса, а потом дополняется своими выражениями. В данном случае вычитанием.

Рассмотрим другой пример. Допустим, в классе KitchenTable нам не нужен метод, поле places должно устанавливаться при создании объекта в конструкторе. В классе можно создать собсвенный конструктор с чистого листа, чем переопределить родительский:

class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        self.length = l
        self.width = w
        self.height = h
        self.places = p

Однако это не лучший способ, если дублируется почти весь конструктор надкласса. Проще вызвать родительский конструктор, после чего дополнить своим кодом:

class KitchenTable(Table):
    def __init__(self, l, w, h, p):
        Table.__init__(self, l, w, h)
        self.places = p

Теперь при создании объекта типа KitchenTable надо указывать в конструкторе четыре аргумента. Три из них будут переданы выше по лестнице наследования, а четвертый оприходован на месте.

>>> tk = KitchenTable(2, 1.5, 0.7, 10)
>>> tk.places
10
>>> tk.width 
1.5

Практическая работа

Разработайте программу по следующему описанию.

В некой игре-стратегии есть солдаты и герои. У всех есть свойство, содержащее уникальный номер объекта, и свойство, в котором хранится принадлежность команде. У солдат есть метод "иду за героем", который в качестве аргумента принимает объект типа "герой". У героев есть метод увеличения собственного уровня.

В основной ветке программы создается по одному герою для каждой команды. В цикле генерируются объекты-солдаты. Их принадлежность команде определяется случайно. Солдаты разных команд добавляются в разные списки.

Измеряется длина списков солдат противоборствующих команд и выводится на экран. У героя, принадлежащего команде с более длинным списком, поднимается уровень.

Отправьте одного из солдат первого героя следовать за ним. Выведите на экран идентификационные номера этих двух юнитов.

Курс с примерами решений практических работ: android-приложение, pdf-версия.

Создано

Обновлено

Комментарии

class Solder:
	def __init__(self, number, team):
		self.team = team
		self.number = number
 
	def to_hero(self, hero):
		return hero.number, self.number
 
class Hero:
	def __init__(self, number, team_number, team): # Если сдесь обьявить team=[] то почему то у нас список не сбрасывается для каждого отдельного экземпляра.
		self.team_number = team_number
		self.number = number
		self.lvl = 0
		self.team = team
 
	def up_lvl(self):
		self.lvl += 1
 
	def add_solder(self, solder):
		return self.team.append(solder)
 
	def look_count_team(self):
		return len(self.team)
 
hero_team_first = Hero(1, 1, [])
hero_team_second = Hero(2, 2, [])
count = 0
while count < 10:
	team_for_solder = random.randrange(1, 3)
	number_for_solder = random.random()
	if team_for_solder == 1:
		hero_team_first.add_solder(Solder(number_for_solder, team_for_solder))
	else:
		hero_team_second.add_solder(Solder(number_for_solder, team_for_solder))
	count += 1	
 
 
 
if hero_team_first.look_count_team() > hero_team_second.look_count_team():
	hero_team_first.up_lvl()
else:
	hero_team_second.up_lvl()
 
print(hero_team_first.team[0].to_hero(hero_team_first))

Первый вопрос следует из комментария, почему если мы объявим список как аргумент по умолчанию, то он становиться единым для все экземпляров класса.

Второй вопрос как мне сделать метод to_here() без дополнительных аргументов, чтоб он следовал за героем к которому он принадлежит?

Ответ на от Iliner

class Hero:
    def __init__(self, number, team_number, team=[]):
        self.team_number = team_number
        self.number = number
        self.lvl = 0
        self.team = team
 
first = Hero(1, 1)
second = Hero(2, 2)
first.team.append('ben')
second.team.append('ken')
print(first.team, second.team)
print(first.team is second.team)

Результат:

['ben', 'ken'] ['ben', 'ken']
True

Переменные first.team и second.team ссылаются на один и тот же объект. Спецэффект связан с особенностями Питона при работе с изменяемыми типами данных. Список к таковым относится. Суть: при связывании списка с новой переменной, он не копируется, а на один и тот же список начинают указывать множество переменных.

Когда аргумент не передается в метод, а используется список [] по-умолчанию, то получается, что он создается единожды. Ссылка на него присваивается переменной team. Далее ссылка на этот же список присваивается переменной self.team. Когда создается новый объект, то новая локальная переменная team связывается с уже существующим списком, который уже не пуст.

Второй вопрос. Если ходите избавиться от аргумента hero, то надо в каком-то поле сохранить героя команды например в методе __init__. Получится длиннее, чем с аргументом hero в методе to_hero.

Уникальный идентификатор объектов (number) реализован не совсем верно. Тема про наследование. По-идее общие атрибуты выносятся в общий надкласс.

Ответ на от plustilino

import random
 
class Character:
    uniq_number = 0
    def __init__(self, number_team):
        self.uniq_number = number_team
        Character.uniq_number += 1
        self.uniq_number = Character.uniq_number
 
class Hero(Character):
    def __init__(self, number_tean):
        super(Hero, self).__init__(number_tean)
        self.lvl = 0
        self.team_list = []
 
    def up_lvl(self):
        self.lvl += 1
 
    def add_solder(self, unit):
        self.team_list.append(unit)
 
    def len_team(self):
        return len(self.team_list)
 
class Solder(Character):
 
    def to_hero(self, hero):
        return "Уникальный номер солдата: {}. Уникальный номер Героя: {} ".format(self.uniq_number, hero.uniq_number)
 
first_hero = Hero(1)
second_hero = Hero(2)
count = 1
while count < 10:
    number_team = random.randrange(1, 3)
    if number_team == 1:
        first_hero.add_solder(Solder(number_team))
    else:
        second_hero.add_solder(Solder(number_team))
    count += 1
 
print(first_hero.len_team(), second_hero.len_team())
if first_hero.len_team() > second_hero.len_team():
    first_hero.up_lvl()
else:
    second_hero.up_lvl()
print(first_hero.team_list[0].to_hero(first_hero))

Только не кидайтесь помидорами)) случился такой затык
import random
class unit:
    def __init__(self, team = random.randint(1, 2)):
        self.numb = id(self)
        self.team = team
 
class soldier(unit):
    def go_with_hero(hero):
        pass
class hero(unit):
    def level_up():
        pass
 
Super = hero(1)
Puper = hero(2)
Super_team = []
Puper_team = []
for i in range(int(input("Введите количество солдат: ")):
    i = soldier()
    if i.team == 1:
        Super_team.append(i)
    else:
        Puper_team.append(i)
при проверке длины списков всем солдатам присвоен один и тот же номер команды, либо 1 либо 2.

Вопрос почему не рандомно каждому солдату?

Ответ на от Илья

Видимо выражение random.randint(1, 2) при использовании его в качестве аргумента метода вычисляется единожды, когда интерпретатор первый раз читает класс. Это происходит до вызова класса. Потом когда создаются объекты используется вычисленное значение. Т. е. на момент вызова имеем например def __init__(self, team=1). Когда создаются герои, то значение тим заменяется на переданное. Когда ничего не передается (в случае солдат), то берется по умолчанию, т. е. то которое было вычислено на момент "компиляции" класса.

Ответ на от plustilino

Спасибо за версию. На след день немного переосмыслил код и всё получилось:
import random
import time
class unit:
    def __init__(self):
        self.numb = id(self)
        self.team = random.randint(1,2)
 
class soldier(unit):
    def go_with_hero(hero):
        pass
class hero(unit):
    def __init__(self, team):
        self.team = team
    def level_up():
        pass
 
Super = hero(1)
Puper = hero(2)
Super_team = []
Puper_team = []
for i in range(int(input("Введите количество солдат: "))):
    i = soldier()
    if i.team == 1:
        Super_team.append(i)
    else:
        Puper_team.append(i)
Вывод - во всех непонятных случаях - ложитесь спать)))

Утро вечера мудренее))

Ответ на от Илья

Мой Вариант)))
import random
class unit:
    def __init__(self):
        self.numb = id(self)
        self.team = random.randint(1,2)
 
class soldier(unit):
    def go_with_hero(self, hero = 0):
        return(id(hero), id(self))
class hero(unit):
    lvl = 0
    def __init__(self, team):
        unit.__init__(self)
        self.team = team
    def level_up(self):
        self.lvl += 1
Super = hero(1)
Puper = hero(2)
Super_team = []
Puper_team = []
for i in range(int(input("Введите количество солдат: "))):
    i = soldier()
    if i.team == 1:
        Super_team.append(i)
    else:
        Puper_team.append(i)
if len(Super_team) > len(Puper_team):
    Super.level_up()
elif len(Super_team) < len(Puper_team):
    Puper.level_up()
else:
    print("Герои имеют равные силы!!")
legend_team = Super_team[0].go_with_hero(Super)
print("Уровен героя Super - {}\nУровен героя Puper - {}".format(Super.lvl, Puper.lvl))
print("Два великих воина - {} и {} отправилис навстречу подвигам!!!".format(Super_team[0].go_with_hero(Super)[0],Super_team[0].go_with_hero(Super)[1]))   

import random
 
class unit:
    def __init__(self, id, team):
        self.id = id
        self.team = team
 
class soldier(unit):
    def follow(self, hero):
        print "Soldier with ID {} is following Hero with ID {}".format(self.id, hero.id)
 
class hero(unit):
    def __init__(self, id, team):
        unit.__init__(self, id, team)
        self.level = 0
 
    def levelup(self, team_size):
        self.level = team_size / 10
 
teams = ['good_team', 'bad_team']
gandalf = hero(1, teams[0])
sauron = hero(2, teams[1])
 
soldiers = []
good_team = [gandalf]
bad_team = [sauron]
 
for i in range(1,1000):
    soldiers.append(soldier(i, random.choice(teams)))
 
for warrior in soldiers:
    if warrior.team == 'good_team':
        good_team.append(warrior)
    else:
        bad_team.append(warrior)
 
print "Good team size: {}, Bad team size: {}".format(len(good_team), len(bad_team))
 
gandalf.levelup(len(good_team))
sauron.levelup(len(bad_team))
 
print "Gandalf lvl {}, Sauron lvl {}".format(gandalf.level, sauron.level)
 
random.choice(good_team).follow(gandalf)

import random
class unit: #создаем родительский клас юнитов с атрибутами: номер и команда
    def __init__(self, n,team):
        self.n=n
        self.team=team
class heroes(unit): # создаем клас героев и добавляем атрибут уровень
    def __init__(self,name,n,team,level=0):
        self.name=name
        self.level = level
        self.team=team
    def level_up(self, incr): # создаем метод повышения уровня, который принимает аргумент "на сколько повышен уровень"
        self.level+=incr
 
class soldier(unit): # создаем клас солдата с методом следовать за героем
    def follow_heroes(self,heroes):
        print("\n"+"Для охраны своих владений герой {} выбрал опытного воина № {} и отправился с ним в поход.".format(heroes.name, self.n))
Tristan,Merlin=heroes("Tristan",1,"red"),heroes("Merlin",2,"green") # создаем два объекта героя
red_team=[] #создаем
green_team=[] #две коменды
quantity=int(input("Количество воинов:"))+1 # пользовательский ввод обозначает общее количество воинов
for i in range(1,quantity): #создам воинов и распределяем между командами героев
    t=random.randint(0,1)
    i=soldier(i,t)
    if i.team==0:
        red_team.append(i)
        i.team="red"
    else:
        green_team.append(i)
        i.team="green"
if len(red_team)>len(green_team): # определяем уровень героя в зависимости от того у кого больше солдат
    Tristan.level_up(1)
elif len(red_team)<len(green_team):
    Merlin.level_up(1)
if len(red_team) >= 10:
    Tristan.level_up(len(red_team) // 5)
if len(green_team)>=10:
    Merlin.level_up(len(green_team)//5)
 
# выводим на экран списки команд
print("В войске героя по имени",Tristan.name+",",str(Tristan.level)+"-го уровня,",len(red_team),"знатных воинов!")
for i in red_team:
    print(i.n, i.team, end=", ")
print("\n"+"В войске героя по имени",Merlin.name+",",str(Merlin.level)+"-го уровня,",len(green_team),"знатных воинов!")
for i in green_team:
    print(i.n,i.team, end=", ")
#выводим случайное имя героя и номер одного из солдат
who=random.randint(0,1)
if who==1:
    t=red_team
    h=Tristan
else:
    t=green_team
    h=Merlin
random.choice(t).follow_heroes(h)

import random
 
class Person:
    def __init__(self, team_id):
        self.id = id(self)
        self.team_id = team_id
    #def __del__(self):
    #    print(f"Я ({self.id}) из команды {self.team_id} покидаю Вас, до свидания.")
 
class Hero(Person):
    level = 0
    def level_up(self, delta = 0):
        if delta:
            self.level += delta
        else:
            self.level += 1
        print(f"Я герой ({self.id}) из команды {self.team_id}, мой уровень = {self.level}")
 
class Solder(Person):
    number = -1
    def set_number(self, number):
        self.number = number
    def to_hero(self, hero):
        self.hero = hero
    def folow_to_hero(self):
        try:
            print(f"Я {self.number} солдат ({self.id}) из команды {self.team_id} следую за героем ({self.hero.id}).")
        except AttributeError:
            print(f"Я {self.number} солдат ({self.id}) из команды {self.team_id} не имею героя.")
 
teams = []
 
teams_cnt = 2
for i in range(teams_cnt):
    teams.append([[], Hero(i+1)])
 
solder_cnt = 100
for i in range(solder_cnt):
    t = random.randint(1,teams_cnt)
    s = Solder(t)
    s.to_hero(teams[t-1][1])
    s.set_number(len(teams[t-1][0])+1)
    teams[t-1][0].append(s)
 
teams_sol_cnt = []
for i in range(teams_cnt):
    print(f"В команде {i+1}: {len(teams[i][0])} солдат(а)")
    teams_sol_cnt.append(len(teams[i][0]))
 
team_max = max(teams_sol_cnt)
for i in range(len(teams_sol_cnt)):
    if team_max == teams_sol_cnt[i]:
        teams[i][1].level_up()
 
rnd_t = random.randint(1,teams_cnt)-1
teams[rnd_t][0][random.randint(1,teams_sol_cnt[rnd_t])-1].folow_to_hero()
 
teams.clear()

import itertools
import random
 
class Unit:
  new_id = itertools.count()
  def __init__(self, command):
    self.id = next(self.new_id)
    self.command = command
 
class Soldier(Unit):
  def __init__(self, command):
    Unit.__init__(self, command)
 
  def move_to_hero(self, Hero):
    print('Moving for hero -', Hero.name, sep=' ')
 
 
class Hero(Unit):
  def __init__(self, command, name):
    Unit.__init__(self, command)
    self.name = name
    self.level = 1
 
  def level_up(self):
    self.level += 1
    print(self.name,', level up!')
 
commands = ['British', 'Norway']
 
h1 = Hero(commands[0], 'Olaf')
h2 = Hero(commands[1], 'Henrich')
 
british_army = []
norway_army = []
 
 
print(range(random.randint(14, 16), random.randint(30, 32)))
 
for x in range(random.randint(14, 16), random.randint(30, 32)):
  random_nations = random.choice(commands)
  if random_nations == 'British':
    british_army.append(Soldier(random_nations))
  else: 
    norway_army.append(Soldier(random_nations))
 
print('British - ',len(british_army))
 
print('Norway - ',len(norway_army))
 
if len(british_army) > len(norway_army):
  h2.level_up()
else:
  h1.level_up()
 
random_soldier = random.choice(british_army)
 
random_soldier.move_to_hero(h1)

from random import choice
 
class Unit:
    new_id = 0
    def __init__(self):
        self.id = Unit.new_id
        Unit.new_id += 1
 
 
class Soldier(Unit):
    def __init__(self):
        Unit.__init__(self)
    def move_to_hero(self, hero):
        print('Moving for hero -', hero.id)
 
 
class Hero(Unit):
    def __init__(self):
        Unit.__init__(self)
        self.level = 1
    def level_up(self):
        self.level += 1
        print(f'Level up!\nYour lvl: {self.level}')
 
COUNT_SOLDIER = 10
 
class Command:
    def __init__(self, name, hero):
        self.name = name
        self.hero = hero
        self.soldiers = []
    def add_soldier(self, soldier):
        self.soldiers.append(soldier)
    @property
    def count_soldier(self):
        return len(self.soldiers)
 
def loop(command_names):
    commands = []
    for name in command_names:
        new_team = Command(name, Hero())
        commands.append(new_team)
 
    i = 0
    while i < COUNT_SOLDIER:
        choice(commands).add_soldier(Soldier())
        i += 1
 
    for command in commands:
        count_soldier = command.count_soldier
        if count_soldier == 0:
            count_soldier = 'no soldiers'
        elif count_soldier < 2:
            count_soldier = str(count_soldier) + ' soldier'
        else:
            count_soldier = str(count_soldier) + ' soldiers'
 
        print(f'In command {command.name}: {count_soldier}')
 
    command_win = commands[0]
 
    for command in commands[1:]:
        count_soldiers = command.count_soldier
        if count_soldiers > command_win.count_soldier:
            command_win = command
 
    command_win.hero.level_up()
 
    if commands[0].count_soldier:
        hero_1 = commands[0].hero
        soldier_1 = commands[0].soldiers[0]
        soldier_1.move_to_hero(hero_1)
        print(f'Hero id: {hero_1.id}. Soldier id: {soldier_1.id}')
 
 
loop(['red', 'blue', 'green'])