Using static methods in python - best practice
假设静态方法何时以及如何在Python中使用?我们已经建立了使用类方法作为工厂方法来创建一个对象实例的方法,应该尽可能避免。换句话说,将类方法用作备用构造函数不是最佳实践(请参见Python对象的工厂方法-最佳实践)。
假设我有一个类用来表示数据库中的一些实体数据。假设数据是一个包含字段名和字段值的
1 2 3 4 | class Entity(object): def __init__(self, data, db_connection): self._data = data self._db_connection |
这里,我的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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) |
上面是一个例子,说明如果有其他选择,什么是不该做的,或者至少是什么是不该做的。现在我想知道是否编辑我的类方法,使它更多的是一个实用方法,更少的工厂方法是一个有效的解决方案。换句话说,下面的示例是否符合使用静态方法的最佳实践。
1 2 3 4 5 6 7 8 9 10 11 12 | 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号中查找实体数据是否更有意义?
1 2 3 4 5 6 7 8 9 | 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) |
注:我不想更改我的
When and how are static methods suppose to be used in python?
油嘴滑舌的回答是:不经常。
即使是油嘴滑舌,但也不像无用的答案:当它们使代码更可读时。
首先,让我们绕道去文档:
Static methods in Python are similar to those found in Java or C++. Also see
classmethod() for a variant that is useful for creating alternate class constructors.
所以,当你需要C++中的静态方法时,你需要一个Python中的静态方法,对吗?
嗯,不。
在爪哇中,没有函数,只是方法,所以最终创建的只是伪静态的捆绑类。在Python中做同样的事情的方法就是只使用自由函数。
这很明显。但是,要想让一个适当的类将函数插入到一个适当的类中,这是很好的Java样式,这样你就可以避免编写那些伪类,而同样的事情又是坏的Python风格,使用自由函数,这就不那么明显了。
C++与Java没有相同的局限性,但是许多C++风格还是非常相似的。(另一方面,如果你是一个"现代C++"程序员,内化了"自由函数是一个类的接口的一部分"的成语,那么你对于"静态方法在哪里有用"的直觉可能对Python来说相当不错。
但是,如果你是从第一原则而不是从另一种语言来理解这一点,那么有一种更简单的方法来看待事物:
当然,当需要访问类时,可以使用
最后,回答你的具体问题:
如果客户机需要按ID查找数据的唯一原因是构造一个
另一方面,如果这种查找在其他与
链接问题的答案明确表示:
A @classmethod is the idiomatic way to do an"alternate constructor"—there are examples all over the stdlib—itertools.chain.from_iterable, datetime.datetime.fromordinal, etc.
所以我不知道你怎么会想到使用类方法本身就不好。实际上,我喜欢在您的特定情况下使用ClassMethod的想法,因为它使遵循代码和使用API变得容易。
另一种方法是使用默认的构造函数参数,比如:
1 2 3 4 5 6 7 8 9 10 11 12 | 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版本。尤其是由于
你的第一个例子对我来说最有意义:
它避免了在接下来的两个示例中使用
使用诸如
我总是想一个名字对一个(a)对系统一无所知,和(b)对系统其他部分一无所知的人意味着什么,而不是说我总是成功的!
下面是@staticmethod的一个不错的用例。
我一直在做一个游戏作为辅助项目。游戏的一部分包括基于统计的掷骰子,以及获取影响角色统计的项目和效果的可能性(无论好坏)。
当我在游戏中掷骰子时,我需要说…获取基本角色统计,然后将任何库存和效果统计添加到这个大净值数字中。
如果不指示程序如何操作,就无法获取这些抽象对象并添加它们。我也没有在类级别或实例级别做任何事情。我不想在一些全局模块中定义函数。最后一个最好的选择是使用静态方法将统计数据相加。这样做最有意义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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) |
这会使stat组合调用像这样工作
1 2 3 4 | a = Stats(strength=3, intellect=3) b = Stats(strength=1, intellect=-1) c = Stats(tenacity=5) print(Stats.combine(a, b, c).__dict__) |
{'strength': 4, 'speed': 0, 'intellect': 2, 'tenacity': 5}
< /块引用>< /块引用>