为 Python 中的特定实例提供默认值的推荐方法是什么?

What's the recommended way to provide default values to specific instances in Python?

我希望某些实例根据对象的初始化参数使用某些默认属性值进行初始化。我正在考虑使用嵌套字典作为 class 属性,但由于某种原因感觉很复杂。是否有针对此类情况的最佳实践?

class Shape:

    metadata = {
        3: {"names": ["Triangle", "Triforce"], "color": "sage"},
        4: {"names": ["Square", "Box", "Cube"], "color": "dusty rose"},
        12: {"names": ["Dodecagon", "Crude circle"], "color": "gold"}
    }

    colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]

    def __init__(self, sides, *names):
        # All instances will certainly have the same attributes
        self.sides = sides
        self.names = list(names)
        # Most attributes will have automatically generated values based on
        # the init parameters
        self.color = self.colors[sides % 7]
        self.names += [str(sides) + "-gon"]
        # But a few will have commonly recognized values which I'd like to set
        # manually.
        # This is the part I'm not sure how to handle
        data = __class__.metadata.get(sides)
        if data is not None:
            self.names += data["names"]
            self.color = data["color"]

我可以在创建对象后添加自定义值,但如果我创建另一个具有相同初始化参数的对象,它不会保留这些自定义值(即我希望我所有的 Shape(3) 对象都具有名称“Triangle”)。

我认为它感觉复杂的原因是因为您的 Shape class 试图一次做太多事情。理想情况下,class 应该负责程序行为的单个部分(这是单一职责原则)。

我建议对您的代码进行两项主要更改。

不要让 Shape class 负责创建自己

一个形状实际上并不需要知道所有其他可能的形状类型,或者决定它是哪种形状所需的规则。这段代码可以抽象出来成为另一个class,所以形状可以专注于包含边、形状和颜色。我建议为此使用工厂模式之类的东西 (https://en.wikipedia.org/wiki/Factory_method_pattern)。

考虑使用多态性

如果您计划只将形状作为侧面名称和颜色的容器,则您当前的 class 可以正常工作。但是,如果您想要添加根据形状类型而变化的功能(例如,如果您想计算它的面积),您将在形状中加入一些复杂的逻辑 class,这意味着它是又回来做太多事情了。

示例:

class Shape:
    def __init__(self, sides, color, *names):
        self.sides = sides
        self.color = color
        self.names = names

    def __str__(self):
       return "Sides: {}\nNames:{}\nColor: {}\n".format(self.sides, self.names, self.color)

class Triangle(Shape):
    def __init__(self, color, *names):
        super().__init__(3, color, *names)

class Square(Shape):
    def __init__(self, color, *names):
        super().__init__(4, color, *names)

class BlueShapeFactory:
    def createShapes(sides):
        if sides == 3:
            return Triangle("Blue", "PointyBoy", "Triangle")
        elif sides == 4:
            return Square("Blue", "Uncool", "Square")
        else:
            return Shape(sides, "Blue", str(sides) + "-o-gon")
            
class DefaultShapeFactory:
    def createShapes(sides):
        if sides == 3:
            return Triangle("green", "Triforce", "Triangle")
        elif sides == 4:
            return Square("blue", "Box", "Cube", "Square")
        else:
            return Shape(sides, "purple", str(sides) + "-o-gon")
            
print("Blueshapes:\n")
print(BlueShapeFactory.createShapes(3))
print(BlueShapeFactory.createShapes(4))
print(BlueShapeFactory.createShapes(42))

print("Your shapes:\n")
print(DefaultShapeFactory.createShapes(3))
print(DefaultShapeFactory.createShapes(4))
print(BlueShapeFactory.createShapes(42))

我找到了一个解决方案:使用 The Flyweight design pattern.

使用此设计模式,对于每个初始化参数,一个实例仅实例化一次,并在再次尝试使用相同的初始化参数构造时被引用。这类似于某些不可变 Python 内置函数的对象缓存。

与其在 class 定义中保存默认属性值,不如在实例化后简单地设置这些值。任何具有相同初始化参数的未来对象都将保留这些自定义值,因为它将引用第一个对象。

class Shape:

    _instances = {}

    colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]

    def __new__(cls, sides, *names):
        # Retrieve the Shape instance if previously instantiated
        self = cls._instances.get(sides)
        # If it has not yet been instantiated...
        if self is None:
            # Save this Shape instance
            self = cls._instances[sides] = object.__new__(Shape)
            # Initialize here for efficiency (because __init__ gets called
            # automatically, even when returning a previously instantiated
            # object)
            self.sides = sides
            self.color = cls.colors[sides % 7]
            self.names = list(names)
            self.names += [str(sides) + "-gon"]
        return self

triangle = Shape(3)
triangle.names += ["triangle", "triforce"]
triangle.color = "sage"

square = Shape(4)
square.names += ["Square", "Box", "Cube"]
square.color = "dust rose"

dodecagon = Shape(12)
dodecagon.names += ["Dodecagon", "Crude circle"]
dodecagon.color = "gold"

通常,此模式应用于不可变对象,因为修改一个对象的属性会导致在引用该对象的所有地方进行更改,这可能会导致意外结果。但是,在这种情况下,这是可取的行为。