26
loading...
This website collects cookies to deliver better user experience
Creational
Behavioral
Structural
When this is useful? Can we just call the constructor directly?
datetime
module is one of the most important ones in the standard library. It defines a few classes such as date
, datetime
, and timedelta
. This module uses the simple factory pattern extensively. A real example is the date
class. It has a method called fromtimestamp
that creates date
instances given a timestamp.In [3]: from datetime import date
In [4]: date.fromtimestamp(time.time())
Out[4]: datetime.date(2020, 11, 10)
time
instance and the call the constructor (cls
). This is the kind of setup that is abstracted away from the user.@classmethod
def fromtimestamp(cls, t):
"Construct a date from a POSIX timestamp (like time.time())."
y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
return cls(y, m, d)
fromisocalendar
method, which performs an extensive setup. Instead of leaving it to the user, the class provides the functionality “for free” by hiding that from you.# https://github.com/python/cpython/blob/c304c9a7efa8751b5bc7526fa95cd5f30aac2b92/Lib/datetime.py#L860-L893
...
@classmethod
def fromisocalendar(cls, year, week, day):
"""Construct a date from the ISO year, week number and weekday.
This is the inverse of the date.isocalendar() function"""
# Year is bounded this way because 9999-12-31 is (9999, 52, 5)
if not MINYEAR <= year <= MAXYEAR:
raise ValueError(f"Year is out of range: {year}")
if not 0 < week < 53:
out_of_range = True
if week == 53:
# ISO years have 53 weeks in them on years starting with a
# Thursday and leap years starting on a Wednesday
first_weekday = _ymd2ord(year, 1, 1) % 7
if (first_weekday == 4 or (first_weekday == 3 and
_is_leap(year))):
out_of_range = False
if out_of_range:
raise ValueError(f"Invalid week: {week}")
if not 0 < day < 8:
raise ValueError(f"Invalid weekday: {day} (range is [1, 7])")
# Now compute the offset from (Y, 1, 1) in days:
day_offset = (week - 1) * 7 + (day - 1)
# Calculate the ordinal day for monday, week 1
day_1 = _isoweek1monday(year)
ord_day = day_1 + day_offset
return cls(*_ord2ymd(ord_day))
....
pandas
is one of the most used Python packages thanks to the rise of Data Science and Machine Learning. Just like Python, pandas
also makes use of factory methods. A classic example is the from_dict
method that belongs to the DataFrame
class.>>> data = {'row_1': [3, 2, 1, 0], 'row_2': ['a', 'b', 'c', 'd']}
>>> pd.DataFrame.from_dict(data, orient='index')
0 1 2 3
row_1 3 2 1 0
row_2 a b c d
@classmethod
def from_dict(cls, data, orient="columns", dtype=None, columns=None) -> DataFrame:
...
index = None
orient = orient.lower()
if orient == "index":
if len(data) > 0:
# TODO speed up Series case
if isinstance(list(data.values())[0], (Series, dict)):
data = _from_nested_dict(data)
else:
data, index = list(data.values()), list(data.keys())
elif orient == "columns":
if columns is not None:
raise ValueError("cannot use columns parameter with orient='columns'")
else: # pragma: no cover
raise ValueError("only recognize index or columns for orient")
return cls(data, index=index, columns=columns, dtype=dtype)
classmethod
. In Python, regular methods are attached to an object instance. We can access the objects’ fields via the self
argument. classmethod
, on the other hand, are bound not to an instance but to a class
. That means when we call MyClass.factory_method
we are passing MyClass
as the first argument, called cls
. This property makes them an excellent alternative for factory methods since calling cls(args)
inside a classmethod
is the same as MyClass(args)
.classmethod
and return a new instance built with the cls
argument. For example, presume that we want to implement a Point
class and we want it also be constructed from Polar coordinates. The extra setup to convert from Polar to Cartesian is kept inside the method. Not simply it’s more readable, but also simplifies the constructor.class Point:
def __init__(self, x: float, y: float):
self.x = x
self.y = y
@classmethod
def from_polar(cls, r: float, theta: float) -> "Point":
"""
Converts a polar coordinate into cartesian point.
>>> Point.from_polar(r=-2**0.5, theta=math.pi / 4)
Point(x=-1.00, y=-1.00)
"""
return cls(r * math.cos(theta), r * math.sin(theta))
def __repr__(self):
return f"{self.__class__.__name__}(x={self.x:.2f}, y={self.y:.2f})"
>>> Point.from_polar(r=-2**0.5, theta=math.pi / 4)
Point(x=-1.00, y=-1.00)
pandas
.