osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Enums are Singletons - but not always?


Ralf M. wrote:

> Hello,
> 
> recently I wrote a small library that uses an Enum. That worked as
> expected. Then I added a main() and if __name__ == "__main__" to make it
> runable as script. Now Enum members that should be the same aren't
> identical any more, there seem to be two instances of the same Enum.
> 
> I think I know what's going on, but cannot find a good and elegant way
> to avoid the problem. I hope someone here can help me there.
> 
> Below are a simplified code sample, the results when I run it and my
> thoughts.
> 
> ##### Code of mod1.py #####
> import enum, mod2
> class En(enum.Enum):
>      A = 1
>      B = 2
> def main():
>      a = mod2.getA()
>      print("a is En.A:", a is En.A)
>      print("a:", repr(a), "    En.A:", repr(En.A))
>      print("id(a), id(a.__class__)", id(a), id(a.__class__))
>      print("id(En.A), id(En)      ", id(En.A), id(En))
> if __name__ == "__main__":
>      main()
> ##### End of mod1.py #####
> 
> ##### Code of mod2.py #####
> import mod1
> def getA():
>      return mod1.En.A
> ##### End of mod2.py #####
> 
> ##### Results when run: #####
> C:\tmp>py mod1.py
> a is En.A: False
> a: <En.A: 1>     En.A: <En.A: 1>
> id(a), id(a.__class__) 33305864 7182808
> id(En.A), id(En)       33180552 7183752
> 
> C:\tmp>py
> Python 3.7.4 (tags/v3.7.4:e09359112e, Jul  8 2019, 20:34:20) [MSC v.1916
> 64 bit
> (AMD64)] on win32
> Type "help", "copyright", "credits" or "license" for more information.
>  >>> import mod1
>  >>> mod1.main()
> a is En.A: True
> a: <En.A: 1>     En.A: <En.A: 1>
> id(a), id(a.__class__) 49566792 44574280
> id(En.A), id(En)       49566792 44574280
>  >>>
> 
> So: When run as script there are two instances of En (different ids),
> but when mod1 is imported and mod1.main() is run it works as expected
> (just one instance of En, same ids).
> BTW: py -m mod1 doesn't work either.
> 
> What I thing is happening:
> When the script is run, mod1.py is executed and an instance of En and
> its members is created. During the same run mod1 is also imported (via
> mod2), the file mod1.py is executed again as part of the import and
> another, different instance of En and its members is created.
> 
> How do I have to change mod1.py to avoid the problem?
> Currently I have moved main() into a new file script.py. That works, but
> is not what I wanted.

But that is exactly the right approach. Avoid importing the main script 
under its name because that will run all code that is not guarded by 

if __name__ == "__main__": ...

twice. In your case it's enum, but the same goes for every class, function 
or object and the module itself. In Python they are all created rather than 
declared.

> I doubt it's a bug in the enum module, but should that be the case, I'm
> willing to open an issue on the bug tracker.
> 
> Or can nothing be done about it?

mod2 could import the __main__ module, e. g. 

> ##### Code of mod2.py #####
  import __main__ as mod1
> def getA():
>      return mod1.En.A
> ##### End of mod2.py #####

but that would hardcode the assumption that __main__ is always mod1.py.

The only sane option is to not put anything in the main script that needs to 
be imported by other modules.

> Looking forward to any ideas
> Ralf M.
> 
> P.S.:
> As I was about to send this post the following modification occured to
> me (see below). It works, but it doesn't feel right to import a module
> directly from inside itself.
> ##### Modified code of mod1.py (one line added) #####
> import enum, mod2
> class En(enum.Enum):
>      A = 1
>      B = 2
> from mod1 import En  # NEW LINE, overwrite just defined En
> def main():
>      a = mod2.getA()
>      print("a is En.A:", a is En.A)
>      print("a:", repr(a), "    En.A:", repr(En.A))
>      print("id(a), id(a.__class__)", id(a), id(a.__class__))
>      print("id(En.A), id(En)      ", id(En.A), id(En))
> if __name__ == "__main__":
>      main()