1

I want to use a function with a default argument (as in argument with default value) in two separate scripts in which that default argument is set differently. Of course, the function may also be called with the argument set to anything other than the default value.

Below is a minimal dummy example using functions to write a message msg to a list of files, write_list. The functions are defined in test_write_utils.py and are imported in multiple different scripts, only for this example in only one script, test_write_main.py.

test_write_utils.py:

""" def write_func(msg, write_list=[file1, file2]):

    for file in write_list:
        print('Write to %s' % file)
        # open(file, 'w').write(msg)
 """
 
def write_func2(msg, write_list=None):

    if write_list is None:
        write_list = [file1, file2]
    for file in write_list:
        print('Write to %s' % file)
        # open(file, 'w').write(msg)

class writeclass:

    # user can (but does not have to) provide a default write list
    def __init__(self, default_write_list=[]):

        self.default_write_list = default_write_list

    def write(self, msg, write_list=None):

        if write_list is None:
            write_list = self.default_write_list
        for file in write_list:
            print('Write to %s' % file)
            # open(file, 'w').write(msg)

test_write_main.py:

# from test_write_utils import write_func               # NameError: name 'file1' is not defined
from test_write_utils import write_func2, writeclass

file1 = 'file1.txt'
file2 = 'file2.txt'
write_list = [file1, file2]

# write_func('test')
# write_func2('test')                                   # NameError: global name 'file1' is not defined

# define variables in namespace of test_write_utils;
# import statement here instead of beginning (bad practice) only to make structure clear
import test_write_utils
test_write_utils.file1 = file1
test_write_utils.file2 = file2
write_func2('test')                                     # works

mywriter = writeclass(write_list)
mywriter.write('test')                                  # works

write_func (when uncommented) raises an error during import since it must have file1 and file2 defined at import time. write_func2, with default argument None based on this post, can be imported, but will raise an error during the function call due to separate namespaces of the scripts. If the variables are defined in the appropriate namespace test_write_utils (I then commented write_func out to avoid the import error), write_func2 works. However, the flow of information is obscured, i.e. the user does not see in test_write_utils.py where the variables are actually defined. Using a method of a class writeclass instead of functions also works and the default write list can be independently set in each script during instantiation.

Question: Is there a non-class-method-based, recommended way to use a function with a "variable" default argument in different scripts (in which the default argument is set differently)? I'm using Python 2.7.18, in case it is relevant for anything.

bproxauf
  • 1,076
  • 12
  • 23
  • Interesting question; that said, your class solution seems no better to me than calling func2 with write_list as an argument: `func2(msg, write_list)`, overriding the usefulness of a default argument. – Swifty Jul 12 '23 at 10:01
  • Could you explain what you are trying to achieve? Do you want to expose the value? Did you want to maintain consistency in a value used in a fucntion by N scripts? What is the goal? – Jason Chia Jul 12 '23 at 10:02
  • @Swifty: You are absolutely correct; however, for the class solution, one can use `__init__(self, write_list=[])`, so the user does not have to set the default argument if he chooses not to. For `write_func2`, this would still not work due to the namespace issue. – bproxauf Jul 12 '23 at 10:08
  • @JasonChia: So the goal is to use a function for two separate scripts that is completely identical apart from different default value(s) for its function argument(s). Imagine the situation where you have a second script apart from test_write_main.py, in which you want to usually (thus default; but not always) write to the files `file3.txt` and `file4.txt`. – bproxauf Jul 12 '23 at 10:12
  • `write_func2 ` seems totally reasonable to me. An option for deriving variants with different defaults would be https://docs.python.org/3/library/functools.html#functools.partial – Anentropic Jul 12 '23 at 10:28
  • You could initialize the class with some setting. I.e. Let script A call f = writeclass(mode1) and script A call f=writeclass(mode2) where the class itself changes the default value depending on the class attribute. – Jason Chia Jul 12 '23 at 10:45
  • @Anentropic: I just glanced over the link, but the way I understood it, partial does not set the default value, but the argument as a whole (meaning the user cannot use a non-default value for the argument if desired). – bproxauf Jul 12 '23 at 12:00
  • @JasonChia: The class in the post essentially already implements such a behavior. However, I'm interested if there's a non-class-based alternative. – bproxauf Jul 12 '23 at 12:03
  • I just had an idea, tested it and... It seems to work, You still need to implement a class, but your function with default argument does not have to be a class method: use a singleton class. – Swifty Jul 12 '23 at 13:20

1 Answers1

0

I implemented a test making use of a Singleton class, and that worked for me:

Module test.py

class SingletonClass(object):
  def __new__(cls):
    if not hasattr(cls, 'instance'):
      cls.instance = super(SingletonClass, cls).__new__(cls)
    return cls.instance


def f(file=None):
    if file == None:
        file = SingletonClass().file1
    print(file)

module main.py

from test import SingletonClass, f
SingletonClass().file1 = 'This is a test'
f()

# This is a test

module main2.py

from test import SingletonClass, f
SingletonClass().file1 = 'Another test'
f()

# Another test

Edit: after some thinking, I realize I overcomplicated this; using any shared variable is enough; for example, assuming you have a configuration file somewhere holding a configuration dictionary, you can do:

module test.py

from some_config_file import config_dict

def f(write_list=None):
    if write_list is None:
        write_list = config_dict.setdefault('writelist', [])
    for file in write_list:
        print(file)

module main1.py

from some_config_file import config_dict
from test import f

config_dict['writelist'] = ['foo', 'bar']
f()

# foo
# bar

module main2.py

from some_config_file import config_dict
from test import f

config_dict['writelist'] = ['hello', 'world']
f()

# hello
# world
Swifty
  • 2,630
  • 2
  • 3
  • 21