【问题标题】:Class Variable as Instance of Enclosing Class类变量作为封闭类的实例
【发布时间】:2017-12-31 20:36:30
【问题描述】:

我知道 Python 可以做枚举(很好),但它做的枚举类型非常原始。例如我可以这样做:

from enum import Enum

class Color(Enum):
  BLACK = 'black'
  WHITE = 'white'

print(Color.BLACK.value)

这很好,但是如果我希望每种颜色都有一个名称和一个十六进制值怎么办?我有几个选择(比如让枚举值成为字典),但我更喜欢 Java 做枚举的方式。在 Java 中,允许枚举像类一样具有字段和方法。所以通常当一种语言只支持像上面的例子这样简单的枚举时,我会像下面这样重构代码:

class Color(object):
  BLACK = Color('black', '#000')
  WHITE = Color('white', '#fff')

  def __init__(self, name, hex):
    self.name = name
    self.hex = hex

print(Color.BLACK.name + ' ' + Color.BLACK.hex)

现在我可以有多个值、自定义方法,并且可以按名称引用不同的字段,因为每种颜色都是一个对象。我已经用几种语言毫无问题地做到了这一点,但 Python 似乎抱怨“名称'颜色'未定义”。我不能在该类中创建一个类的实例吗?我的 hacky 解决方案是这样做:

class Color(object):
  def __init__(self, name, hex):
    self.name = name
    self.hex = hex

Color.BLACK = Color('black', '#000')
Color.WHITE = Color('white', '#fff')

print(Color.BLACK.name + ' ' + Color.BLACK.hex)

效果很好。不过,我的问题是,为什么类中不允许使用这些字段?我可以添加一些东西或对其进行重组以使其允许吗?提前感谢您的任何回答!

【问题讨论】:

  • 那么 Python 必须构造一个 Color 类,作为参数 BLACK = Color('black', '#000')。但那时,Color 尚未定义。所以这就像“鸡和蛋”的问题。

标签: python class enums


【解决方案1】:

枚举直接支持这个用例。该库的文档在Planet example in the examples section 中对此进行了介绍:

如果定义了__new__()__init__(),则枚举成员的值将传递给这些方法:

>>> class Planet(Enum):
...     MERCURY = (3.303e+23, 2.4397e6)
...     # ...
...
...     def __init__(self, mass, radius):
...         self.mass = mass       # in kilograms
...         self.radius = radius   # in meters
...     @property
...     def surface_gravity(self):
...         # universal gravitational constant  (m3 kg-1 s-2)
...         G = 6.67300E-11
...         return G * self.mass / (self.radius * self.radius)

[...]

>>> Planet.EARTH.value
(5.976e+24, 6378140.0)
>>> Planet.EARTH.surface_gravity
9.802652743337129

因此,对于您的具体示例,只需定义一个 __init__ 方法:

from enum import Enum

class Color(Enum):
    BLACK = ('black', '#000')
    WHITE = ('white', '#fff')

    def __init__(self, color_name, hex):
        self.color_name = color_name
        self.hex = hex 

print(Color.BLACK.color_name + ' ' + Color.BLACK.hex)

我没有使用name作为属性,因为这是一个保留属性(用来反映枚举值名称,这里是BLACKWHITE):

>>> Color.BLACK
<Color.BLACK: ('black', '#000')>
>>> Color.BLACK.name
'BLACK'
>>> Color.BLACK.color_name
'black'
>>> Color.BLACK.hex
'#000'

您仍然可以使用 @property 覆盖 name 属性,但我不会偏离这里的标准。

我使用这种技术在我的Advent of Code day 22 solution 中定义了病毒状态,定义了下一个状态名称和每个条目的方向变化。

【讨论】:

    【解决方案2】:

    使用元组值和@property 访问器为元组元素命名:

    from enum import Enum
    
    class Color(Enum):
        BLACK = ('black', '#000')
        WHITE = ('white', '#fff')
    
        @property
        def name(self):
            return self.value[0]
    
        @property
        def hex(self):
            return self.value[1]
    
    print(Color.BLACK.name)
    print(Color.BLACK.hex)
    

    Output:

    black
    #000
    

    至于为什么你的代码不起作用,Python 类定义是必不可少的。在您尝试构建 Color 实例时,Color 类还不存在。

    【讨论】:

      【解决方案3】:

      鸡和蛋”问题

      您在这里遇到了“鸡和蛋”问题。因为如果你构造一个类,Python 必须将属性和函数的名称与属性和函数的值相关联。如果你访问Color.abc,那么它会查看是否找到了这样的对应名称,并返回值/函数定义。

      但是现在有一个问题。如果你写:

      class Foo(object):
          bar = Foo()

      为什么?那么为了构造class,它首先必须构造属性。所以它必须构造一个映射到Foo() 的结果的'bar' 条目,但我们当时正在构造Foo,那么如果Foo 依赖于该构造,我们如何构造Foo。我们不可以。在 Java 中,它更简单,因为类是在 编译时在概念上构建的。

      不过,我们有一些选择。

      猴子修补Color

      我们可以先构造Color 类,然后“monkey patch”那个类:

      class Color(object):
      
        def __init__(self, name, hex):
          self.name = name
          self.hex = hex
      
      Color.black = Color('black', '#000')
      Color.white = Color('white', '#fff')

      这里我们先定义Color类,然后给Color类添加属性。之后我们可以这样做,因为现在对象已经定义好了。

      将值附加到Enum 对象

      我们还可以将值附加到Enum 对象:

      from enum import Enum
      
      class Color(Enum):
      
        white = {'name': 'white', 'hex': '#fff'}
        black = {'name': 'black', 'hex': '#000'}
      
        @property
        def name(self):
          return self.value['name']
      
        @property
        def hex(self):
          return self.value['hex']

      我们可以为每个Enum 成员附加一个值。例如,我们将{'name': 'white', 'hex': '#fff'} 附加到white。我们稍后可以通过self.value 访问该值。所以现在我们可以通过定义一个访问字典的'name' 键的属性函数def name(self):Color.white 上定义一个属性。

      【讨论】:

      • 类不是字典。只有字典才是字典。更准确地说,类有一个字典,或者(如果你想更多地强调字典)类是字典的包装器。
      【解决方案4】:

      命名元组解决方案怎么样?

      from collections import namedtuple
      
      color = namedtuple('Color', ['name', 'value'])  # Add attributes as you please
      
      class Color:
          BLACK = color('black', '#000')
          WHITE = color('white', '#fff')
      
      print(Color.BLACK.name, Color.BLACK.value)
      

      输出

      黑色 #000

      添加新的就这么简单

      Color.RED = color('red', '#ff0')
      print(Color.RED.name, Color.RED.value)
      

      红色#ff0

      【讨论】:

      • 我认为有两个像这样的颜色类可能会有点混乱(colortuple 的子类,这是 namedtuple() 工厂函数返回的内容)。
      【解决方案5】:

      您可以通过使用元类来帮助构建Color 类来做您想做的事:

      class ColorMeta(type):
          def __new__(cls, class_name, parents, attrs):
              NUL = object()  # Sentinel.
              meta_args = attrs.get('meta_args', NUL)
              if meta_args is NUL:
                  meta_args = []
              else:
                  del attrs['meta_args']  # Clean up so isn't part of class created.
      
              clsobj = super().__new__(cls, class_name, parents, attrs)
      
              for meta_arg in meta_args:
                  name, hex = meta_arg
                  color = clsobj(name, hex)
                  setattr(clsobj, name, color)
      
              return clsobj
      
      
      class Color(metaclass=ColorMeta):
          meta_args = [('WHITE', '#fff'),
                       ('BLACK', '#000'),]
      
          def __init__(self, name, hex):
              self.name = name
              self.hex = hex
      
      
      print(Color.WHITE.name + ' ' + Color.WHITE.hex)
      print(Color.BLACK.name + ' ' + Color.BLACK.hex)
      

      【讨论】:

        【解决方案6】:

        我认为user2357112 已经有您正在寻找的答案,但可能值得研究namedtuples 以及访问属性。

        命名元组:

        from collections import namedtuple
        Color = namedtuple('Color', 'name hex')
        Black = Color(name='Black', hex='#000')
        
        print(Black.hex)
        print(Black.name)
        >#000
        >Black
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-06-23
          • 2011-01-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-10-10
          • 2012-11-16
          相关资源
          最近更新 更多