用于可扩展、可重用和优雅的代码的Python工厂
通过将行为(在工厂超类中定义)与对象创建(在子类中)解耦,创建灵活、可扩展的代码。
在现实生活中的工厂中,相同或相似对象的生产不是单独完成的,而是在流水线上精简的。同样地,工厂设计模式允许你创建的软件不与特定的产品相联系,而是可以在许多类似的应用中重复使用。工厂模式是著名的Gof(四人帮)设计模式之一[1]。它是一种创造型的设计模式,它将对象的创建封装在一个叫做工厂的类中。这种模式通过与系统解耦来促进系统的灵活性,即如何、何时以及由谁来创建其对象[6]。
当你需要创建新类型的对象时,工厂就特别有用。如果对象的创建分散在整个代码中,那么你将不得不花时间去寻找代码中对象类型的确切位置[2]。在软件工程方面,这意味着你有大量的技术债务需要处理。这在机器学习中尤其如此,正如[4]中所解释的那样。机器学习会产生巨大的维护成本,这源于数据依赖、数据漂移和隐藏的反馈回路。最近,通过创建特征商店[3][5],工厂已经在机器学习中找到了自己的方式。特征库可以是在线的,也可以是离线的,其主要目的是抽象数据转换,因为它们适用于不同的特征,并通过这种方式实现更快的开发,更好的协作,有效的模型部署和扩展[7]。
1.0 在Python中实现工厂的一个例子
现在让我们来看一个如何在Python中使用工厂的具体例子。假设我们想为一个刚从大学毕业的儿子创建一个新的衣柜,他在一家银行找到了第一份工作,是一个定量金融分析师。鉴于他精通Python,他决定实现一个虚拟衣柜的索引。我们将帮助他为每件衣服分配一个 "正式等级",因为他完全有能力穿着T恤和破洞牛仔裤出现在一个重要的会议上。
如下图所示,虚拟衣橱索引从创建两个抽象工厂开始, 一个是鞋类(FootWear) 一个是衣服(MensClothes)
这些都是抽象类,它们只起到模板的作用。与 Java 不同,Python 没有一个固有的抽象类类型。相反,它提供了一个叫做 abc 的模块,它提供了抽象类的基础结构。
MensClothes
类的 colort
和 colorb
变量分别代表上衣 (衬衫、毛衣等) 和下衣 (裤子) 的颜色。注意,我们给鞋子和上下装的颜色分配了一个默认值 "棕色"(安全的颜色)。
import ast
from abc import ABC
class FootWear(ABC):
def shoespecs(self, c="Brown"):
self.color = c
class MensClothes(ABC):
def __init__(self, rt="Brown", rb="Brown"):
self.colort = rt
self.colorb = rb
def clothespecs(self, foot_w):
pass
现在让我们通过定义具体的类Boots
和Loafers
来实现两种类型的鞋类。我们继承自FootWear
类,然后覆盖其shoespecs()
方法来实现每个具体类的具体动作。
class Boots(FootWear):
def __init__(self):
print("Wear boots on a cold day")
def shoespecs(self, c="Brown"):
print(f"You picked {c} boots")
class Loafers(FootWear):
def __init__(self):
print("Shoes for a nice day")
def shoespecs(self, c="Brown"):
print(f"You picked {c} loafers")
同样地,我们创建了两个继承自MensClothes
的具体类:SweaterAndCurdoroyPants
,和ButtonDownShirtAndDressPants
。
每个具体的类都重写了抽象类的方法 clothespecs()
,以便为每个样式指定特定的内容。请注意,clothespecs()
方法接受一个FootWear
对象,以使鞋类的颜色与裤子的颜色相匹配。
class SweaterAndCorduroyPants(MensClothes):
def __init__(self, rt="Brown", rb="Brown"):
super().__init__(rt, rb)
print(f"You picked the {rt} sweater and the {rb} corduroy pants")
def clothespecs(self, foot_w):
foot_w.shoespecs(c=self.colorb)
class ButtonDownShirtAndDressPants(MensClothes):
def __init__(self, rt="Brown", rb="Brown"):
super().__init__(rt, rb)
print(f"You picked the {rt} button-down shirt and the {rb} dress pants ")
def clothespecs(self, foot_w):
foot_w.shoespecs(c=self.colorb)
下面的抽象类Outfit是一个工厂,它提供了选择衣服和鞋子的方法,分别是choose_clothes()
和choose_shoes()
,并且有一个实例属性,formality
。
class Outfit(ABC):
def __init__self(self):
self.formality=0
pass
def choose_clothes(self):
pass
def choose_shoes(self):
pass
下面的具体类LightColor_ColdAndCasualDay
继承自抽象类Outfit
,有以下方法。
(a) 一个构造函数,指定了形式和名称变量。形式变量是从Outfit继承的。
(b) Outfit
类的choice_clothes()
方法的重载形式,它返回一个SweaterAndCorduroyPants
对象。
(c) Outfit
的choice_shoes()
方法的重载形式,该方法返回一个Boots
对象。
正如我们所看到的,当我们向上移动类的层次时,事情变得更加具体,而较低级别的类,如FootWear
、MensClothes
和Outfit
,对于创建什么子类、在哪里创建它们的对象以及它们如何被聚合都是不可知的。
下面的DarkColor_WarmSemiFormalDay
类也定义了一种类似的结构,它定义了一些方法来返回衣服的ButtonDownShirtAndDressPants
对象和鞋类的Loafers
对象。
class LightColor_ColdAndCasualDay(Outfit):
def __init__(self):
self.formality = 1
self.name = "Light Colored Outfit for a Cold and Casual Day"
def choose_clothes(self):
return SweaterAndCorduroyPants(rt="Cream-colored", rb="Light Brown")
def choose_shoes(self):
return Boots()
class DarkColor_WarmSemiFormalDay(Outfit):
def __init__(self):
self.formality = 2
self.name = "Dark Colored Outfit for a Semi-Formal Day"
def choose_clothes(self):
return ButtonDownShirtAndDressPants(rt='burgundy',rb='black')
def choose_shoes(self):
return Loafers()
与到目前为止我们看到的抽象工厂相比,DarkColor_WarmSemiFormalDay
和LightColor_ColdAndCasualDay
是具体的工厂。它们如何作为对象的工厂,在下面的Look类的定义中得到了说明。类Look是负责创建对象的协调类。
正如我们在下面看到的,它的构造函数需要一个工厂作为参数。在我们有限的衣柜里,这个工厂可以是LightColor_ColdAndCasualDay
或DarkColor_WarmSemiFormalDay
对象。
当我们创建一个Look类的对象时,工厂的choose_clothes()
和choose_shoes()
被调用,它们分别给实例变量clo
和sho
赋值。
然后,这些变量被用于重要的dress()
方法,该方法将所有东西放在一起并创建完整的外观!这是一个可扩展、可重复使用的方法。这是可扩展的、可重复使用的代码,因为战略性地放置了对象创建的封装和多态性。
class Look:
def __init__(self, factory):
self.factory = factory
self.name = factory.name
print(factory.name)
self.clo = factory.choose_clothes()
self.sho = factory.choose_shoes()
self.formality = factory.formality
def dress(self):
self.clo.clothespecs(self.sho)
def __eq__(self, m):
return self.formality == m.formality
def __gt__(self, m):
return self.formality > m.formality
在下面的代码片段中,我们可以看到我们的对象创建和礼服()方法的调用。请注意,我们检查了两个对象是否相等或对象g2是否大于对象g1。
g1 = Look(LightColor_ColdAndCasualDay())
g1.dress()
g2 = Look(DarkColor_WarmSemiFormalDay())
g2.dress()
print(g1 == g2)
print(g2 > g1
在上面Look类的定义中,我们把__eq__
运算符的输出定义为两个对象的形式性的平等比较的输出。正如我们所讨论的,我们的新晋金融分析师对形式上的风格一无所知,所以我们需要以一种特定的方式(形式上的等级)告诉他,哪些外观在形式上被认为是相等的。
所以,3是最高的正式性(商务套装),0是没有正式性(T恤衫和运动鞋)。下面的等号操作返回假,大号操作返回真,因为g2的正式性等级(2)比g1(1)高。
最后,在下面的代码片段中,我们导入了当前的虚拟衣橱,它以字典的形式存在于一个文本文件中,并将我们创建的两个新外观附加到它上面,Outfit对象的名称作为键,形式感作为值。
with open('virtual_wardrobe.txt') as f:
data = f.read()
wardrobe = ast.literal_eval(data)
print(wardrobe)
wardrobe[g1.name] = g1.formality
wardrobe[g2.name] = g2.formality
print(wardrobe)
with open('virtual_wardrobe.txt', 'w') as v_wardrobe:
v_wardrobe.write(str(wardrobe))
v_wardrobe.close()
你可以在我的github目录中看到整个代码,作为一个Jupiter笔记本:https://github.com/theomitsa/Python-factories
谢谢您的阅读!
参考文献
Gamma, E., Helm, R., Johnson, and R., J. Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley Professional, Oct. 1994.
Eckel, B. et al., Python 3 Patterns, Recipes, and Idioms, Read the Docs (ebook) Fly, A., Feature Store: 数据科学工厂的组成部分,中。迈向数据科学》,2019年9月。
Sculley, D. et al., 机器学习。The High-Interest Credit Card of Technical Debt, Software Engineering for Machine Learning, NIPS Conference Workshop, 2014.
Taifi, M., ML Feature Stores: 休闲之旅1/3, Medium, 2020年4月。 Bishop, J, C# 3.0 Design Patterns, O'Reilly Media, p. 336, 2007.
Hirschtein, A., What Are Feature Stores And Why Are They Critical For Scaling Data Science? , Medium: 迈向数据科学》,2020年4月。
本文由 mdnice 多平台发布