Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why use getattr instead of hasattr for sys.frozen?

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:

  • Determining application path in a Python EXE generated by pyInstaller
  • Pyinstaller documentation (Even uses hasattr on something else)
  • Cx_Freeze documentation
like image 959
Ofer Sadan Avatar asked Oct 23 '25 15:10

Ofer Sadan


1 Answers

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.

like image 171
ctrueden Avatar answered Oct 26 '25 07:10

ctrueden



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!