27

这是一个关于使用 python 从相同数据的不同形式创建类或类型的实例的最佳实践的问题。使用类方法更好还是完全使用单独的函数更好?假设我有一个用于描述文档大小的类。(注意:这只是一个示例。我想知道创建类实例的最佳方式,而不是描述文档大小的最佳方式。)

class Size(object):
    """
    Utility object used to describe the size of a document.
    """

    BYTE = 8
    KILO = 1024

    def __init__(self, bits):
        self._bits = bits

    @property
    def bits(self):
        return float(self._bits)

    @property
    def bytes(self):
        return self.bits / self.BYTE

    @property
    def kilobits(self):
        return self.bits / self.KILO

    @property
    def kilobytes(self):
        return self.bytes / self.KILO

    @property
    def megabits(self):
        return self.kilobits / self.KILO

    @property
    def megabytes(self):
        return self.kilobytes / self.KILO

我的__init__方法采用以位表示的大小值(位且只有位,我想保持这种方式),但可以说我有一个以字节为单位的大小值,我想创建我的类的一个实例。使用类方法更好还是完全使用单独的函数更好?

class Size(object):
    """
    Utility object used to describe the size of a document.
    """

    BYTE = 8
    KILO = 1024

    @classmethod
    def from_bytes(cls, bytes):
        bits = bytes * cls.BYTE
        return cls(bits)

或者

def create_instance_from_bytes(bytes):
    bits = bytes * Size.BYTE
    return Size(bits)

这似乎不是一个问题,也许这两个例子都是有效的,但我每次需要实现这样的东西时都会考虑它。很长一段时间以来,我都偏爱类方法方法,因为我喜欢将类和工厂方法捆绑在一起的组织优势。此外,使用类方法保留了创建任何子类的实例的能力,因此它更加面向对象。另一方面,一位朋友曾经说过“当有疑问时,做标准库所做的事情”,我还没有在标准库中找到这样的例子。

4

1 回答 1

33

首先,大多数时候你认为你需要这样的东西,你不需要;这表明您正在尝试将 Python 视为 Java,解决方案是退后一步,询问您为什么需要工厂。

通常,最简单的做法是使用带有默认/可选/关键字参数的构造函数。即使是在 Java 中你永远不会这样写的情况——即使是在 C++ 或 ObjC 中重载的构造函数会感觉不对的情况——在 Python 中也可能看起来非常自然。例如size = Size(bytes=20),,或size = Size(20, Size.BYTES)看起来合理。就此而言,一个Bytes(20)继承自Size并仅添加__init__重载的类看起来是合理的。这些是微不足道的定义:

def __init__(self, *, bits=None, bytes=None, kilobits=None, kilobytes=None):

或者:

BITS, BYTES, KILOBITS, KILOBYTES = 1, 8, 1024, 8192 # or object(), object(), object(), object()
def __init__(self, count, unit=Size.BITS):

但是,有时您确实需要工厂功能。那么,你会怎么做呢?嗯,有两种东西经常被归为“工厂”。

A@classmethod是执行“替代构造函数”的惯用方式——stdlib 中到处都有示例——<code>itertools.chain.from_iterabledatetime.datetime.fromordinal等。

函数是执行“我不在乎实际类是什么”工厂的惯用方式。例如,查看内置open函数。你知道它在 3.3 中返回什么吗?你关心?没有。这就是为什么它是一个函数,而不是io.TextIOWrapper.open什么。

您给定的示例似乎是一个完全合法的用例,并且非常适合“备用构造函数”bin(如果它不适合“带有额外参数的构造函数”bin)。

于 2013-02-21T00:28:16.487 回答