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

时间:2013-02-22 05:49:47

标签: python coding-style static-methods

何时以及如何在python中使用静态方法?我们已经建立了使用类方法作为工厂方法来创建对象的实例应该尽可能避免。换句话说,使用类方法作为替代构造函数并不是最佳实践(参见Factory method for python object - best practice)。

假设我有一个用于表示数据库中某些实体数据的类。想象一下,数据是一个包含字段名称和字段值的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 个答案:

答案 0 :(得分:23)

  

何时以及如何在python中使用静态方法?

滑稽的回答是:不常见。

即使是光滑的,但也没有那么无用的答案是:当它们使你的代码更具可读性时。


首先,让我们绕道前往the docs

  

Python中的静态方法与Java或C ++中的静态方法类似。另请参阅classmethod()以获取对创建备用类构造函数有用的变体。

所以,当你需要一个C ++中的静态方法时,你需要一个Python中的静态方法,对吧?

嗯,不。

在Java中,没有函数,只有方法,因此您最终创建的伪类只是静态方法的捆绑。在Python中执行相同操作的方法是使用自由函数。

这很明显。但是,对于一个合适的类来楔入一个函数来说,尽可能地努力寻找Java风格是好的,所以你可以避免编写那些伪类,同时做同样的事情就是糟糕的Python风格 - 再次使用自由函数 - 这个不太明显。

C ++与Java没有相同的限制,但无论如何,许多C ++样式都非常相似。 (另一方面,如果你是一个“现代C ++”程序员,内化了“自由函数是类接口的一部分”这个习惯用法,你对“静态方法有用”的直觉对于Python来说可能相当不错。)< / p>


但是,如果你是从第一原则而不是从另一种语言来实现的,那么有一种更简单的方法来看待事物:

@staticmethod基本上只是一个全局函数。如果您有一个函数foo_module.bar(),如果它被拼写为foo_module.BazClass.bar(),则由于某种原因会更具可读性,请将其设为@staticmethod。如果没有,不要。这就是它的全部内容。唯一的问题是建立你的本能,以获得一个惯用的Python程序员更可读的东西。

当然,当您需要访问该类时,请使用@classmethod,但实例 - 备用构造函数不是这样的范例,正如文档所暗示的那样。虽然你经常可以通过显式引用类来模拟@classmethod@staticmethod(特别是当你没有太多的子类)时,你不应该。


最后,回答您的具体问题:

如果客户需要按ID查找数据的唯一原因是构建Entity,这听起来像是一个你不应该暴露的实现细节,它也会使客户端代码变得更复杂。只需使用构造函数。如果您不想修改__init__(并且您认为有充分理由可能不想这样做),请使用@classmethod作为替代构造函数:Entity.from_id(id_number, db_connection)

另一方面,如果该查找对于与Entity构造无关的其他情况下客户端本身有用的东西,似乎这与Entity类无关(或者至少不超过同一模块中的任何其他内容)。所以,让它成为一个免费的功能。

答案 1 :(得分:11)

相关问题的答案具体说明了这一点:

  

@classmethod是执行“替代构造函数”的惯用方法 - 遍及stdlib-itertools.chain.from_iterable,datetime.datetime.fromordinal等的示例。

所以我不知道你是怎么认为使用classmethod本身就是坏事。我实际上喜欢在你的特定情况下使用classmethod的想法,因为它遵循代码并使用api easy。

替代方法是使用默认构造函数参数,如:

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相当模糊。

答案 2 :(得分:7)

你的第一个例子对我来说最有意义:Entity.from_id非常简洁明了。

它避免在接下来的两个例子中使用data,它没有描述返回的内容; data用于构建Entity。如果您想具体了解用于构建data的{​​{1}},那么您可以将方法命名为Entity或等效函数Entity.with_data_for_id

使用诸如entity_with_data_for_id之类的动词也可能相当混乱,因为它没有给出返回值的任何指示 - 当它找到数据时应该做什么功能? (是的,我意识到find有一个str方法;它不会更好地命名为find吗?但是那时还有index_of ...)它让我想起了经典:

find x

我总是试着思考一个名字会给某人带来什么样的表示:(a)不了解系统,以及(b)对系统其他部分的了解 - 并不是说​​我总是成功!

答案 3 :(得分:0)

这是@staticmethod的一个不错的用例。

我一直在做游戏作为辅助项目。该游戏的一部分包括基于统计数据的滚动骰子,以及拾取影响角色统计数据的项目和效果的可能性(无论好坏)。

当我在游戏中掷骰子时,我需要基本上说...获取基本角色统计数据,然后将任何库存和效果统计数据添加到这个大网状数据中。

您无法在不指示程序的情况下使用这些抽象对象并添加它们。我也没有在班级或实例级别做任何事情。我不想在某个全局模块中定义该函数。最后一个最佳选择是使用静态方法将统计信息加在一起。它只是以这种方式最有意义。

class Stats:

    attribs = ['strength', 'speed', 'intellect', 'tenacity']

    def __init__(self,
                 strength=0,
                 speed=0,
                 intellect=0,
                 tenacity=0
                 ):
        self.strength = int(strength)
        self.speed = int(speed)
        self.intellect = int(intellect)
        self.tenacity = int(tenacity)

    # combine adds stats objects together and returns a single stats object
    @staticmethod
    def combine(*args: 'Stats'):
        assert all(isinstance(arg, Stats) for arg in args)
        return_stats = Stats()
        for stat in Stats.attribs:
            for _ in args:
                setattr(return_stats, stat,
                        getattr(return_stats, stat) + getattr(_, stat))
        return (return_stats)

这会使统计组合调用像这样工作

a = Stats(strength=3, intellect=3)
b = Stats(strength=1, intellect=-1)
c = Stats(tenacity=5)
print(Stats.combine(a, b, c).__dict__)
  
    
      

{&#39;力量&#39;:4,&#39;速度&#39;:0,&#39;智力&#39;:2,&#39;坚韧&#39;:5}