Задача «Классы и наследование» Решение

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

Предположим есть данные о разных автомобилях и спецтехнике. Данные представлены в виде таблицы с характеристиками. Вся техника разделена на три вида: спецтехника, легковые и грузовые автомобили. Обратите внимание на то, что некоторые характеристики присущи только определенному виду техники. Например, у легковых автомобилей есть характеристика «кол-во пассажирских мест», а у грузовых автомобилей — габариты кузова: «длина», «ширина» и «высота».

Тип (car_type) Марка (brand) Кол-во пассажирских мест (passenger_seats_count) Фото (photo_file_name) Кузов ДxШxВ, м (body_whl) Грузоподъемность, Тонн (carrying) Дополнительно (extra)
car Nissan xTtrail 4 f1.jpeg 2.5
truck Man f2.jpeg 8x3x2.5 20
car Mazda 6 4 f3.jpeg 2.5
spec_machine Hitachi f4.jpeg 1.2 Легкая техника для уборки снега

 

Вам необходимо создать свою иерархию классов для данных, которые описаны в таблице. Классы должны называться CarBase (базовый класс для всех типов машин), Car (легковые автомобили), Truck (грузовые автомобили) и SpecMachine (спецтехника). Все объекты имеют обязательные атрибуты:

— car_type, значение типа объекта и может принимать одно из значений: «car», «truck», «spec_machine».

— photo_file_name, имя файла с изображением машины, допустимы названия файлов изображений с расширением из списка: «.jpg», «.jpeg», «.png», «.gif»

— brand, марка производителя машины

— carrying, грузоподъемность

В базовом классе CarBase нужно реализовать метод get_photo_file_ext для получения расширения файла изображения. Расширение файла можно получить при помощи os.path.splitext.

Для грузового автомобиля необходимо в конструкторе класса определить атрибуты: body_lengthbody_widthbody_height, отвечающие соответственно за габариты кузова — длину, ширину и высоту. Габариты передаются в параметре body_whl (строка, в которой размеры разделены латинской буквой «x»). Обратите внимание на то, что характеристики кузова должны быть вещественными числами и характеристики кузова могут быть не валидными (например, пустая строка). В таком случае всем атрибутам, отвечающим за габариты кузова, присваивается значение равное нулю.

Также для класса грузового автомобиля необходимо реализовать метод get_body_volume, возвращающий объем кузова.

В классе Car должен быть определен атрибут passenger_seats_count (количество пассажирских мест), а в классе SpecMachine — extra (дополнительное описание машины).

Полная информация о атрибутах классов приведена в таблице ниже, где 1 — означает, что атрибут обязателен для объекта, 0 — атрибут должен отсутствовать.

Car Truck SpecMachine
car_type 1 1 1
photo_file_name 1 1 1
brand 1 1 1
carrying 1 1 1
passenger_seats_count 1 0 0
body_width 0 1 0
body_height 0 1 0
body_length 0 1 0
extra 0 0 1

 

Обратите внимание, что у каждого объекта из иерархии должен быть свой набор атрибутов и методов. Например, у класса легковой автомобиль не должно быть метода get_body_volume в отличие от класса грузового автомобиля. Имена атрибутов и методов должны совпадать с теми, что описаны выше.

Далее вам необходимо реализовать функцию get_car_list, на вход которой подается имя файла в формате csv. Файл содержит данные, аналогичные строкам из таблицы. Вам необходимо прочитать этот файл построчно при помощи модуля стандартной библиотеки csv. Затем проанализировать строки на валидность и создать список объектов с автомобилями и специальной техникой. Функция должна возвращать список объектов.

Вы можете использовать для отладки работы функции get_car_list следующий csv-файл:

 

Первая строка в исходном файле — это заголовок csv, который содержит имена колонок. Нужно пропустить первую строку из исходного файла. Обратите внимание на то, что в некоторых строках исходного файла , данные могут быть заполнены некорректно, например, отсутствовать обязательные поля или иметь не валидное значение. В таком случае нужно проигнорировать подобные строки и не создавать объекты. Строки с пустым или не валидным значением для body_whl игнорироваться не должны.  Вы можете использовать стандартный механизм обработки исключений в процессе чтения, валидации и создания объектов из строк csv-файла. Проверьте работу вашего кода с входным файлом, прежде чем загружать задание для оценки.

Пример кода, демонстрирующего чтение csv файла:

import csv
with open(csv_filename) as csv_fd:
    reader = csv.reader(csv_fd, delimiter=’;’)
    next(reader)  # пропускаем заголовок
    for row in reader:
        print(row)
Ниже приведен шаблон кода для выполнения задания:
class CarBase:
    def __init__(self, brand, photo_file_name, carrying):
        pass
class Car(CarBase):
    def __init__(self, brand, photo_file_name, carrying, passenger_seats_count):
        pass
class Truck(CarBase):
    def __init__(self, brand, photo_file_name, carrying, body_whl):
        pass
class SpecMachine(CarBase):
    def __init__(self, brand, photo_file_name, carrying, extra):
        pass
def get_car_list(csv_filename):
    car_list = []
    return car_list
Несколько примеров работы:
from solution import *
>>> car = Car(‘Bugatti Veyron’, ‘bugatti.png’, ‘0.312’, ‘2’)
>>> print(car.car_type, car.brand, car.photo_file_name, car.carrying,
… car.passenger_seats_count, sep=’\n’)
car
Bugatti Veyron
bugatti.png
0.312
2
>>> truck = Truck(‘Nissan’, ‘nissan.jpeg’, ‘1.5’, ‘3.92×2.09×1.87’)
>>> print(truck.car_type, truck.brand, truck.photo_file_name, truck.body_length,
… truck.body_width, truck.body_height, sep=’\n’)
truck
Nissan
nissan.jpeg
3.92
2.09
1.87
>>> spec_machine = SpecMachine(‘Komatsu-D355’, ‘d355.jpg’, ’93’, ‘pipelayer specs’)
>>> print(spec_machine.car_type, spec_machine.brand, spec_machine.carrying,
… spec_machine.photo_file_name, spec_machine.extra, sep=’\n’)
spec_machine
Komatsu-D355
93.0
d355.jpg
pipelayer specs
>>> spec_machine.get_photo_file_ext()
‘.jpg’
>>> cars = get_car_list(‘cars_week3.csv’)
>>> len(cars)
4
>>> for car in cars:
…     print(type(car))
<class ‘solution.Car’>
<class ‘solution.Truck’>
<class ‘solution.Truck’>
<class ‘solution.Car’>
>>> cars[0].passenger_seats_count
4
>>> cars[1].get_body_volume()
60.0
>>>

Решение

import csv
import sys
import os.path


class CarBase:
    """Базовый класс с общими методами и атрибутами"""

    def __init__(self, brand, photo_file_name, carrying):
        # проверка что аргументы не являются пустой строкой
        if not all(i != '' for i in (brand, photo_file_name, carrying)):
            raise ValueError

        self.brand = brand
        self.photo_file_name = photo_file_name
        self.carrying = float(carrying)
        # вызов метода для проверки расширения файла изображения
        self.ext = self.get_photo_file_ext()

    def get_photo_file_ext(self):
        _, ext = os.path.splitext(self.photo_file_name)
        if ext not in ['.jpg', '.jpeg', '.png', '.gif']:
            raise ValueError
        return ext


class Car(CarBase):
    """Класс легковой автомобиль"""

    car_type = 'car'

    def __init__(self, brand, photo_file_name, carrying, passenger_seats_count):
        super().__init__(brand, photo_file_name, carrying)
        self.passenger_seats_count = int(passenger_seats_count)


class Truck(CarBase):
    """Класс грузовой автомобиль"""

    car_type = 'truck'

    def __init__(self, brand, photo_file_name, carrying, body_whl):
        super().__init__(brand, photo_file_name, carrying)
        # обрабатываем поле body_whl
        try:
            length, width, height = (float(c) for c in body_whl.split('x', 2))
        except ValueError:
            length, width, height = .0, .0, .0

        self.body_length = length
        self.body_width = width
        self.body_height = height

    def get_body_volume(self):
        return self.body_width * self.body_height * self.body_length


class SpecMachine(CarBase):
    """Класс спецтехника"""

    car_type = 'spec_machine'

    def __init__(self, brand, photo_file_name, carrying, extra):
        super().__init__(brand, photo_file_name, carrying)
        # проверка что аргумент extra не является пустой строкой
        if extra == '':
            raise ValueError
        self.extra = extra


def get_car_list(csv_filename):
    with open(csv_filename, encoding='utf-8') as csv_fd:
        # создаем объект csv.reader для чтения csv-файла
        reader = csv.reader(csv_fd, delimiter=';')

        # пропускаем заголовок csv
        next(reader)

        # это наш список, который будем возвращать
        car_list = []

        # объявим словарь, ключи которого - тип автомобиля (car_type),
        # а значения - функция, создающая экземпляр нужного класса
        car_types = {
            'car': lambda x: Car(x[1], x[3], x[5], x[2]),
            'truck': lambda x: Truck(x[1], x[3], x[5], x[4]),
            'spec_machine': lambda x: SpecMachine(x[1], x[3], x[5], x[6])}

        # обрабатываем csv-файл построчно
        for row in reader:
            try:
                car_type = row[0]
                # если тип машины в словаре - создаем экземпляр класса
                if car_type in car_types:
                    car_list.append(car_types[car_type](row))
            # при возникновении ошибки - пропускаем строку
            except (ValueError, IndexError):
                pass

    return car_list


if __name__ == '__main__':
    print(get_car_list(sys.argv[1]))