在Python中使用静态方法 - 最佳实践

26 投票
4 回答
31742 浏览
提问于 2025-04-17 16:43

在Python中,静态方法应该在什么情况下使用呢?我们已经知道,尽量避免使用类方法作为工厂方法来创建对象实例。换句话说,使用类方法作为替代构造函数并不是最佳实践(可以参考Python对象的工厂方法 - 最佳实践)。

假设我有一个类,用来表示数据库中的某些实体数据。想象一下,这些数据是一个dict对象,里面包含了字段名称和字段值,其中有一个字段是ID号码,用来唯一标识这些数据。

class Entity(object):
    def __init__(self, data, db_connection):
        self._data = data
        self._db_connection

在这里,我的__init__方法接收这个实体数据的dict对象。假设我只有一个ID号码,我想创建一个Entity实例。首先,我需要找到其他的数据,然后再创建我的Entity对象。根据我之前的问题,我们已经确定尽量避免使用类方法作为工厂方法。

class Entity(object):

    @classmethod
    def from_id(cls, id_number, db_connection):
        filters = [['id', 'is', id_number]]
        data = db_connection.find(filters)
        return cls(data, db_connection)

    def __init__(self, data, db_connection):
        self._data = data
        self._db_connection


# Create entity
entity = Entity.from_id(id_number, db_connection)

上面的例子就是不应该怎么做,或者至少在有其他选择的情况下不应该这样做。现在我在想,如果把我的类方法改成一个更像工具方法而不是工厂方法,这样是否可行。换句话说,下面的例子是否符合使用静态方法的最佳实践。

class Entity(object):

    @staticmethod
    def data_from_id(id_number, db_connection):
        filters = [['id', 'is', id_number]]
        data = db_connection.find(filters)
        return data


# Create entity
data = Entity.data_from_id(id_number, db_connection)
entity = Entity(data)

或者,使用一个独立的函数来根据ID号码查找实体数据是否更合理呢。

def find_data_from_id(id_number, db_connection):
    filters = [['id', 'is', id_number]]
    data = db_connection.find(filters)
    return data


# Create entity.
data = find_data_from_id(id_number, db_connection)
entity = Entity(data, db_connection)

注意:我不想改变我的__init__方法。之前有人建议把我的__init__方法改成这样__init__(self, data=None, id_number=None),但实际上可能有101种不同的方法来查找实体数据,所以我更希望在某种程度上将这个逻辑分开。这样说你明白了吗?

4 个回答

9

你第一个例子对我来说最有意义:Entity.from_id 这个名字简洁明了。

它避免了在接下来的两个例子中使用 data,因为这个词并没有清楚地描述返回的内容;data 是用来构建一个 Entity 的。如果你想更具体地说明这个 data 是用来构建 Entity 的话,你可以把方法命名为 Entity.with_data_for_id 或者类似的 entity_with_data_for_id

使用像 find 这样的动词也可能让人困惑,因为它并没有说明返回的值是什么——当找到数据后这个函数应该做什么呢?(是的,我知道 str 有一个 find 方法;难道不应该叫 index_of 吗?但还有 index 呢……)这让我想起了经典的例子:

find x

我总是试着考虑一个名字对那些(a)对系统没有了解的人,以及(b)对系统其他部分有了解的人会传达什么——当然,我并不是总能成功!

16

这个回答专门提到了一点:

@classmethod 是一种常见的写法,用来做“备用构造函数”——在标准库里有很多例子,比如 itertools.chain.from_iterable、datetime.datetime.fromordinal 等等。

所以我不明白你为什么会觉得使用 classmethod 本身就是个坏主意。我其实很喜欢在你这种情况下使用 classmethod,因为这样可以让代码更容易理解,使用起来也方便。

另一种方法是使用默认的构造函数参数,像这样:

class Entity(object):
    def __init__(self, id, db_connection, data=None):
        self.id = id
        self.db_connection = db_connection
        if data is None:
            self.data = self.from_id(id, db_connection)
        else:
            self.data = data

    def from_id(cls, id_number, db_connection):
        filters = [['id', 'is', id_number]]
        return db_connection.find(filters)

不过我还是更喜欢你最开始写的 classmethod 版本。尤其是因为 data 这个名字有点模糊。

40

在Python中,静态方法应该什么时候用,怎么用呢?

简单来说:用得不多。

再简单一点的说法是:当它能让你的代码更易读时,就可以用。


首先,我们先看看官方文档

Python中的静态方法和Java或C++中的类似。还有classmethod(),它是一个变种,适合用来创建备用的类构造函数。

所以,如果在C++中需要静态方法,那在Python中也需要静态方法,对吧?

其实不是。

在Java中,只有方法,没有函数,所以你会创建一些伪类,里面全是静态方法。而在Python中,做同样的事情只需要用普通函数就可以了。

这点很明显。不过,在Java中,尽量找一个合适的类来放函数是个好习惯,这样可以避免写那些伪类;而在Python中,做同样的事情就不太好——还是用普通函数吧,这一点就不那么明显了。

C++没有Java那样的限制,但很多C++的风格还是挺相似的。不过,如果你是个“现代C++”程序员,已经习惯了“普通函数是类接口的一部分”这种说法,那么你对“静态方法在哪儿有用”这个问题的直觉可能对Python也挺有帮助。


但是如果你是从基础知识出发,而不是从其他语言过来的话,有一个更简单的看法:

@staticmethod基本上就是一个全局函数。如果你有一个函数foo_module.bar(),如果把它写成foo_module.BazClass.bar()会让人更容易理解,那就把它做成@staticmethod。如果没有,那就别做。其实就这么简单。唯一的问题是,你需要培养出对什么更易读的直觉,尤其是对Python程序员来说。

当然,当你需要访问类而不是实例时,就用@classmethod——备用构造函数就是一个典型的例子,文档中也提到了。虽然你通常可以通过显式引用类来模拟@classmethod,尤其是在没有太多子类的情况下,但最好还是不要这么做。


最后,针对你的具体问题:

如果客户只需要通过ID查找数据来构造一个Entity,那听起来像是一个不应该暴露的实现细节,而且这样会让客户的代码变得更复杂。直接用构造函数就行。如果你不想修改你的__init__(而且你说得对,确实有很多好理由不想修改),可以用@classmethod作为备用构造函数:Entity.from_id(id_number, db_connection)

另一方面,如果这个查找在其他情况下对客户是有用的,而和Entity的构造没有关系,那这就和Entity类没什么关系(或者说关系不大)。所以,直接把它做成一个普通函数就好。

撰写回答