什么时候使用 class 和 Python 来组织代码比较合适?

When is it appropriate to organize code using a class with Python?

虽然我有很多使用 Python 的经验,但我发现有时很难确定相关函数和属性是否应该放在 class 中。更具体地说,我有一个函数使用 class 的属性,后面的函数依次使用前一个函数的 returned 值。例如函数 1 --> 函数 2 --> 函数 3 等等,每个函数 returning 一些东西。

我想知道在这种情况下使用 class 是否有意义,因为这对我来说很常见。我想确保对象 (sales table) 是以合乎逻辑且干净的方式创建的。

到目前为止,我只创建了一个带有一些属性和实例方法的简单 class。我不确定我还能怎么做。我查阅了很多关于 Stacks 的帖子、文章和许多其他资源。我相信我对 class 的目的有一个很好的理解,但在什么时候适合使用它方面却不太了解。

需要说明的是,我并不是在寻求函数本身或其逻辑方面的帮助(尽管我很感激任何建议!)。我只想知道使用 class 是否可行。我没有在函数中包含任何代码,因为我认为它们的逻辑与我的问题无关(如有必要,我可以添加!)

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.)"""

理想情况下,我喜欢 return 销售 table 通过最后一个函数 (combine_dfs) 遵循函数序列。我可以很容易地完成这项任务,但是,我不确定这是我构建脚本的最佳方式,或者它在逻辑上是否有意义,尽管它按我想要的方式工作。

如果一堆数据和函数似乎在一起,也就是说您通常同时引用它们,那么您有充分的理由认为您手上可能有一个对象。

另一个很好的理由是对象是否有一个自然名称。很奇怪,我知道,但这确实是一个有用的指导原则。

继续阅读 SOLID 也可能会给您一些启发。

理想情况下,class 有两个主要用途:

1) 防止重复。如果您多次创建同一个对象而不是 class.

2) 将事物组合在一起。如果所有相关的功能和属性都组合在一起,那么阅读某人的代码会容易得多。这也使 可维护性可移植性 更容易。

方法在 class 中相互调用是很常见的,因为理想情况下方法不应超过 30 行(尽管不同的组有不同的标准)。如果您仅从 class 中调用方法,那么该方法应该是 private 并且您应该在该方法之前附加 __(两个下划线)。

OOP 新手往往会创建太多 classes(我知道我一开始就是这样做的)。一个问题是代码可读性:当代码使用自定义 class 时,通常需要阅读 class 定义以弄清楚 class 应该做什么。如果代码只使用内置类型,通常更容易理解。此外,作为 classes 自然特征的复杂内部状态通常是细微错误的来源,并使代码更难以推理。

This book很有帮助

您上面的每个方法看起来都与 class 相关。因此,假设您在 class 之外定义了一堆函数,并且将同一组十个变量作为参数传递给它们中的每一个。那将是他们应该在 class 中的标志。访问和修改太多变量并将它们作为参数传递给其他函数而不是将它们作为 class 属性在每个方法内部进行修改将表明您未能利用其中的一个好处classes。在那本书中,我记得有一个部分详细介绍了您的代码需要 OOP 的各种迹象。

因为只有sales_periods实际使用了实例属性,而且它returns一个dict,而不是SalesTable的另一个实例,所有其他方法都可以移出的 class 并定义为常规函数:

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()))))

现在仔细看看你的class:你只有两种方法,__init__sales_periods。除非 __init__ 做了一些你不想重复的昂贵的事情(并且你会在同一个实例上多次调用 sales_periods),整个 class 可以简化为一个函数结合 __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(...)))))