Why does a Python variable keep the default value although changed inside a function?

Written by - 0 comments

Published on - last updated on February 17th 2024 - Listed in Coding Python


While working on a Python script, I was going nuts on a very strange behaviour of a variable, which was supposed to be changed inside a function and then be used again later in the script. But for an unknown reason (at least at the begin), the variable changed back to the default value.

Python

But let's start at the beginning - where the variable is defined.

Verbose argument should change variable to True

At the beginning of the Python script, the verbose variable is declared and set to False:

#!/usr/bin/python3
# ---------------------------------------------------------------------
# Define variables and defaults
ignore_list=[]
verbose=False

Later in the script the getargs() function is responsible to handle script arguments from the user. This is also where the -v / --verbose flag is defined:

# ---------------------------------------------------------------------
# Arguments
def getargs() :
  parser = argparse.ArgumentParser(description='Script help')
  parser.add_argument('-i', '--ignore', dest='ignore_list', required=False, help='Set warning threshold')
  parser.add_argument('-o', '--output', dest='output', choices=['nagios', 'prometheus'], required=False, help='Set output type to either nagios or prometheus (defaults to nagios)')
  parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', default=False, required=False, help='Enable verbose mode for debugging plugin')
  args = parser.parse_args()

The verbose argument is using the action 'store_true', which will automatically set the variable to True if this argument is used.

A bit further down the script, but still inside the getargs() function, the temporary variable args.verbose is read and its value (True if used) is used to overwrite the "verbose" variable, declared at the beginning of the script:

   if (args.verbose):
      verbose = args.verbose
      print(verbose)

And finally inside the main part of the script, getargs() is called and I verified the value of the "verbose" variable:

# ---------------------------------------------------------------------
# Main
getargs()
print(verbose)

But variable falls back to default value (False)

But when I executed the script, I got two different outputs:

$ python3 script.py -v
True
False

The first output (True) comes from the first print(verbose) line inside the getargs() function.

The second output (False) comes from the print(verbose) line in the main part of the script.

So why would the variable fall back to the default after leaving the getargs() function? Yeah... I was scratching my head a lot, because in other programming languages this would have worked (at least the limited list of languages I know of).

The (somewhat explaining) answer was found on StackOverflow by user f.rodrigues:

That's because 'global x' is different than 'fun x', 'fun x' overlaps/masks the 'global x', does whatever you do with it and then it ceases to exist. The mask is not there so former 'global x' is again the current 'x'.

Let me rewrite this explanation - if I understood this correctly: The variable "verbose" is a global variable because it is defined inside the main part of the Python script. When the variable is overwritten inside a function (here getargs() function), this overwritten value remains inside the function and is not transported outside of the function.

Use 'global variable' inside function

To handle this, the function needs to be told that it is in fact working with a global variable - prior to setting any value to the variable. 

Inside the getargs() function I therefore added the "global verbose" line:

  if (args.verbose):
      global verbose
      verbose = args.verbose
      print(verbose)

And after executing the Python script once more, both print outputs now show True:

$ python3 script.py -v
True
True

Mutable vs. Immutable variables

I received this helpful information from Claire M. on the BlueSky Social Media platform, appreciate the comment!

But why does Python behave this way? Why is a variable not simply set to a new value and retains the value across the whole script, including functions - as the data "flows" through the script?

The reason is not obvious to a Python beginner (or occasional Python script writer like me): There are mutable and immutable types of variables.

Mutable means the variable's value can be changed everywhere, across all functions, as the variable is handled during script execution. Mutable types are dict, list and set.

Immutable means the variable's value "reverts" to the global value once leaving a function. Or differently said: The global variable is actually never changed, only a "copied" variable inside the function has its value changed. By using the "global var" declaration inside the function (see above) the value of the "outer" global value is changed, too. Immutable types are int, float, str.


Add a comment

Show form to leave a comment

Comments (newest first)

No comments yet.

RSS feed

Blog Tags:

  AWS   Android   Ansible   Apache   Apple   Atlassian   BSD   Backup   Bash   Bluecoat   CMS   Chef   Cloud   Coding   Consul   Containers   CouchDB   DB   DNS   Database   Databases   Docker   ELK   Elasticsearch   Filebeat   FreeBSD   Galera   Git   GlusterFS   Grafana   Graphics   HAProxy   HTML   Hacks   Hardware   Icinga   Influx   Internet   Java   KVM   Kibana   Kodi   Kubernetes   LVM   LXC   Linux   Logstash   Mac   Macintosh   Mail   MariaDB   Minio   MongoDB   Monitoring   Multimedia   MySQL   NFS   Nagios   Network   Nginx   OSSEC   OTRS   Office   PGSQL   PHP   Perl   Personal   PostgreSQL   Postgres   PowerDNS   Proxmox   Proxy   Python   Rancher   Rant   Redis   Roundcube   SSL   Samba   Seafile   Security   Shell   SmartOS   Solaris   Surveillance   Systemd   TLS   Tomcat   Ubuntu   Unix   VMWare   VMware   Varnish   Virtualization   Windows   Wireless   Wordpress   Wyse   ZFS   Zoneminder