【问题标题】:How to implement a factory class?如何制作 Python 工厂类
【发布时间】:2019-03-24 02:35:37
【问题描述】:

我希望能够基于枚举类创建对象,并使用字典。像这样的:

class IngredientType(Enum):
    SPAM        = auto() # Some spam
    BAKE_BEANS  = auto() # Baked beans
    EGG         = auto() # Fried egg

class Ingredient(object):
    pass    
class Spam(Ingredient):
    pass
class BakedBeans(Ingredient):
    pass
class Egg(Ingredient):
    pass


class IngredientFactory(object):
    """Factory makes ingredients"""

    choice = {
        IngredientType.SPAM: IngredientFactory.MakeSpam,
        IngredientType.BAKED_BEANS: IngredientFactory.MakeBakedBeans,
        IngredientType.EGG: IngredientFactory.MakeEgg
    }

    @staticmethod
    def make(type):
        method = choice[type]
        return method()

    @staticmethod
    def makeSpam():
        return Spam()

    @staticmethod
    def makeBakedBeans():
        return BakedBeans()

    @staticmethod
    def makeEgg():
        return Egg()

但我得到了错误:

NameError: name 'IngredientFactory' is not defined

由于某种原因无法创建字典。 我哪里错了?

【问题讨论】:

  • 你到底为什么要这样做?
  • 在类解析/定义时,IngredientFactory 还不存在。直到 Python 解析器在 IngredientFactory 类定义下留下最后一个缩进,它才可供参考。
  • 代码究竟在哪里引发了错误?
  • 配料工厂的第 4 行它给了我错误。
  • 这是做工厂的错误方法吗?

标签: python dictionary enums factory


【解决方案1】:

Python 不是 Java,并不要求所有东西都在一个类中。这里你的 IngredientFactory 类没有状态,只有静态方法,所以它实际上只是一个单例命名空间,在 python 中,它是使用模块作为单例命名空间和普通函数来完成的。此外,由于 Python 类已经是可调用的,因此将实例化包装在函数中是没有意义的。简单、直接的 pythonic 实现是:

# ingredients.py

class IngredientType(Enum):
    SPAM        = auto() # Some spam
    BAKE_BEANS  = auto() # Baked beans
    EGG         = auto() # Fried egg

class Ingredient(object):
    pass    

class Spam(Ingredient):
    pass

class Beans(Ingredient):
    pass

class Egg(Ingredient):
    pass


_choice = {
        IngredientType.SPAM: Spam,
        IngredientType.BAKED_BEANS: Beans,
        IngredientType.EGG: Egg
    }

def make(ingredient_type):
    cls = _choice[ingredient_type]
    return cls()

以及客户端代码:

import ingredients
egg = ingredients.make(ingredients.IngredientType.EGG)

# or much more simply:

egg = ingredients.Egg()

FWIW IngredientType 枚举在这里并没有带来太多好处,甚至使事情变得更加复杂——你可以只使用纯字符串:

# ingredients.py

class Ingredient(object):
    pass    

class Spam(Ingredient):
    pass

class Beans(Ingredient):
    pass

class Egg(Ingredient):
    pass


_choice = {
        "spam": Spam,
        "beans": Beans,
        "egg": Egg
    }

def make(ingredient_type):
    cls = _choice[ingredient_type]
    return cls()

以及客户端代码:

import ingredients
egg = ingredients.make("egg")

或者,如果您真的想使用 Enum,您至少可以按照 MadPhysicist 的建议,通过将类本身用作枚举的值来摆脱 choices 字典:

# ingredients.py

class Ingredient(object):
    pass    

class Spam(Ingredient):
    pass

class Beans(Ingredient):
    pass

class Egg(Ingredient):
    pass

class IngredientType(Enum):
    SPAM = Spam
    BEANS = Beans
    EGG = Egg

    @staticmethod
    def make(ingredient_type):
        return ingredient_type.value()

和客户端代码

 from ingredients import IngredientType
 egg = IngredientType.make(IngredientType.EGG)

但我也真的没有看到任何好处

编辑:你提到:

我正在尝试实现工厂模式,目的是隐藏对象的创建。然后工厂的用户在不知道具体类型的情况下处理“成分”

用户仍然必须指定他想要什么样的成分(ingredient_type 参数)所以我不确定我是否理解这里的好处。您的真实用例究竟是什么? (编造/编造的例子的问题在于它们并不能说明整个故事)。

【讨论】:

  • 此时,您可以直接将对该类的引用粘贴到枚举中,然后完全跳过第二个查找表。
  • @MadPhysicist 可能是的 - 到目前为止我还没有使用 python3 Enum。
  • 值可以是我认为的任何值。一个类将和auto() 一样好。
  • @DogBreath“你猜”???你是问如何实现这一点的人,但你不知道为什么要这样做???究竟应该由谁来决定究竟会创造出什么样的鸡蛋?
  • 请注意,如果您的意思是允许基于平台、设置或类似的东西交换实现,那么有更简单的方法 - 例如,您可以看看 os.path 的工作原理。请记住,1/ 在 Python 中一切都是对象(包括类、模块等),并且 2/ 所有顶级代码都在导入时执行,因此您可以有条件地交换绑定到名称的类/函数/模块(参见 @ 987654335@再次)。
【解决方案2】:

看了Bruce Eckel's book之后,我想到了这个:

#Based on Bruce Eckel's book Python 3 example
# A simple static factory method.
from __future__ import generators
import random
from enum import Enum, auto

class ShapeType(Enum):
    CIRCLE  = auto() # Some circles
    SQUARE  = auto() # some squares

class Shape(object):
    pass

class Circle(Shape):
    def draw(self): print("Circle.draw")
    def erase(self): print("Circle.erase")

class Square(Shape):
    def draw(self): print("Square.draw")
    def erase(self): print("Square.erase")

class ShapeFactory(object):

    @staticmethod
    def create(type):
        #return eval(type + "()") # simple alternative
        if type in ShapeFactory.choice:
            return ShapeFactory.choice[type]()

        assert 0, "Bad shape creation: " + type    

    choice = { ShapeType.CIRCLE:  Circle,
               ShapeType.SQUARE:  Square                
             }

# Test factory
# Generate shape name strings:
def shapeNameGen(n):

    types = list(ShapeType)

    for i in range(n):
        yield random.choice(types)

shapes = \
  [ ShapeFactory.create(i) for i in shapeNameGen(7)]

for shape in shapes:
    shape.draw()
    shape.erase()

这让用户从枚举中选择一个类类型,并阻止任何其他类型。这也意味着用户不太可能写出带有拼写错误的“坏字符串”。他们只使用枚举。 测试的输出是这样的:

Circle.draw
Circle.erase
Circle.draw
Circle.erase
Square.draw
Square.erase
Square.draw
Square.erase
Circle.draw
Circle.erase
Circle.draw
Circle.erase
Square.draw
Square.erase

【讨论】:

    【解决方案3】:

    将您的映射放在类的末尾,并直接引用这些方法,因为它们在同一个命名空间中:

    choice = {
        IngredientType.SPAM: makeSpam,
        IngredientType.BAKED_BEANS: makeBakedBeans,
        IngredientType.EGG: makeEgg
    }
    

    直到类主体中的所有代码都不会创建类对象,因此您无法访问类本身。但是,由于类主体是在专用命名空间中处理的,因此您可以访问您在此之前定义的任何属性(这就是映射必须在最后出现的原因)。另请注意,虽然您可以访问全局变量和内置函数,但不能访问封闭类或函数的命名空间。

    这是官方文档中详细但仍然是介绍性的解释,解释了如何执行类:https://docs.python.org/3/tutorial/classes.html#a-first-look-at-classes

    【讨论】:

    • 那么choice 变量仍然存在于类中吗?但最后呢?因为我仍然收到错误:NameError: name 'MakeSpam' is not defined
    • @DogBreath。那是因为您没有正确输入方法名称。 Python 区分大小写。请注意,答案的大小写正确:例如makeSpam.
    • 我有点迷路了:~
    • 你为什么迷路了?
    • @DogBreath 这应该是IngredientFactory.choice(我的意思是在静态方法中)。使用 classmethods 你可以使用 cls.choices
    猜你喜欢
    • 1970-01-01
    • 2022-10-01
    • 2023-03-15
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-22
    • 1970-01-01
    相关资源
    最近更新 更多