如何在每次迭代中获取枚举属性的随机值?

21

我创建了这样一个Enum对象:

class Gender(Enum):
    FEMALE = 'female'
    MALE = 'male'
    RANDOM = random.choice([FEMALE, MALE])

我希望每一次获取的随机值都是真正的随机,但它似乎无法实现:

>>> class Gender(Enum):
...    MALE = 'male'
...    FEMALE = 'female'
...    RANDOM = choice([MALE, FEMALE])
... 
>>> Gender.RANDOM
<Gender.MALE: 'male'>
>>> Gender.RANDOM
<Gender.MALE: 'male'>
>>> Gender.RANDOM
<Gender.MALE: 'male'>
>>> Gender.RANDOM
<Gender.MALE: 'male'>

我也尝试使用lambda,但它看起来不太好,尽管它可以工作:

Gender.RANDOM()

除了使用lambda表达式外,还有其他方法可以每次获取随机值吗?

我们将此枚举对象用作某个方法参数的默认值,因此它应该是一个属性而不是函数,因为当我们使用Gender.FEMALE时,它不是一个函数,而是一个属性,Gender.RANDOM也应该是一个属性:

def full_name(gender=Gender.FEMALE):
    ...


def full_name(gender=Gender.RANDOM):
    ...

1
默认属性只会在定义函数时被评估一次,因此随机值只会是随机的一次,每次调用该函数时您将获得相同的初始值。 - Ethan Furman
那是我想要改变的通常行为。 - Lo L
什么样的解决方法?def full_name(gender=Gender.RANDOM):不是一个解决方法。 - Ethan Furman
@EthanFurman 我可以使用这个答案中的描述符classproperty来实现,但我希望有一个更简单、更Pythonic的解决方案。 - Lo L
显示剩余2条评论
8个回答

14

正如其他人所说,最好的方法是将 random() 设置为枚举类的方法以明确表明 RANDOM 不是成员。

然而,由于我喜欢解谜:

from enum import Enum
import random

class enumproperty(object):
    "like property, but on an enum class"

    def __init__(self, fget):
        self.fget = fget

    def __get__(self, instance, ownerclass=None):
        if ownerclass is None:
            ownerclass = instance.__class__
        return self.fget(ownerclass)

    def __set__(self, instance, value):
        raise AttributeError("can't set pseudo-member %r" % self.name)

    def __delete__(self, instance):
        raise AttributeError("can't delete pseudo-member %r" % self.name)

class Gender(Enum):
    FEMALE = 'female'
    MALE = 'male'
    @enumproperty
    def RANDOM(cls):
        return random.choice(list(cls.__members__.values()))

在你的full_name定义中,使用Gender.RANDOM作为默认值并不能得到你想要的结果。这样的标准是:

def full_name(gender=None):
    if gender is None:
        gender = Gender.RANDOM   # we get `MALE` or `FEMALE`, not `RANDOM`

使用这种方式可能会让读者感到困惑。使用正常的方法会更好:

def full_name(gender=None):
    if gender is None:
        gender = Gender.random()

你说得对。使用 Gender.RANDOM 作为默认值,比起之前更加清晰易懂。谢谢! - Lo L

8

我尝试了一种使用元类的方法,它有效了!

import random
import enum
class RANDOM_ATTR(enum.EnumMeta):
    @property
    def RANDOM(self):
        return random.choice([Gender.MALE, Gender.FEMALE])


class Gender(enum.Enum,metaclass=RANDOM_ATTR): #this syntax works for python3 only
    FEMALE = 'female'
    MALE = 'male'


print(Gender.RANDOM)   #prints male or female randomly

通过将RANDOM_ATTR作为Gender的元类,就像将Gender作为RANDOM_ATTR类的对象一样,这样Gender就拥有了RANDOM属性。详情请参考属性装饰器

但是,你在问题中描述的下面代码并不能按你所期望的那样工作。

def full_name(gender=Gender.RANDOM):
    ...
RANDOM属性只会被调用一次。想了解为什么,请阅读这篇答案。默认参数就像是函数的属性,它们只会被初始化一次。
因此,我建议您像这样操作:
def full_name(gender=None):
    gender = gender or Gender.RANDOM
    ...

虽然它确实可以工作,但我会投反对票,因为只有在必要时才应该对EnumMeta进行子类化,并且还有其他解决此问题的方法。请参见何时应该对EnumMeta进行子类化以了解有关EnumMeta子类化的详细信息。但是,由于这是一次成功的努力,我将为其提供悬赏。 - Ethan Furman

5

你可能应该在你的Enum中创建一个方法来获取随机性别:

import random
import enum

class Gender(enum.Enum):
    FEMALE = 'female'
    MALE = 'male'

    @classmethod
    def get_gender(cls):
        return random.choice([Gender.FEMALE, Gender.MALE])

Gender.get_gender()

我们将这个枚举对象用作某个方法的默认值,应该是Gender.RANDOM,而不是Gender.get_random()或者Gender.RANDOM()。这是为了清晰的API。 - Lo L

2

我认为您想要一个随机方法,而不是直接将值保存在变量中,因为如果这样做,值将永远不会更改:

如果您不想使用函数

import random import enum

class Gender(enum.Enum):

  MALE = 'male'
  FEMALE = 'female'
  RANDOM = random.choice([MALE, FEMALE])


  def __getattribute__(self,item):
    if item == "RANDOM":
      Gender.RANDOM = random.choice([self.MALE, self.FEMALE])
      return Gender.RANDOM
    else:
      return object.__getattribute__(self, item)

gender = Gender()

看这里:

   gender.RANDOM
=> 'female'
   gender.RANDOM
=> 'male'
   gender.RANDOM
=> 'male'
   gender.RANDOM
=> 'male'
   gender.RANDOM
=> 'female'
   gender.RANDOM
=> 'male'
   gender.RANDOM
=> 'male'
   gender.RANDOM
=> 'female'

我尝试过这个,它可以工作,但是大写字母的方法看起来很丑。 - Lo L
@LkRi 我知道它看起来很丑...我只是按照你的例子做的,显然你可以放想要的名称。 - developer_hatch
@LkRi 现在,看我改了它,甚至每次调用随机方法时,都将其存储在RANDOM变量中。 - developer_hatch
@EthanFurman 忘记类方法吧,我误解了问题。 - developer_hatch
你之前的回答更好一些 ;) __getattribute__ 这个方法容易出错,而且对于这么简单的问题来说有点杀鸡焉用牛刀。 - Ethan Furman
显示剩余2条评论

2

由于RANDOM并不是你枚举中的一个选项,因此我认为更加连贯的方法是将其保持为一个方法而非属性(毕竟它并不是属性!)。

import random
import enum


class Gender(enum.Enum):
    MALE = 'male'
    FEMALE = 'female'

    @staticmethod
    def random():
        return random.choice(list(Gender))

然后,您可以将“我不选择”的行为转移到实际更有意义的函数中。
def full_name(gender=None):
    if gender is None:
        gender = Gender.random()
    # ...

0

从文档中可以得知:"枚举是一组绑定到唯一的常量值的符号名称(成员)集合。" 你需要切换到另一种表示方式,比如类。


为什么?保留一个“枚举”有其优点,特别是对于使用“性别”类型。 - Reblochon Masque
我认为Pythonic的方式就是创建一个函数,返回枚举之外的随机性别。 - LeGBT
1
使用 Enum 是可以的。但是使用 RANDOM 作为非常量是不行的。 - Ethan Furman

0

尽管在许多情况下修改类可能更清晰和DRY,但如果您只需要执行一次或不想修改枚举,则可以执行以下操作:

random.choice([enm.value for enm in Gender])


0

另一个适合您目标的选项是:

from enum import Enum
import random

class Gender(Enum):
    MALE = object()
    FEMALE = object()

    @classmethod
    def get_random_gender(cls):
        gender = random.choice(cls._member_names_)
        return gender.lower()


print(Gender.get_random_gender())
>> female

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接