21

I have created such Enum object:

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

and i want to get really random value each time, but it does not work:

>>> 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'>

I have also tried use lambda, but it's looks not so good, although it works:

Gender.RANDOM()

Are there other way to get random values each time, without using lambda expressions?

We use this enum object as default value of the argument of the some method that's why it should be an attribute, not a function, because when we use Gender.FEMALE it is not a function, it's an attribute and Gender.RANDOM should be an attribute too:

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


def full_name(gender=Gender.RANDOM):
    ...
7
  • 1
    Default attributes are only evaluated once (when you define the function), so the random value will only be random the one time, and every time you call that function you will get the same initial value. Commented Nov 17, 2017 at 15:32
  • That's usual behavior which i want to change. Commented Nov 17, 2017 at 15:33
  • That is a Python fundamental behavior and you cannot change it. Commented Nov 17, 2017 at 15:35
  • I was referring to the workaround. Of course i can't change the Python and i don't want to do it. Commented Nov 17, 2017 at 15:36
  • 1
    Obligatory xkcd Commented Nov 21, 2017 at 18:44

8 Answers 8

14

As others have said, the best way is to just make random() be a method on your enum class to make it clear that RANDOM is not a member.

However, since I like puzzles:

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()))

In your full_name definition, using Gender.RANDOM as a default value will not get you what you want. The standard for such is:

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

Which is going to be confusing to the reader. This is much better using a normal method:

def full_name(gender=None):
    if gender is None:
        gender = Gender.random()
Sign up to request clarification or add additional context in comments.

1 Comment

You are right. It's much clearer, than using Gender.RANDOM as default value. Thanks!
8
+100

I tried a way with metaclasses. And it works!

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

Here by making RANDOM_ATTR the metaclass of Gender, Gender is like an object of class RANDOM_ATTR, so Gender has the property RANDOM.

However,the below code you described in your question doesn't work the way you expect.

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

The RANDOM property will be called only once. To know why, please read this answer. Default arguments are like attributes to function, which will be initialised only once.

For that i would suggest you do something like this:

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

1 Comment

While it does work, I'm down-voting because EnumMeta should only be subclassed when necessary and there are other ways to solve this problem. See When should I subclass EnumMeta for details on EnumMeta subclassing. However, since this was a successful effort I am offering a bounty on it.
5

You probably should create a method in your Enum to obtain a random gender:

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()

1 Comment

We use this enum object as default value of some method, and it should be Gender.RANDOM, not Gender.get_random() or Gender.RANDOM(). It's for clear API.
2

I think you want a method random, instead to save the value directly in the variable, because if you do that, the value will never change:

If you DON'T WANT A FUNCTION

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()

look:

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

7 Comments

I tried this, it's works, but methods with uppercase is looks ugly.
Image
@LkRi I know it look ugly.. I just followed your example, obviously you can put the name you want
Image
@LkRi now, look I changed it, even every time you call the random method, you store it in the RANDOM variable
Using a classmethod your RANDOM signature should be def RANDOM(cls): and your line can me RANDOM = random.choice(list(cls))`
Image
@LkRi now I understood your question
|
2

As RANDOM is not really an item in your enumeration, I think a more coherent approach would be to keep it precisely as a method and not an attribute (it isn't after all!).

import random
import enum


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

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

Then, you could transfer the "I'm not choosing" behaviour to the function where it actually makes more sense.

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

Comments

0

From the documentation : "An enumeration is a set of symbolic names (members) bound to unique, constant values." You need to switch to another representation like a class.

3 Comments

Why? Keeping an Enum has its advantages, notably using types for the Gender
Well I think that the pythonic way would simply be to make a fonction that return a random Gender outside of the enum...
Using an Enum is fine. Using RANDOM as a non-constant is not.
0

Although modifying the class may be clearer and DRY-er in many cases, if you're only doing this once or don't want to modify the enum you could do:

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

Comments

0

Another option that works for your goal is:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.