When is a default object created in Python?

Question:

I have a Python (3) structure like following:

  • main_script.py
  • util_script.py
  • AccessClass.py

The main script is calling a function in util with following signature:

def migrate_entity(project, name, access=AccessClass.AccessClass()):

The call itself in the main script is:

migrate_entity(project_from_file, name_from_args, access=access_object)

All objects do have values when the call is done.
However, As soon as the main script is executed the AccessClass in the function parameters defaults is initialized, even though it is never used. For example this main script __init__ will create the default class in the function signature:

if __name__ == "__main__":
  argparser = argparse.ArgumentParser(description='Migrate support data')
  argparser.add_argument('--name', dest='p_name', type=str, help='The entity name to migrate')
  load_dotenv()
  fileConfig('logging.ini')

  # Just for the sake of it
  quit()
  # The rest of the code...
  # ...and then
  migrate_entity(project_from_file, name_from_args, access=access_object)

Even with the quit() added the AccessClass is created. And if I run the script with ./main_script.py -h the AccessClass in the function signature is created. And even though the only call to the function really is with an access object I can see that the call is made to the AccessClass.__init__.

If I replace the default with None and instead check the parameter inside the function and then create it, everything is working as expected, i.e. the AccessClass is not created if not needed.

Can someone please enlighten me why this is happening and how defaults are expected to work?

Are parameter defaults always created in advance in Python?

Asked By: Sven

||

Answers:

Basically the mutable objects are initialized the moment you declare the function, not when you invoke it. That’s why it’s widely discouraged to use mutable types as defaults. You can use None as you mentioned and inside the body do the check if something is None and then initialize it properly.

def foo_bad(x = []): pass # This is bad

foo_bad() # the list initialized during declaration used
foo_bad([1,2]) # provided list used
foo_bad() # again the list initialized during declaration used 

def foo_good(x = None):
    if x is None:
        x=[]
    ... # further logic
Answered By: Gameplay

AccessClass is being created because you’ve set it as a default parameter, so it it’s in the scope of the file itself and will be initialised when the file is first imported. This is also why it’s not recommended to use lists or dicts as default parameters.

This is a much safer way of defining a default value if nothing is provided:

def migrate_entity(project, name, access=None):
    if access is None:
        access = AccessClass.AccessClass()

You could also use type hinting to demonstrate what type access should be:

def migrate_entity(project, name, access: Optional[AccessClass.AccessClass] = None): ...
Answered By: Peter
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.