kovalibre / electro

My first Python programm
1 stars 0 forks source link

Implementation example #10

Open sentyaev opened 6 years ago

sentyaev commented 6 years ago

Я накидал пример как бы я решал задачу.

# Это сам счетчик
class Meter:
    def __init__(self, readings):
        self.readings = readings

    # возвращает кол-во электроэнергии для оплаты
    def consumption(self):
        if len(self.readings) == 0:
            return 0

        self.readings.sort(key=lambda x: x.date)
        if len(self.readings) == 1:
            return self.readings[0].val

        return self.readings[-1].val - self.readings[-2].val

# Этот класс хранит показаня счетчика на определенную дату
class MeterReading:
    def __init__(self, val, date):
        self.val = val
        self.date = date

class Apartment:
    def __init__(self, number, rooms, meter):
        self.number = number
        self.rooms = rooms
        self.meter = meter

    def number_of_people(self):
        return sum([r.number_of_people for r in self.rooms])

    def rooms_consumption(self):
        return sum([r.consumption() for r in self.rooms])

    def consumption(self):
        return self.meter.consumption() - self.rooms_consumption()

    def debt(self):
        return sum([r.debt for r in self.rooms])

class Room:
    debt = 0

    def __init__(self, number, number_of_people, meter):
        self.number = number
        self.number_of_people = number_of_people
        self.meter = meter

    def consumption(self):
        return self.meter.consumption()

class Debt:
    def __init__(self, apartment_id, room_id, debt):
        self.apartment_id = apartment_id
        self.room_id = room_id
        self.debt = debt

# Отчет
class ConsumptionReport:
    def __init__(self, apartment, tariff, month):
        self.apartment = apartment
        self.tarif = tariff
        self.month = month

    @property
    def rooms_cost(self):
        return [(r.consumption() * self.tarif) + r.debt for r in
                self.apartment.rooms]

    @property
    def rooms_communal_cost(self):
        people = self.apartment.number_of_people()
        return [((self.apartment.consumption() / people) * r.number_of_people) * self.tarif
                for r in self.apartment.rooms]

    @property
    def communal_cost(self):
        apartment_consumption = self.apartment.consumption()
        return apartment_consumption * self.tarif

    @property
    def full_cost(self):
        full_consumption = self.apartment.consumption() + self.apartment.rooms_consumption()
        return (full_consumption * self.tarif) + self.apartment.debt()

    def print(self, writer):
        pass

class ConsoleConsumptionReport(ConsumptionReport):
    def print(self, writer):
         # собственно тут можно добавить логику вывода на экран
        pass

Ну а вот пример использования:

april = datetime.date(2018, 4, 1)
    may = datetime.date(2018, 5, 1)
    tariff = 10
    meter1_readings = [MeterReading(200, may), MeterReading(100, april)]
    meter2_readings = [MeterReading(50, may), MeterReading(20, april)]
    meter3_readings = [MeterReading(50, may), MeterReading(30, april)]

    meter1 = Meter(meter1_readings)
    meter2 = Meter(meter2_readings)
    meter3 = Meter(meter3_readings)

    room1 = Room('#1', 2, meter2)
    room1.debt = 42
    room2 = Room('#2', 1, meter3)

    apartment = Apartment('#42', [room1, room2], meter1)

    report = ConsoleConsumptionReport(apartment, tariff, month=5)
    report.print(writer=print)

    # проверки
    assert report.communal_cost == 500
    assert report.full_cost == 1042
    assert report.rooms_cost[0] == 342
    assert report.rooms_cost[1] == 200
    x = ((50/3)*2)*10
    y = ((50/3)*1)*10
    assert report.rooms_communal_cost[0] == pytest.approx(x)
    assert report.rooms_communal_cost[1] == pytest.approx(y)
kovalibre commented 6 years ago
def debt(self):
   return sum([r.debt for r in self.rooms])

Мне не обязательно считать суммарный долг по всей квартире. Дальше оно используется в ConsumptionReport.full_cost(), что в общем мне тоже не нужно.

class Room:
   debt = 0

Зачем тут "debt=0"?

class ConsumptionReport:
    def __init__(self, apartment, tariff, month):
        ...
        self.month = month

А тут атрибут "month"?

@property

Вот это клевый чит!)) Только почему ты его не используешь для методов в классах квартир и комнат, а только здесь? Это же декоратор, превращающий метод класса в аргумент, или я его не правильно понял?

report = ConsoleConsumptionReport(apartment, tariff, month=5)

Название класса здесь, полагаю, просто опечатка?

Круто и лаконично! Мне до этого еще учиться и учиться. Основная мысль, что я вынес из твоего примера, это то, что все вводимые данные ты загнал в соответствующие классы. Точнее, вообще почти все данные)

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

sentyaev commented 6 years ago

Твои замечания про dept & month правильные. Я просто по быстрому это накидал и скорее всего что-то забыл, что-то зря добавил.

Про @property - это называется "свойство". Удобно помечать им методы которые как-бы не методы, а свойства. Обычно легко понять свойство это или метод так: метод - это глагол, свойство - существительное. Т.е вес, рост, цвет глаз человека это свойства, а ходить, говорить, плавать - методы.

Тут нет опечатки ConsoleConsumptionReport. ConsumptionReport - базовый класс, у него пустой метод print. ConsoleConsumptionReport - его наследник, в нем уже определен метод print и он печатает в консоль. Можно сделать еще одного наследника, например FileConsumptionReport и его метод print определить так, чтобы он печатал в файл. Можно было сделать еще круче, но я уже не стал, но можно сделать так:

# Отчет
class ConsumptionReport:
    def __init__(self, apartment, tariff, month):
        self.apartment = apartment
        self.tarif = tariff
        self.month = month

    @property
    def rooms_cost(self):
        return [(r.consumption() * self.tarif) + r.debt for r in
                self.apartment.rooms]

    @property
    def rooms_communal_cost(self):
        people = self.apartment.number_of_people()
        return [((self.apartment.consumption() / people) * r.number_of_people) * self.tarif
                for r in self.apartment.rooms]

    @property
    def communal_cost(self):
        apartment_consumption = self.apartment.consumption()
        return apartment_consumption * self.tarif

    @property
    def full_cost(self):
        full_consumption = self.apartment.consumption() + self.apartment.rooms_consumption()
        return (full_consumption * self.tarif) + self.apartment.debt()

    def print(self, writer):
        writer.print(self)

class ConsoleWriter:
    def print(self, report):
         print(report.full_cost)
         print(report.another_stuff)

class FileWriter:
  def print(self, report):
     # store report to file
     pass

# А использовать можно так
report = ConsumptionReport(apartment, tariff, month=5)
report.print(writer=ConsoleWriter())
report.print(writer=FileWriter())

Такое разделение полезно, т.к. не изменяя класс ConsumptionReport ты можешь добавить сколько угодно новых Writer's.