Конструктор класса — метод __init__. Урок 3

Методическая разработка урока
Элективный курс: Введение в объектно-ориентированное программирование на Python
Уровень: Программирование для начинающих

Большинство классов имеют специальный метод, который автоматически при создании объекта создает ему атрибуты. Т.е. вызывать данный метод не нужно, т.к. он сам запускается при вызове класса. (Вызов класса происходит, когда создается объект.) Такой метод называется конструктором класса и в языке программирования Python носит имя __init__. (В начале и конце по два знака подчеркивания.)

Первым параметром, как и у любого другого метода, у __init__ является self, на место которого подставляется объект в момент его создания. Второй и последующие (если есть) параметры заменяются аргументами, переданными в конструктор при вызове класса.

Рассмотрим два класса: в одном будет использоваться конструктор, а в другом нет. Требуется создать два атрибута объекта.

class YesInit:
     def __init__(self,one,two):
          self.fname = one
          self.sname = two
 
obj1 = YesInit("Peter","Ok")
 
print (obj1.fname, obj1.sname)

class NoInit:
     def names(self,one,two):
          self.fname = one
          self.sname = two
 
obj1 = NoInit()
obj1.names("Peter","Ok")
 
print (obj1.fname, obj1.sname) 

Вывод интерпретатора в обоих случаях:

Peter Ok

В обоих программах у объекта появляются два атрибута: fname и sname. Однако в первом случае они инициализируются при создании объекта и должны передаваться в скобках при вызове класса. Если какие-то атрибуты должны присутствовать у объектов класса обязательно, то использование метода __init__ - идеальный вариант. Во второй программе (без использования конструктора) атрибуты создаются путем вызова метода names после создания объекта. В данном случае вызов метода names необязателен, поэтому объекты могут существовать без атрибутов fname и sname.

Обычно метод __init__ предполагает передачу аргументов при создании объектов, однако аргумент может не быть передан. Например, если в примере выше создать объект так: obj1 = YesInit(), т.е. не передать классу аргументы, то произойдет ошибка. Чтобы избежать подобных ситуаций, можно в методе __init__ присваивать параметрам значения по умолчанию. Если при вызове класса были заданы аргументы для данных параметров, то хорошо — они и будут использоваться, если нет — еще лучше — в теле метода будут использованы значения по умолчанию. Пример:

class YesInit:
     def __init__(self,one="noname",two="nonametoo"):
          self.fname = one
          self.sname = two
 
obj1 = YesInit("Sasha","Tu")
obj2 = YesInit()
obj3 = YesInit("Spartak")
obj4 = YesInit(two="Harry")
 
print (obj1.fname, obj1.sname)
print (obj2.fname, obj2.sname)
print (obj3.fname, obj3.sname)
print (obj4.fname, obj4.sname)

Вывод интерпретатора:

Sasha Tu
noname nonametoo
Spartak nonametoo
noname Harry

В данном случае, второй объект создается без передачи аргументов, поэтому в методе __init__ используются значения по умолчанию ("noname" и "nonametoo"). При создании третьего и четвертого объектов передаются по одному аргументу. Если указывается значение не первого аргумента, то следует явно указать имя параметра (четвертый объект).

Метод __init__ может содержать параметры как без значений по умолчанию, так и со значениями по умолчанию. В таком случае, параметры, аргументы которых должны быть обязательно указаны при создании объектов, указываются первыми, а параметры со значениями по умолчанию — после. Например, ниже вторая программа с ошибкой:

class fruits:
     def __init__(self,w,n=0):
          self.what = w
          self.numbers = n
 
f1 = fruits("apple",150)
f2 = fruits("pineapple")
 
print (f1.what,f1.numbers)
print (f2.what,f2.numbers)

class fruits:
     def __init__(self,n=0,w): #ERROR
          self.what = w
          self.numbers = n
 
f1 = fruits(150,"apple")
f2 = fruits("pineapple")
 
print (f1.what,f1.numbers)
print (f2.what,f2.numbers)

Напишем более существенную программу с использованием конструктора. Допустим это будет класс, значение начальных атрибутов (из метода __init__) которого зависит от переданных аргументов при создании объектов. Далее эти свойства объектов, созданных на основе данного класса, можно менять с помощью обычных методов.

class Building:
     def __init__(self,w,c,n=0):
          self.what = w
          self.color = c
          self.numbers = n
          self.mwhere(n)
 
     def mwhere(self,n):
          if n <= 0:
               self.where = "отсутствуют"
          elif 0 < n < 100:
               self.where = "малый склад"
          else:
               self.where = "основной склад"
 
     def plus(self,p):
          self.numbers = self.numbers + p
          self.mwhere(self.numbers)
     def minus(self,m):
          self.numbers = self.numbers - m
          self.mwhere(self.numbers)
 
m1 = Building("доски", "белые",50)
m2 = Building("доски", "коричневые", 300)
m3 = Building("кирпичи","белые")
 
print (m1.what,m1.color,m1.where)
print (m2.what,m2.color,m2.where)
print (m3.what,m3.color,m3.where)
 
m1.plus(500)
print (m1.numbers, m1.where)

В данном примере значение атрибута where объекта зависит от значения атрибута numbers.

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

  1. Спишите представленные выше скрипт с классом Building. Запустите программу, объясните как она работает. В какой момент создается атрибут where объектов? Зачем потребовалось конструкцию if-elif-else вынести в отдельную функцию, а не оставить ее в методе __init__?
  2. Самостоятельно придумайте класс, содержащий конструктор. Создайте на его основе несколько объектов.

Подскажите, пожалуйста,

Подскажите, пожалуйста, кто-нибудь! Я хочу, чтобы значение s определялось в зависимости от значения t. Не могу понять, почему не работает.

class Repa:
    def __init__(self,t,s):
        self.time=t
        self.stoimost=s
        self.zena_rep(t)
        self.ozenka(s)
 
    def zena_rep(self,t):
        if t=0:
            s=0
        elif t=1:
            s=200
        elif t=2:
            s=400
        else:
            s=999
 
    def ozenka(self,s):
        if s<=0:
            self.ozenka="Халява!"
        elif 100<s<300:
            self.ozenka="Дешево"
        elif 300<s<600:
            self.ozenka="Дорого"
        else:
            self.ozenka="ужас, как дорого!"
 
 
 
repa1=Repa(int(input("сколько будем репать? ")))
 
print(repa1.ozenka)

1. У тебя указано 2 аргумента

1. У тебя указано 2 аргумента а даётся, при создании объекта, один.
2. В методе zena_rep стоит знак присваивания (=) а не сравнения (==), должно быть так: t==0 и т.д.
3. Тебе не нужен второй аргумент, если ты хочешь, чтобы цена зависела от времени.
4. Нужно задать в методе __init__ переменную self.s
5. Да, нужно писать в zena_rep не s, а self.s

class Repa:
    def __init__(self,t):
        self.s = 0
        self.time = t
        self.zena_rep(t)
        self.vozenka(self.s)
 
    def zena_rep(self,t):
        if t == 0:
            self.s = 0
        elif t == 1:
            self.s = 200
        elif t == 2:
            self.s = 400
        else:
            self.s = 999
 
    def vozenka(self, s):
        if s <= 0:
            self.ozenka="Халява!"
        elif 100 < s < 300:
            self.ozenka="Дешево"
        elif 300 < s < 600:
            self.ozenka="Дорого"
        else:
            self.ozenka="ужас, как дорого!"
 
repa1=Repa(int(input("сколько будем репать? ")))
 
print(repa1.ozenka)

В методе zena_rep надо

В методе zena_rep надо использовать self.s

Все норм? class

Все норм?

class Government(object):
 
    def __init__(self, army=0, workers=0, scientists=0):
        self.army = army
        self.workers = workers
        self.scientists = scientists
        self.people_counter(army, workers, scientists)
 
    def people_counter(self, *args):
        for elem in args:
            if elem == self.army:
                if self.army == 0:
                    self.counter_ar = 'Нет бойцов'
                else:
                    self.counter_ar = 'Количество бойцов:{0}'.format(self.army)
            elif elem == self.workers:
                if self.workers == 0:
                    self.counter_wor = 'Нет рабочих'
                else:
                    self.counter_wor = 'Количество рабочих:{0}'.format(self.workers)
            elif elem == self.scientists:
                if self.scientists == 0:
                    self.counter_sci = 'Нет ученых'
                else:
                    self.counter_sci = 'Количество ученых:{0}'.format(self.scientists)
 
    def __str__(self):
        return '{0}, {1}, {2}'. \
            format(self.counter_ar, self.counter_wor, self.counter_sci)
 
People = Government(10, 0, 20)
print(People)

class People: def

class People:
    def __init__(self, sex = None, age = None, race = None):
        self.sex = sex
        self.age = age
        self.race = race
        self.what_status(age)
       #self.what_status(self.age) <-- ?
 
    def what_status(self, age):
        if 0 <= age < 30:
            self.status = "Молодой"
        elif 30 <= age < 50:
            self.status = "Зрелый"
        elif 50 <= age < 60:
            self.status = "Пристарелый"
        elif 60 <= age <= 120:
            self.status = "Старый"
        else:
            self.status = "Не человек"
 
    def plus_age(self, increase):
        self.age += increase
        self.what_status(self.age)
 
    #Вряд ли можно уменьшить возраст...
 
Man = People("Мужчина", 50, "Нигер")
print(Man.status, Man.sex, Man.race)
Man.plus_age(80)
print(Man.status, Man.sex, Man.race)
 
    #Не учитывается пол (Молодой Девушка Азиат)

Никак не могу понять эту

Никак не могу понять эту часть программы

def plus(self,p):
          self.numbers = self.numbers + p
          self.mwhere(self.numbers)
     def minus(self,m):
          self.numbers = self.numbers - m
          self.mwhere(self.numbers)

и значение вот этого:
m1.plus(500)

Если можно, укажите пальцом на мою ошибку

#!/usr/bin/env python
# -*- coding: utf-8 -*- 
 
class exams:
    def __init__(self,p,s=0):
        self.predmet = p
        self.stoimost = s
        self.jadnost_prepoda(s)
 
        def jadnost_prepoda(self,s):
            if s <= 0:
                self.jadnost_prepoda = 'Чистоплотный преподаватель!!!'
            elif 200 < s <= 400:
                self.jadnost_prepoda = 'В пределах нормы'
            elif 400 < s > 500:
                self.jadnost_prepoda = 'Еще куда не шло'
            else:
                self.jadnost_prepoda = 'Борзая!!!'
 
 subject1 = exams('Геология', 200)
 subject2 = exams('Начертательная геометрия', 999)
 subject3 = exams('Высшая математика')
 
 print (subject1.predmet,subject1.stoimost,subject1.jadnost_prepoda)
 print (subject2.predmet,subject2.stoimost,subject2.jadnost_prepoda)
 print (subject3.predmet,subject3.stoimost,subject3.jadnost_prepoda)
 

Скрипт не работает по многим

Скрипт не работает по многим причинам

class exams:
    def __init__(self,p,s=0):
        self.predmet = p
        self.stoimost = s
        self.jadnost_prepoda(s)
 
        def jadnost_prepoda(self,s): # Сейчас это определение внутри __init__ это Ошибка
            if s <= 0:
                self.jadnost_prepoda = 'Чистоплотный преподаватель!!!'
            elif 200 < s <= 400:#Нет отрезка от 0 до 200 включительно - 
# при 100, например, jadnost_prepoda будет - 'Борзая!!!'
                self.jadnost_prepoda = 'В пределах нормы'
            elif 400 < s > 500:# Наверно хотел написать  400 < s < 500 
                self.jadnost_prepoda = 'Еще куда не шло'
            else:
                self.jadnost_prepoda = 'Борзая!!!'
# Питон очень чувствителен  к отступам в начале строчки  
 subject1 = exams('Геология', 200)#перед строчкой надо  убрать пробел это Ошибка
 subject2 = exams('Начертательная геометрия', 999)#перед строчкой надо  убрать пробел это Ошибка
 subject3 = exams('Высшая математика')#перед строчкой надо  убрать пробел это Ошибка
 
 print (subject1.predmet,subject1.stoimost,subject1.jadnost_prepoda)#перед строчкой надо  убрать пробел это Ошибка
 print (subject2.predmet,subject2.stoimost,subject2.jadnost_prepoda)#перед строчкой надо  убрать пробел это Ошибка
 print (subject3.predmet,subject3.stoimost,subject3.jadnost_prepoda)#перед строчкой надо  убрать пробел это Ошибка

Правильно работающий скрипт будет:
class exams:
    def __init__(self,p,s=0):
        self.predmet = p
        self.stoimost = s
        self.jadnost_prepoda(s)
 
    def jadnost_prepoda(self,s):
        if s <= 0:
            self.jadnost_prepoda = 'Чистоплотный преподаватель!!!'
        elif 0 < s <= 400:
            self.jadnost_prepoda = 'В пределах нормы'
        elif 400 < s < 500:
            self.jadnost_prepoda = 'Еще куда не шло'
        else:
            self.jadnost_prepoda = 'Борзая!!!'
 
subject1 = exams('Геология', 200)
subject2 = exams('Начертательная геометрия', 999)
subject3 = exams('Высшая математика')
 
print (subject1.predmet,subject1.stoimost,subject1.jadnost_prepoda)
print (subject2.predmet,subject2.stoimost,subject2.jadnost_prepoda)
print (subject3.predmet,subject3.stoimost,subject3.jadnost_prepoda)

400 < s > 500 Может в обоих

400 < s > 500

Может в обоих случаях знак <

Как-то примеров мало, скину

Как-то примеров мало, скину свой:

class Kitchen:
    def __init__(self, fridge = "cold", oven = "hot"):
        self.one = fridge
        self.two = oven
 
    def colorful(self, color):
        self.three = color
 
fridge_temp = Kitchen()
oven_temp = Kitchen()
 
fridge_color = Kitchen()
oven_color = Kitchen()
 
fridge_color.colorful("white")
oven_color.colorful("black")
 
print("My fridge has " + fridge_color.three + "  color and inside it is " + fridge_temp.one + ".")
print("My oven has %s color and inside it is %s." % (oven_color.three, oven_temp.two))

А какой смысл в вынесении

А какой смысл в вынесении if-elif-else в отдельную функцию, я так и не понял..

Потому, что атрибут where

Потому, что атрибут where меняется еще в методах plus и minus. Иначе пришлось бы в них также писать if-else

Вот по аналогии, вводятся

Вот по аналогии, вводятся значения вчерашнего курса валюты )), и сегодняшнего, считается баланс,делается прогноз

class Valuta:
	def __init__(self, y, t):
		self.yest_k = y
		self.today_k = t
		self.soot(y,t)
	def soot(self, y,t):
		self.balans = (t*100)/y-100
		if self.balans > 0:
			self.prognoz = 'rost, not sale'
		else:
			self.prognoz = 'padenie, prodaem'
	def cmena_kursa(self, y, t):
		self.yest_k = y
		self.today_k = t
		self.soot(self.yest_k, self.today_k)
 
 
>>> sun = Valuta(32.1, 30.5)
>>> print (sun.yest_k, sun.today_k, sun.balans, sun.prognoz)
32.1 30.5 -4.984423676012469 padenie, prodaem
>>> sun.cmena_kursa(29.1, 30.5)
>>> print (sun.yest_k, sun.today_k, sun.balans, sun.prognoz)
29.1 30.5 4.81099656357388 rost, not sale

Без строки:
self.soot(self.yest_k, self.today_k)

не работает, получается, все атрибуты и функции описанные в __init__,
срабатывают сразу и только при создании объекта, функции ищут свое
"описание" ниже, "не в __init__-функциях".
Затем же, чтобы работали не __init__ функции, менялись атрибуты, нашим нововведенным значениям нужно присваивать "внутренний" атрибут self,
соответственно, например функция
def soot(self, y,t):

если говорить грубо, преобразовывается в
def self.soot(self, self.yest_k, self.today_k):

а в self.yest_k и self.today_k мы уже записали новый y и t.

Ну, если я правильно, конечно, понял))

Проще говоря, метод soot

Проще говоря, метод soot вызывается из cmena_kursa для перезаписи свойств balans и prognoz.

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

С точки зрения реального программирования, этот код (как и пример в уроке) опасен тем, что soot можно вызвать за пределами класса через объект:

sun.soot(10,12)

При этом значения свойств yest_k и today_k не поменяются, а баланс и прогноз - да.

Чтобы такого не происходило, в других языках методы делят на защищенные и публичные. В Python подобной инкапсуляции нет. Хотя есть специальное соглашение при именовании методов (добавление подчеркивания), использование которого говорит программисту, что метод не предназначен для вызова извне. Но все равно вызвать такой метод будет можно.

чем являются fname и sname?

class YesInit:
 def __init__(self,one,two):
  self.fname = one
  self.sname = two
obj1 = YesInit("Peter","Ok")
print(obj1.fname,obj1.sname)

1) как я понял два метода __init__ быть не может?
2) а чем являются fname и sname ? атрибутами которым передаются значения one, two?
fname и sname это как отдельные переменные?

1) Может, но в подавляющем

1) Может, но в подавляющем ряде случаев в этом нет смысла. Второй метод __init__ переопределит первый, т.е. из двух будет рабочим один - тот, который определен позже.

Также есть особенности наследования методов и свойств у классов, связанных отношениями родитель-потомок.

2) Переменная - это имя, связанное с областью памяти, в которой хранится какое-либо значение, структура данных или объект. Поэтому в данном случае one, two, fname, sname, obj1 - это переменные. По смыслу переменная obj1 связана с целым объектом (ссылается на него в понятиях языка Python). Переменные fname и sname ссылаются на конкретные свойства (атрибуты) объекта. Эти свойства (атрибуты, поля) описаны в теле класса. У объекта могут быть свойства, не описанные в классе, от которого он был создан.

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

Замечания от Дмитрия

В этом месте собки наверное лишние, без них выводится именно два слова. со скобками выводится список...
print (obj1.fname, obj1.sname)

Это зависит еще от версии

Это зависит еще от версии самого Python'а. Например в python 3 и выше, скобки к print обязательны.