本博客所回答的核心问题:
(注:本文第6节类工厂函数部分尚待补充,不影响前5节的阅读,如果您有更好的建议,请留言告诉我,谢谢!)
1. 什么是工厂函数(factory function)?
工厂函数是用于生成特定数据类型的新数据项的函数。
因为现实世界中“工厂”生产制造物品,所以用类比的方法,“工厂函数”制造对象。
举例:
(1)set(),创造新的集合 set。
(2)dict(),创造新的字典 dict。
(3)Athlete(), 创造一个新的运动员对象。(咦,这也是工厂函数吗?)
是的,在 《Head First Python》 一书中,作者 Paul Barry 称 a=Athlete() 这种调用方式为工厂函数调用(factory function call)。实际上,Python 在处理这行代码时,会将工厂函数调用转化为如下调用:Athlete().__init__(a)。
那么工厂函数与类有什么区别呢?
2. 工厂函数是类吗?
在 Python 中,函数和类通常可以互换,因为二者都是可调用的对象,而且没有实例化对象的new运算符,所以调用构造方法与调用工厂函数没有区别。此外,只要能放回新的可调用对象,代替被装饰的函数,二者都可以用作装饰器。
但是工厂函数还有一些其他的作用,让我们看一下在具体的应用场景中工厂函数是如何使用的。
3. 工厂函数的应用场景有哪些?
3.1 工厂函数在 NetworkX 中的应用
class Graph:
node_dict_factory = dict
node_attr_dict_factory = dict
adjlist_outer_dict_factory = dict
adjlist_inner_dict_factory = dict
edge_attr_dict_factory = dict
graph_attr_dict_factory = dict
def __init__(self, incoming_graph_data=None, **attr):
...
self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
self._node = self.node_dict_factory() # empty node attribute dict
self._adj = self.adjlist_outer_dict_factory() # empty adjacency dict
...
你可能会有一个问题:为什么不直接使用 self.graph = {},以下创建字典的两种方式区别是什么?
# First
dict_factory = dict
dict_a = dict_factory()
# Second
dict_a = {}
两种创建方式结果是相同的,dict_a 都是一个空字典,那为什么这里采用第一种使用方法而不是第二种呢?答案在第4节,工厂函数与工厂模式之间的关系,这也是工厂函数得名的原因。
4. 工厂函数与工厂模式之间的关系是什么?
工厂函数是简单工厂模式的体现。
本节参考《Head First 设计模式》一书第4章 Pizza 工厂的例子,书中给出的是 Java 版本,这里作者近似给出 Python 实现。下面的 pizza_factory 函数实现了一个简单 pizza 工厂函数,可以根据参数 string 创造并返回一个 CheesePizza 对象或 VeggiePizza 对象。
def pizza_factory(string):
pizza = None
if string == "cheese":
pizza = CheesePizza()
elif string == "veggie":
pizza = VeggiePizza()
else:
raise ValueError('String must be cheese or veggie')
return pizza
我们回顾本文 3.1 节中的例子,如果采用下面的等价形式,是不是可以看出 graph_attr_dict_factory 函数和 pizza_factory 的形式是一样的?
class Graph:
# graph_attr_dict_factory = dict
def graph_attr_fict_factory():
return dict
def __init__(self, incoming_graph_data=None, **attr):
...
self.graph = self.graph_attr_dict_factory() # dictionary for graph attributes
...
这里给出工厂方法模式的正式定义:工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个,工厂方法让类把实例化推迟到子类。更多关于工厂方法模式的内容,请参阅《Head First 设计模式》。
采用工厂函数的目的与工厂方法模式的目的是相同的,即针对接口编程,而不是针对实现编程。工厂方法将客户和实际创建具体产品的代码分隔开来。同样考虑 3.1 节中的例子,可以看到 __init__函数中在构建 graph 属性时调用了工厂函数而非直接使用 ‘{}’,体现了对扩展开放,对修改关闭的原则,当你想创造某个子类继承 Graph 类时,如果想要改变 Graph 所采用的数据结构,采用其他的 dict-like 的数据结构而不是 dict 时,可以直接修改工厂函数(或者说接口)而不需要修改 __init__函数。
下面还给出了两类特殊的工厂函数。
5. 什么是特性工厂函数?
(注:本节内容参考 fluent python 19.4 定义一个特性工厂函数)
特性工厂函数用于生成满足某类性质的新特性。
下面的 quantity 特性工厂函数是书中本节给出的一个示例, 该函数返回 property 类的对象(特性),这个 property 需要满足大于 0 的性质。
def quantity(storage_name):
def qty_getter(instance):
return instance.__dict__[storage_name]
def qty_setter(instance, value):
if value > 0:
instance.__dict__[storage_name] = value
else:
raise ValueError('Value must be > 0.')
return property(qty_getter, qty_setter)
特性工厂函数是高阶函数,在闭包中存储 storage_name 等设置
书中还给出了调用的例子
class LineItem:
weight = quantity('weight')
price = quantity('price')
def __init__(self, weight, price):
self.weight = weight
self.price = price
一个很直接的问题是:为什么要采用特性工厂函数而不是 @property 装饰器?
class LineItem:
def __init__(self, weight, price):
self.weight = weight
self.price = price
@property
def weight(self):
return self.__weight
@weight.setter
def weight(self, value):
if value > 0:
self.__weight = value
else:
raise ValueError('Weight must be > 0.')
@property
def price(self):
return self.__price
@price.setter
def price(self, value):
if value > 0:
self.__price = value
else:
raise ValueError('Price must be > 0.')
可以看到 weight 和 price 的读值方法、设置方法重复了,而特性工厂函数借助函数式编程模式避免重复编写读值方法和设置方法。
个人理解:这种案例某种程度上也可以看做是装饰器句法的一个缺点。
6. 什么是类工厂函数?
(to be completed)
参考文献
1. Ramalho, Luciano. Fluent python. " O'Reilly Media, Inc.", 2017.
2. Barry, Paul. Head first Python: A brain-friendly guide. " O'Reilly Media, Inc.", 2016.
3. Freeman, Eric, et al. Head First Design Patterns: A Brain-Friendly Guide. " O'Reilly Media, Inc.", 2004.