Search code examples
pythonpython-3.xdesign-patterns

Passing keyword arguments with default values in a layered call


I am working on a python library where I want to give some flexibility to the user. I am facing below challenge.

Let's say I exposed the class Client to the user, which is defined like below:

class Client:
    def __init__(self, path=None):
        self.file_obj = FileOpener(path = path)
    

And my FileOpener class is:

class FileOpener:
    def __init__(self, path='my/default/path'):
        # My logic 
        pass

In above code, the issue is, if the user initialized the Client object as c = Client(), my underlying FileOpener class receives None as the path. But I want that even if None is passed, it should pick the default path.

Here are few solutions for this problem that I came up with, but they are somewhat flawed:

  1. The most basic approach - filter the arguments and pass only those which are not None. I don't want to put a if - else ladder or create any unnecessary dict to store the values. Its not maintainable / readable and new coders can mess up easily.

  2. I can define the default value in Client class itself. If the user doesn't pass anything, it will receive the default value. The issue is, suppose I have multiple layers, and in future I want to change the default value, I will need to change it everywhere. Hence this approach is not maintainable at all.

  3. Second thing that came to my mind was, using **kwargs, that I can pass easily to underlying objects. This will not pass None by default if user didn't explicitly pass it. But now my user doesn't know what args my class is taking. He will need to go through the docs every time and won't be able to take advantage of his code editor (example - in VS Code you can use auto complete).

I want to understand if there is any standard approach for resolving this issue. Or any suggestions to get rid of the flaws in my approach. I want to have a maintainable solution so that if anyone in future comes to contribute, he can understand just by looking at the code and doesn't mess up.


Solution

  • I came up with another approach, which is maintainable and flexible. Though it still doesn't make the default value of keyword args useful. It just makes them optional. Use a class attribute to define the default path in the underlying FileOpener class. This way, the default value is centralized and easily modifiable. Here's an example:

    class FileOpener:
        default_path = 'my/default/path'
    
        def __init__(self, path=None):
            self.path = path or self.default_path
            # my logic here
            pass
    
    class Client:
        def __init__(self, path=None):
            self.file_obj = FileOpener(path=path)
    

    This approach allows you to set the default path in one place (FileOpener class) and avoids cluttering the Client class with default values. It's maintainable, readable, and provides a clear structure for future contributors.

    Now, if you want to change the default path, you only need to update it in the FileOpener class. This design has better code maintainability and readability without sacrificing flexibility.