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.
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).