This is a question regarding the best practice for creating an instance of a class or type from different forms of the same data using python. Is it better to use a class method or is it better to use a separate function altogether? Let's say I have a class used to describe the size of a document. (Note: This is simply an example. I want to know the best way to create an instance of the class not the best way to describe the size of a document.)
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
My __init__
method takes a size value represented in bits (bits and only bits and I want to keep it that way) but lets say I have a size value in bytes and I want to create an instance of my class. Is it better to use a class method or is it better to use a separate function altogether?
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)
OR
def create_instance_from_bytes(bytes): bits = bytes * Size.BYTE return Size(bits)
This may not seem like an issue and perhaps both examples are valid but I think about it every time I need to implement something like this. For a long time I have preferred the class method approach because I like the organisational benefits of tying the class and the factory method together. Also, using a class method preserves the ability to create instances of any subclasses so it's more object orientated. On the other hand, a friend once said "When in doubt, do what the standard library does" and I am yet to find an example of this in the standard library.
The Factory Method pattern is generally used in the following situations: A class cannot anticipate the type of objects it needs to create beforehand. A class requires its subclasses to specify the objects it creates. You want to localize the logic to instantiate a complex object.
Factory Method Pattern allows the sub-classes to choose the type of objects to create. It promotes the loose-coupling by eliminating the need to bind application-specific classes into the code.
the abstract factory pattern,the static factory method, the simple factory (also called factory).
First, most of the time you think you need something like this, you don't; it's a sign that you're trying to treat Python like Java, and the solution is to step back and ask why you need a factory.
Often, the simplest thing to do is to just have a constructor with defaulted/optional/keyword arguments. Even cases that you'd never write that way in Java—even cases where overloaded constructors would feel wrong in C++ or ObjC—may look perfectly natural in Python. For example, size = Size(bytes=20)
, or size = Size(20, Size.BYTES)
look reasonable. For that matter, a Bytes(20)
class that inherits from Size
and adds absolutely nothing but an __init__
overload looks reasonable. And these are trivial to define:
def __init__(self, *, bits=None, bytes=None, kilobits=None, kilobytes=None):
Or:
BITS, BYTES, KILOBITS, KILOBYTES = 1, 8, 1024, 8192 # or object(), object(), object(), object() def __init__(self, count, unit=Size.BITS):
But, sometimes you do need factory functions. So, what do you do then? Well, there are two kinds of things that are often lumped together into "factories".
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.
A function is the idiomatic way to do an "I don't care what the actual class is" factory. Look at, e.g., the built-in open
function. Do you know what it returns in 3.3? Do you care? Nope. That's why it's a function, not io.TextIOWrapper.open
or whatever.
Your given example seems like a perfectly legitimate use case, and fits pretty clearly into the "alternate constructor" bin (if it doesn't fit into the "constructor with extra arguments" bin).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With