类变量作为封闭类的实例

时间:2017-12-31 20:36:30

标签: python class enums

我知道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似乎抱怨说" name' Color'未定义"。我可以不在该类中创建类的实例吗?我的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)

哪种方法效果很好。不过,我的问题是为什么课堂内不允许这些字段?我可以添加一些东西或重组它以使其允许吗?提前感谢您的任何答案!

6 个答案:

答案 0 :(得分:9)

Enums直接支持此用例 。该库的文档涵盖了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'

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

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

答案 1 :(得分:5)

使用元组值和@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类尚不存在。

答案 2 :(得分:3)

&#34; 鸡肉和鸡蛋&#34; -problem

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

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

class Foo(object):
    bar = Foo()

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

但是我们有一些选择。

猴子修补Foo

我们可以先构建Color类,然后再构建&#34; monkey patch &#34;那个班:

Color

这里我们首先定义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键的属性函数Color.white来定义def name(self):上的属性。

答案 3 :(得分:2)

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

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

答案 4 :(得分:1)

您可以通过使用元类来帮助构建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)

答案 5 :(得分:0)

我认为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