Is it possible to change the attribute value in the enum? - python

I need to reassign the attribute value in Enum.
from enum import Enum
class Number(Enum):
number = "1"
Number.number = "2" # AttributeError: cannot reassign member 'number'
I tried to reassign the attribute, but I got:
AttributeError: cannot reassign member 'number'

Author's note: This is a horrible idea.
Let's just delete the string "1" from Python and replace it with "2"
from ctypes import c_byte
from enum import Enum
from sys import getsizeof
def change_enum_value(old: object, new: object) -> None:
"""
Assigns contents of new object to old object.
The size of new and old objection should be identical.
Args:
old (Any): Any object
new (Any): Any object
Raises:
ValueError: Size of objects don't match
Faults:
Segfault: OOB write on destination
"""
src_s, des_s = getsizeof(new), getsizeof(old)
if src_s != des_s:
raise ValueError("Size of new and old objects don't match")
src_arr = (c_byte * src_s).from_address(id(new))
des_arr = (c_byte * des_s).from_address(id(old))
for index in range(len(des_arr)):
des_arr[index] = src_arr[index]
class Number(Enum):
number = "1"
change_enum_value(Number.number.value, "2")
print(Number.number.value) # 2
You don't have the "1" anymore, quite literally.
>>> "1"
'2'
>>>
which sure is a tad concerning...

When using Enum, your number = "1" is considered as one of the enum entry.
So, firstly, it is thought to be used this way, for example :
from enum import Enum
class Number(Enum):
numberOne = "1"
numberTwo = "2"
By the way, when you access to Number.number, you access to the "enum item", which is more than just the value. The item has, indeed, a name (number) and a value ("1").
So, in theory, the good way to change the value should be :
Number.number.value = "2"
But, in any was, an Enum is made to not be mutable. So you can't do that anyway.

Conceptually, an Enum is a way to give "nicer names" to "constants" and then use the nicer names in your code rather than using the constants.
This intent is implemented by Python by making the enum members as "functionally constant" ... See Python Enum Documentation.
The names associated with Enums are not like variables.
Hence you cannot change the value of the name.

If you read the enum Python documentation:
The attributes, in your case Number.number, etc., are enumeration members (or members) and are functionally constants.

Related

How to correctly specify the type of the argument?

I have a ButtonTypes class:
class ButtonTypes:
def __init__(self):
self.textType = "text"
self.callbackType = "callback"
self.locationType = "location"
self.someAnotherType = "someAnotherType"
And a function that should take one of the attributes of the ButtonTypes class as an argument:
def create_button(button_type):
pass
How can I specify that the argument of the create_button function should not just be a string, but exactly one of the attributes of the ButtonTypes class?
Something like this:
def create_button(button_type: ButtonTypes.Type)
As far as I understand, I need to create a Type class inside the ButtonTypes class, and then many other classes for each type that inherit the Type class, but I think something in my train of thought is wrong.
It sounds like you actually want an Enum:
from enum import Enum
class ButtonTypes(Enum):
textType = "text"
callbackType = "callback"
locationType = "location"
someAnotherType = "someAnotherType"
def func(button_type: ButtonTypes):
# Use button_type
The enum specifies a closed set of options that the variable must be a part of.
Use an enumerated type.
from enum import Enum
class ButtonType:
TEXT = "text"
CALLBACK = "callback"
LOCATION = "location"
SOMETHINGELSE = "someOtherType"
def create_button(button_type: ButtonType):
...
This goes one step further: not only are there only 4 values of type ButtonType, but no string will work, even at runtime, since your function will either ignore the string value associated with the ButtonType value altogether, or use code to extract the string that will break if button_type is an arbitrary string.

Python type hinting annotation for Dataclass attribute

I have a dataclass and I use it as a constant store.
#dataclass
class MyClass:
CONSTANT_1 = "first"
CONSTANT_2 = "second"
I have a function:
def my_func(value: ?):
print(value)
I want to add annotation to my function to specify that possible value is one of attribute of MyClass
How to do it (I am using python 3.10) ?
Hopefully I not misunderstand the ask, please let me know if so. But I think in this case is best to use Enum type in python.
Here is a simple example:
from enum import Enum
class MyEnum(Enum):
CONSTANT_1 = "first"
CONSTANT_2 = "second"
Then to answer the second part, for annotation the ? becomes a MyEnum. This means any enum member of this type, but not the type (class) itself.
def my_func(value: MyEnum):
print(value, value.name, value.value)
Putting it all together, it becomes like:
from enum import Enum
class MyEnum(Enum):
CONSTANT_1 = "first"
CONSTANT_2 = "second"
def my_func(value: MyEnum):
# technically you can remove this check
if not isinstance(value, MyEnum):
return
print(value, value.name, value.value)
# note below: only type checker or ide complain, but code still runs fine
my_func('hello') # not OK!
my_func('second') # not OK!
my_func(MyEnum) # not OK!
my_func(MyEnum.CONSTANT_1) # OK
I think you're asking an XY problem. From your response in the comments, it seems like what you want is rather:
Have a class-like interface to hold a bunch of constant values.
Constraint the argument to only take the above values.
As as mentioned in rv.kvetch's answer, the conventional way of doing this is to use enums. I'm not sure what you mean by "wanting to skip .value", the value field of an enum simply gives you what's associated with that enum, and I would say that it's not important at all. Here's an example:
class StrEnum(enum.Enum):
FIRST = "first"
SECOND = "second"
class StrEnum2(enum.Enum):
FIRST = "first"
SECOND = "second"
print(StrEnum.FIRST.value) # first
print(StrEnum2.FIRST.value) # first
print(StrEnum.FIRST.value == StrEnum2.FIRST.value) # True
print(StrEnum.FIRST == StrEnum2.FIRST) # False
class IntEnum(enum.Enum):
FIRST = enum.auto()
SECOND = enum.auto()
print(IntEnum.FIRST.value) # 1
print(IntEnum.SECOND.value) # 2
What I want to show with this example are two things:
You don't really need .value at all if you're just comparing enums.
You don't even need to manually assign values to the enums; you can use enum.auto() to auto-assign a unique value to it.
Because at the end of the day, enums themselves already represent a choice among valid choices, so it doesn't matter what values it has.
That said, if what you want is just to put a type constraint on what values an argument can type, and not have to use enums, then you can use the Literal type. See this answer for details. For your example, you could do something like:
from typing import Literal, Final
def my_func(value: Literal["first", "second"]):
print(value)
my_func("first") # ok
my_func("not first") # bad
x = "first"
y: Final = "first"
my_func(x) # bad, because `x` is not final
my_func(y) # ok
But note that type annotations don't really prevent you from calling a function with an invalid value, it's just a hint for IDEs and type checkers.

Arbitrary type conversion in python

An arbitrary typecasting function (shown below as cast) seems like a fairly straightforward function:
print(type(variable))
variable = cast(variable,type) # where type is any type included in __builtins__
print(type(variable))
And the result:
>>> <original_type>
>>> <type>
Does such a function exist in python? I can't seem to find any reference to it if it does. If this function does not exist, please explain the rationale for why it does not.
As one example usage, I have a config with arbitrarily many values, and a schema with the desired type of each. I want to check that specified value for each config variable can be cast as corresponding type specified in the schema. Treating each as a dict below for convenience:
for variable in config.keys():
val = config[variable]
type_name = schema[variable]
try:
config[variable] = cast(val,type_name)
except TypeError:
print("Schema checking failed for variable {}".format(variable))
Ok, I think the comments have covered the matter in enough detail so I'll just try to summarize my best understanding of them here. Most of this is by way of #juanpa.arrivillaga.
A standard python casting operation like int(x) (or more precisely, a type conversion operation), is actually a call to the __call__() function of an object. Types like int, float, str, etc are all object classes and are all instances of the metaclass type. A call to one of these instance of type e.g. int.__call__() calls the int object constructor which creates a new instance of that type and initializes it with the inputs to __call__().
In short, there is nothing special or different about the common python "type conversions" (e.g. int(x), str(40)) other than that the int and str objects are included in __builtins__.
And to answer the original question, if type_name is an instance of the type class then the type_name.__call__() function simply declares and initializes a new instance of that type. Thus, one can simply do:
# convert x to type type_name
x = type_name(x)
however this may cause an exception if x is not a valid input to the type_name constructor.
To cast a value in another type you can use the type itself, you can pass the type as an argument and call it into a function and you can get it from the builtins module if you sure that the type is a builtin:
value = "1"
value = int(value) # set value to 1
value = 1
value = str(value) # set value to "1"
def cast(value, type_):
return type_(value)
import buitlins
def builtin_cast(value, type_):
type_ = getattr(buitlins, type_, None)
if isinstance(type_, type):
return type_(value)
raise ValueError(f"{type_!r} is not a builtins type.")
value = cast("1", int) # set value to 1
value = cast(1, str) # set value to "1"
value = builtin_cast("1", "int") # set value to 1
value = builtin_cast(1, "str") # set value to "1"

Returned enum type not recognised [duplicate]

I found a very strange behavior in the Enum class in Python. So the enumerated type is simple:
from enum import Enum
Analysis = Enum('Analysis', 'static dynamic')
So I use this enumerated type in for step objects so that they store it in the attribute analysis, as follows:
class Step:
def __init__(self):
self.analysis = None
self.bcs = []
Very simple so far, so when I have a few of these steps in a list, then I try to see the enumerated type and it has been assigned correctly. But they are not equal:
# loop over steps
for s, step in enumerate(kwargs['steps']):
print(kwargs)
print(step)
print(step.analysis)
print("test for equality: ",(step.analysis == Analysis.static))
quit()
which prints
{'mesh': <fem.mesh.mesh.Mesh object at 0x10614d438>,
'steps': [<hybrida.fem.step.Step object at 0x10614d278>,
<hybrida.fem.step.Step object at 0x10616a710>,
<hybrida.fem.step.Step object at 0x10616a390>]}
Step:
analysis: Analysis.static
bcs: [<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a0f0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a320>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a3c8>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a470>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a518>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a5c0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a668>]
Analysis.static
test for equality: False
This is not correct, but I have no ideas on how to debug this.
UPDATE
Following the suggestion by #martineau, I created an IntEnum instead and that solved my problem. Yet, I don't understand why the normal Enum doesn't work.
In the comments, you say:
The input file contains many steps, and every time I add a new step I
have to set up the analysis type
If I understand you correctly, you're saying that you create a new Enum object each time you add a new step. This may be why you're seeing your "bug". The values of two different Enum objects, despite having the same name and order, do not necessarily compare as equal. For example:
import enum
Analysis1 = enum.Enum("Analysis", "static dynamic")
Analysis2 = enum.Enum("Analysis", "static dynamic")
But:
>>> Analysis1.static == Analysis2.static
False
This happens because the equality operator is not defined for Enum objects, as far as I can tell, so the default behavior of checking ids is used.
As #martineau suggests in the comments, one way of avoiding this issue is to instead use the IntEnum type, which subclasses int, and therefore defines the equality operator in terms of the value of the Enum, not the id:
import enum
Analysis1 = enum.IntEnum("Analysis", "static dynamic")
Analysis2 = enum.IntEnum("Analysis", "static dynamic")
Then:
>>> Analysis1.static == Analysis2.static
True
Why have Enum and IntEnum?
It may seem at first glance that IntEnum is always what we want. So what's the point of Enum?
Suppose you want to enumerate two sets of items, say, fruits and colors. Now, "orange" is both a fruit, and a color. So we write:
Fruits = enum.IntEnum("Fruits", "orange apple lemon")
Colors = enum.IntEnum("Colors", "orange red blue")
But now:
>>> Fruits.orange == Colors.orange
True
But, philosophically speaking, "orange" (the fruit) is not the same as "orange" (the color)! Shouldn't we be able to distinguish the two? Here, the subclassing of int by IntEnum works against us, as both Fruits.orange and Colors.orange equate to 1. Of course, as we saw above, comparison of Enums compares ids, not values. Since Fruits.orange and Colors.orange are unique objects, they do not compare as equal:
Fruits = enum.Enum("Fruits", "orange apple lemon")
Colors = enum.Enum("Colors", "orange red blue")
So that:
>>> Fruits.orange == Colors.orange
False
and we no longer live in a world where some colors are things that you can find in the produce section of your local grocery store.
In case anyone else finds themselves here after us, we experienced the same issue. We were able to trace it back to an unintentional mixing of absolute and relative imports, in a manner similar to the situation described below.
# File: package/module/analysis_types.py
Analysis = enum.Enum("Analysis", "static dynamic")
# File: package/module/static_thing.py
from .analysis_types import Analysis
class StaticThing:
...
analysis = Analysis.static
...
# File: package/module/static_thing_test.py
from package.module.static_thing import StaticThing
from .analysis_types import Analysis
# This throws an AssertionError because as
# id(StaticThing.analysis) != id(Analysis.static)
assert StaticThing.analysis == Analysis.static
Expected behavior was restored with the following changes:
# File: package/module/static_thing_test.py
from .static_thing import StaticThing
from .analysis_types import Analysis
# This does NOT throw an AssertionError because as
# id(StaticThing.analysis) == id(Analysis.static)
assert StaticThing.analysis == Analysis.static
For anyone who's stuck in the situation as described by Austin Basye, and cannot resolve the issue by just changing the imports, try using the enum value.
That is, if obj1.type1 == obj2.type1 is False (but it should be True), check if obj1.type1.value == obj2.type2.value works.
In this case, Analysis1.static.value == Analysis2.static.value should return the correct value all the time.

Enum in Python doesn't work as expected

I found a very strange behavior in the Enum class in Python. So the enumerated type is simple:
from enum import Enum
Analysis = Enum('Analysis', 'static dynamic')
So I use this enumerated type in for step objects so that they store it in the attribute analysis, as follows:
class Step:
def __init__(self):
self.analysis = None
self.bcs = []
Very simple so far, so when I have a few of these steps in a list, then I try to see the enumerated type and it has been assigned correctly. But they are not equal:
# loop over steps
for s, step in enumerate(kwargs['steps']):
print(kwargs)
print(step)
print(step.analysis)
print("test for equality: ",(step.analysis == Analysis.static))
quit()
which prints
{'mesh': <fem.mesh.mesh.Mesh object at 0x10614d438>,
'steps': [<hybrida.fem.step.Step object at 0x10614d278>,
<hybrida.fem.step.Step object at 0x10616a710>,
<hybrida.fem.step.Step object at 0x10616a390>]}
Step:
analysis: Analysis.static
bcs: [<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a0f0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a320>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a3c8>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a470>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a518>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a5c0>,
<hybrida.fem.conditions.dirichlet.Dirichlet object at 0x10616a668>]
Analysis.static
test for equality: False
This is not correct, but I have no ideas on how to debug this.
UPDATE
Following the suggestion by #martineau, I created an IntEnum instead and that solved my problem. Yet, I don't understand why the normal Enum doesn't work.
In the comments, you say:
The input file contains many steps, and every time I add a new step I
have to set up the analysis type
If I understand you correctly, you're saying that you create a new Enum object each time you add a new step. This may be why you're seeing your "bug". The values of two different Enum objects, despite having the same name and order, do not necessarily compare as equal. For example:
import enum
Analysis1 = enum.Enum("Analysis", "static dynamic")
Analysis2 = enum.Enum("Analysis", "static dynamic")
But:
>>> Analysis1.static == Analysis2.static
False
This happens because the equality operator is not defined for Enum objects, as far as I can tell, so the default behavior of checking ids is used.
As #martineau suggests in the comments, one way of avoiding this issue is to instead use the IntEnum type, which subclasses int, and therefore defines the equality operator in terms of the value of the Enum, not the id:
import enum
Analysis1 = enum.IntEnum("Analysis", "static dynamic")
Analysis2 = enum.IntEnum("Analysis", "static dynamic")
Then:
>>> Analysis1.static == Analysis2.static
True
Why have Enum and IntEnum?
It may seem at first glance that IntEnum is always what we want. So what's the point of Enum?
Suppose you want to enumerate two sets of items, say, fruits and colors. Now, "orange" is both a fruit, and a color. So we write:
Fruits = enum.IntEnum("Fruits", "orange apple lemon")
Colors = enum.IntEnum("Colors", "orange red blue")
But now:
>>> Fruits.orange == Colors.orange
True
But, philosophically speaking, "orange" (the fruit) is not the same as "orange" (the color)! Shouldn't we be able to distinguish the two? Here, the subclassing of int by IntEnum works against us, as both Fruits.orange and Colors.orange equate to 1. Of course, as we saw above, comparison of Enums compares ids, not values. Since Fruits.orange and Colors.orange are unique objects, they do not compare as equal:
Fruits = enum.Enum("Fruits", "orange apple lemon")
Colors = enum.Enum("Colors", "orange red blue")
So that:
>>> Fruits.orange == Colors.orange
False
and we no longer live in a world where some colors are things that you can find in the produce section of your local grocery store.
In case anyone else finds themselves here after us, we experienced the same issue. We were able to trace it back to an unintentional mixing of absolute and relative imports, in a manner similar to the situation described below.
# File: package/module/analysis_types.py
Analysis = enum.Enum("Analysis", "static dynamic")
# File: package/module/static_thing.py
from .analysis_types import Analysis
class StaticThing:
...
analysis = Analysis.static
...
# File: package/module/static_thing_test.py
from package.module.static_thing import StaticThing
from .analysis_types import Analysis
# This throws an AssertionError because as
# id(StaticThing.analysis) != id(Analysis.static)
assert StaticThing.analysis == Analysis.static
Expected behavior was restored with the following changes:
# File: package/module/static_thing_test.py
from .static_thing import StaticThing
from .analysis_types import Analysis
# This does NOT throw an AssertionError because as
# id(StaticThing.analysis) == id(Analysis.static)
assert StaticThing.analysis == Analysis.static
For anyone who's stuck in the situation as described by Austin Basye, and cannot resolve the issue by just changing the imports, try using the enum value.
That is, if obj1.type1 == obj2.type1 is False (but it should be True), check if obj1.type1.value == obj2.type2.value works.
In this case, Analysis1.static.value == Analysis2.static.value should return the correct value all the time.

Categories

Resources