Когда уместно организовать код с помощью класса с Python?

Хотя у меня есть большой опыт использования Python, я обнаружил, что иногда довольно сложно определить, следует ли помещать в класс соответствующие функции и атрибуты. Точнее, у меня есть функция, которая использует атрибуты класса, а следующие функции последовательно используют возвращаемое значение предыдущей функции. Например, Функция 1 --> Функция 2 --> Функция 3 и так далее, причем каждая функция что-то возвращает.

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

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

Чтобы было ясно, я не прошу помощи по самим функциям или их логике (хотя я ценю любые предложения!). Я просто хочу знать, подходит ли использование класса. Я не включал какой-либо код в функции, так как не думаю, что их логика имеет отношение к моему вопросу (при необходимости могу добавить!)

class SalesTable:

    def __init__(self, banner, start_year, start_month, end_year, end_month):
        """These attributes act as filters when searching for the relevant data."""
        self.banner = banner
        self.start_year = start_year
        self.start_month = start_month
        if not end_year:
            self.end_year = start_year
        else:
            self.end_year = end_year
        if not end_month:
            self.end_month = start_month
        else:
            self.end_month = end_month

    def sales_periods(self):
        """Will create a dict with a key as the year and each year will have a list of months as the value. The
        stated attributes are used ONLY here as filters to determine what years and months are included"""
        pass

    def find_sales_period_csv(self):
        """Using the dictionary returned from the function above, will search through the relevant directories and 
        subdirectories to find all the paths for individual csvs where the sales data is stored as determined by the
        value in the dictionary and store the paths in a list"""
        pass

    def csv_to_df(self):
        """Using the list returned from the function above, will take each csv path in the list and convert them into a
        dataframe and store those dateframes in another list"""
        pass

    def combine_dfs(self):
        """Using the list return from the function above, will concatenate all dfs into a single dataframe"""

    def check_data(self):
        """Maybe do some checking here to ensure all relevant data concatenated properly (i.e total row count etc.)"""

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

Удалите любой метод, который не использует self из класса. По сути, вы спрашиваете о композиции f(g(h(x))), которая не зависит от того, какие f, g или h являются методами или обычными функциями. Использует ли что-нибудь кроме sales_periods атрибуты SalesTable?

chepner 17.04.2019 17:17

И, если после исключения всех «обычных» функций из вашего класса, если у вас не останется ничего, кроме __init__ и еще одного метода, велика вероятность, что другой метод должен быть также просто функцией, которая принимает дополнительные параметры из текущего состояния. несуществующий метод __init__, и класс должен быть утилизирован.

chepner 17.04.2019 17:19

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

ShockDoctor 17.04.2019 17:33
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
3
1 683
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

Еще одна веская причина — если у объекта есть естественное имя. Странно, я знаю, но это действительно полезный руководящий принцип.

Чтение ТВЕРДЫЙ также может дать вам пищу для размышлений.

Спасибо за совет! Это «стол», поэтому я подумал, что он кажется естественным, хотя он кажется более абстрактным, чем реальным. Пойду читать, спасибо за ссылку!

ShockDoctor 17.04.2019 17:39

В идеале есть два основных варианта использования class:

1) Во избежание повторения. Если вы создаете один и тот же объект несколько раз, чем он должен быть в классе.

2) Группировать предметы. Читать чей-то код намного проще, если все связанные функции и атрибуты сгруппированы вместе. Это также упрощает ремонтопригодность и портативность.

Обычно методы вызывают друг друга внутри класса, поскольку в идеале методы не должны быть длиннее 30 строк (хотя разные группы имеют разные стандарты). Если вы вызываете методы только внутри класса, то этот метод должен быть private, и вы должны добавить __ (два символа подчеркивания) перед этим методом.

Таким образом, объект таблицы продаж будет создан только один раз, поэтому, основываясь на номере 1, я бы предположил, что класс будет неуместным. Однако мои функции и атрибуты связаны тем, что вам нужны атрибуты для первой функции и первой функции для второй и так далее, поэтому я думаю, что номер 2 применим. Это просто вопрос суждения? Я также буду помнить о подчеркиваниях! Спасибо!

ShockDoctor 17.04.2019 17:38

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

Error - Syntactical Remorse 17.04.2019 17:43

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

Error - Syntactical Remorse 17.04.2019 17:45

Единственное исключение, если я делаю быстрый/короткий скрипт, чтобы сделать что-то простое, например, переименовать все файлы в каталоге.

Error - Syntactical Remorse 17.04.2019 18:05

Люди, плохо знакомые с ООП, склонны создавать слишком много классов (я знаю, что делал это вначале). Одной из проблем является читабельность кода: когда в коде используется пользовательский класс, часто необходимо прочитать определение класса, чтобы понять, что этот класс должен делать. Если в коде используются только встроенные типы, разобраться обычно проще. Кроме того, сложное внутреннее состояние, которое является естественной особенностью классов, часто является источником незаметных ошибок и затрудняет анализ кода.

Понимаю. Таким образом, мне на самом деле нужно создать 4 таблицы, все разные с точки зрения данных, но в конечном итоге все они должны быть фреймами данных по отдельности (тот же вывод, что и выше). Я думал, что создание отдельного модуля для каждой таблицы, и каждый модуль будет иметь класс для создания экземпляра одного из них, упростит внесение изменений и переносимость. Не могли бы вы сказать, что просто сделать 4 разных модуля без классов будет правильным решением?

ShockDoctor 17.04.2019 17:45

Эта книга весьма полезен

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

Ответ принят как подходящий

Поскольку только sales_periods фактически использует атрибуты экземпляра и возвращает dict, а не другой экземпляр SalesTable, все остальные методы можно вынести из класса и определить как обычные функции:

class SalesTable:

    def __init__(self, banner, start_year, start_month, end_year, end_month):
        ...

    def sales_periods(self):
        # ...
        return some_dict


def find_sales_period_csv(dct):
    return some_list

def csv_to_df(lst):
    return some_list

def combine_dfs(lst):
    return some_df

def check_data(df):
    pass

И вы будете называть их всех по цепочке:

x = SalesTable(...)
check_data(combine_dfs(csv_to_df(find_sales_period_csv(x.sales_periods()))))

Теперь внимательно посмотрите на свой класс: у вас есть только два метода, __init__ и sales_periods. Если __init__ не делает что-то дорогое, что вы не хотите повторять (и вы бы вызывали sales_periods для одного и того же экземпляра несколько раз), весь класс можно свести к одной функции, которая объединяет __init__ и метод sales_period:

def sales_periods(banner, start_year, start_month, end_year, end_month):
    ...
    return some_dict

check_data(combine_dfs(csv_to_df(find_sales_period_csv(sales_periods(...)))))

Это имеет большой смысл. Было бы неуместно использовать статические методы в качестве альтернативы, поскольку они не используют никаких атрибутов?

ShockDoctor 17.04.2019 17:52

Вы могли бы, но эти функции не звучат так, как будто они имеют какое-либо отношение к SalesTable иначе. Статические методы обычно имеют менее общее назначение, поскольку они четко определены только как помощники для «обычных» методов или, по крайней мере, в некоторой степени связаны с классом, если не с каким-либо конкретным экземпляром класса.

chepner 17.04.2019 17:57

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

ShockDoctor 17.04.2019 17:59

Другие вопросы по теме