3

Every documentation and answer I could find, says that in order to check if the program is "frozen" (an exe for example), we can use getattr(sys, 'frozen', False) in the following way:

import sys
if getattr(sys, 'frozen', False):
    print('program is frozen exe')
else:
    print('program is a .py script')

Where False is returned by default if the frozen attribute doesn't exist instead of throwing an AttributeError. An example from the console:

>>> getattr(sys, 'frozen')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'sys' has no attribute 'frozen'
>>> getattr(sys, 'frozen', False)
False
>>> hasattr(sys, 'frozen')
False

This is all fine, but there is a shorter version of this that does the same job, unless I'm missing something:

hasattr(sys, 'frozen')

Which simply returns True or False without the need to specify a default. Despite this being shorter and possibly more readable, every documentation and answer online uses getattr instead. I'm sure there's a clever difference I might be overlooking, which is why I'm asking this question.

Example sources that refer to getattr:

Ofer Sadan
  • 11,391
  • 5
  • 38
  • 62
  • 2
    `hasattr` only tells you if the attribute exists, it won't return its value in case it does. – Thierry Lathuille Dec 08 '19 at 18:01
  • Adding to @ThierryLathuille's answer, and thus by using `getattr` you'll get the value of `frozen` attribute and if it is `True` then only the `if` part is executed. If you wanna do it with hasattr, do `if hasattr(sys,'frozen') and sys['frozen']:`. – Ajay Dabas Dec 08 '19 at 18:13
  • What if the attribute exists and its value is `False`…? – deceze Dec 08 '19 at 18:32
  • I see your point. Are there any examples of when it's useful to force `sys.frozen` to exist and be `False`? Or any packages / extensions / program that do it? – Ofer Sadan Dec 08 '19 at 19:11
  • 1
    Thanks to your question I found that there was a missing `True` in the `PyInstaller` manual which led to an error when run: https://pyinstaller.readthedocs.io/en/stable/runtime-information.html – bomben Aug 07 '20 at 11:37

1 Answers1

1

The PyInstaller documentation itself uses the getattr style block of code here, so by the copy-paste effect, it will proliferate.

So then the question becomes: why does the PyInstaller documentation do it this way? As others said in comments, sys.frozen could theoretically be set to False or some other falsy value, in which case, hasattr(sys, 'frozen') would still return True:

>>> class Stuff:
...     pass
... 
>>> x = Stuff()
>>> x.yes = True
>>> hasattr(x, 'yes')
True
>>> getattr(x, 'yes', False)
True
>>> x.no = False
>>> hasattr(x, 'no')
True
>>> getattr(x, 'no', False)
False

But as used by PyInstaller, is it possible for sys.frozen to be set, but with a falsy value? Let's check the source code:

$ git clone git://github.com/pyinstaller/pyinstaller
Cloning into 'pyinstaller'...
...
$ cd pyinstaller
$ git grep '\(sys\.frozen\|frozen = \)' PyInstaller
PyInstaller/loader/pyiboot01_bootstrap.py:    sys.frozen = True
PyInstaller/utils/win32/winutils.py:            # True if "sys.frozen" is currently set.
PyInstaller/utils/win32/winutils.py:            is_sys_frozen = hasattr(sys, 'frozen')
PyInstaller/utils/win32/winutils.py:            # Current value of "sys.frozen" if any.
PyInstaller/utils/win32/winutils.py:            sys_frozen = getattr(sys, 'frozen', None)
PyInstaller/utils/win32/winutils.py:            sys.frozen = '|_|GLYH@CK'
PyInstaller/utils/win32/winutils.py:            # If "sys.frozen" was previously set, restore its prior value.
PyInstaller/utils/win32/winutils.py:                sys.frozen = sys_frozen
PyInstaller/utils/win32/winutils.py:                del sys.frozen

So this variable is only set in two scenarios:

  1. To True, while loading/bootstrapping; and
  2. To '|_|GLYH@CK' temporarily in a utility method.

It never receives a falsy value from PyInstaller.

So in practice, using hasattr(sys, 'frozen') will work fine. It just feels wrong from a correctness perspective, because it's an incorrectly handled edge case, which is probably why the PyInstaller documentation uses getattr instead: it's more future-proof.

ctrueden
  • 6,751
  • 3
  • 37
  • 69