Python类的使用风格与单一职责原则

6 投票
4 回答
1143 浏览
提问于 2025-04-17 15:30

我已经学习Python一段时间了,了解了一些Python的风格,但在如何正确使用类方面还是有些困惑。当我阅读面向对象的课程时,经常会看到一些规则,比如单一职责原则,它的意思是:

“单一职责原则说的是,一个类应该只有一个、并且仅有一个,改变的理由。”

看到这个,我可能会想到把一个类拆分成两个,比如:

class ComplicatedOperations(object):
    def __init__(self, item):
        pass

    def do(self):
        ...

    ## lots of other functions

class CreateOption(object):
    def __init__(self, simple_list):
        self.simple_list = simple_list

    def to_options(self):
        operated_data = self.transform_data(self.simple_list)
        return self.default_option() + operated_data

    def default_option(self):
        return [('', '')]

    def transform_data(self, simple_list):
        return [self.make_complicated_operations_that_requires_losts_of_manipulation(item)
                        for item in simple_list]

    def make_complicated_operations_that_requires_losts_of_manipulation(self, item):
            return ComplicatedOperations(item).do()

这让我产生了很多不同的问题,比如:

  • 我应该什么时候使用类变量,或者在类函数中传递参数呢?
  • ComplicatedOperations这个类应该是一个类,还是只是一堆函数呢?
  • __init__方法应该用来计算最终结果吗?这样会不会让这个类变得难以测试?
  • 对于Python程序员来说,有哪些规则呢?

在得到答案后编辑:

所以,阅读了Augusto的理论后,我会得到这样的结果:

class ComplicatedOperations(object):
    def __init__(self):
        pass

    def do(self, item):
        ...

    ## lots of other functions

def default_option():
    return [('', '')]

def complicate_data(item):
    return ComplicatedOperations().do(item)

def transform_data_to_options(simple_list):
    return default_option() + [self.complicate_data(item)
                    for item in simple_list]

(还修正了一个关于default_option的小错误。)

4 个回答

1

我什么时候应该使用类变量,或者在类函数中传递参数?

在你的例子中,我会把 item 作为参数传递给 do 方法。此外,这个原则适用于任何编程语言:给类提供它所需的信息(最小权限),而把那些不是你算法内部需要的东西通过参数传递(依赖注入)。所以,如果 ComplicatedOperations 不需要 item 来初始化自己,就不要把它作为初始化参数;如果它需要 item 来完成工作,那就把它作为参数传递。

ComplicatedOperations 类应该是一个类,还是只是一堆函数?

这要看情况。如果你在使用各种操作,并且它们有一些共同的接口或约定,那当然可以。如果这些操作反映了某个概念,并且所有方法都和这个类相关,那也没问题。但如果它们之间关系不大,你可能只需要用函数,或者重新考虑一下单一职责原则,把这些方法拆分到其他类中。

初始化方法应该用来计算最终结果吗?这样会让这个类难以测试吗?

不会,初始化方法是用来初始化的,你应该把具体的工作放在一个单独的方法里。

另外,由于缺乏上下文,我不太明白 CreateOption 的作用。如果它只是像上面那样使用,你可能可以直接把它去掉……

1

类是一种组织结构。所以,如果你不利用它们来进行组织,那就是在走错路。 :)

你可以用类来做几件不同的事情来帮助组织:

  1. 把数据和使用这些数据的方法放在一起,这样代码就能在一个地方处理这些数据。
  2. 把相似的功能放在一起,这样就能提供一个易于理解的接口,因为“大家都知道”所有的数学功能都在数学对象里。
  3. 提供方法之间的明确沟通,建立一个“传送带”式的操作流程,每个操作都是一个黑箱,只要遵循标准,它们可以随意改变。
  4. 抽象一个概念。这可以包括子类、数据、方法等等,围绕某个中心思想,比如数据库访问。这个类就成了你在其他项目中可以用的组件,修改的工作量很小。

如果你不需要像上面那样进行组织,那就应该追求简单,采用过程式或函数式编程。Python的核心在于拥有一个工具箱,而不是一把锤子。

1

我个人觉得类就像是一些概念。我会定义一个操作类(Operation),它的行为就像一个操作,所以里面会有一个 do() 方法,还有其他可能让它独特的方法和属性。

正如 mgilson 所说的,如果你无法清晰地定义和区分某个概念,也许用简单的函数式方法会更好。

来回答你的问题:

  • 当某个属性在多个实例之间是共享的时候,你应该使用类属性(在 Python 中,类属性是在编译时初始化的,所以不同的对象会看到相同的值。通常,类属性应该是常量)。使用实例属性来存储特定于对象的属性,这样在方法中使用时就不需要传递它们。这并不是说你应该把所有东西都放在 self 里,而是只放那些你认为能代表这个对象的属性。使用传入的变量来存储那些与对象无关的值,这些值可能依赖于外部对象的状态(或者程序的执行情况)。
  • 如上所述,我会保持一个单独的操作类(Operation),然后用一个操作对象的列表来进行计算。
  • init 方法只是用来实例化对象,并进行所有必要的处理,以确保对象的正常行为(换句话说,就是让它准备好使用)。
  • 只需考虑你想要建模的那些想法。

撰写回答