Search code examples
pythonpython-3.xclassnameerror

Python - Using classes as default values of another class' attribute - NameError


I'm creating a structure of classes for a wrapper of an API I'm currently writing. I have multiple classes defined inside my models file. I want to assign the default value of some attributes of classes to other classes. When I do this, I get a NameError because sometimes I try to use classes that are defined below the current class, thus Python does not know these classes yet. I've tried multiple solutions but none of them seem to work. Does anybody know an alternative or has experience with this?

my classes I've defined:

class RateResponse(BaseModel):

    def __init__(self, 
        provider=Provider()
    ):

        self.provider = provider


class Provider(ObjectListModel):

    def __init__(self):

        super(Provider, self).__init__(list=[], listObject=ProviderItem)

    @property
    def providerItems(self):
        return self.list

class ProviderItem(BaseModel):

    def __init__(self,
        code=None,
        notification=Notification(),
        service=Service()
    ):

        self.code = code
        self.notification = notification
        self.service = service

As you can see above, I'm initialising the attribute 'provider' on the class RateResponse with the an empty object of the class Provider, which is defined below it. I'm getting a NameError on this line because it's defined below RateResponse.

provider=Provider()
NameError: name 'Provider' is not defined

The simple solution to above would be to shift the places of the classes. However, this is only a snippet of my file that is currently 400 lines long, all with these types of classes and initializations. It would be impossible to order them all correctly.

I've looked up some solutions where I thought I could return an empty object of a class by a string. I thought the function would only evaluate after all the classes were defined, but I was wrong. This is what I tried:

def getInstanceByString(classStr):
    return globals()[classStr]()


class RateResponse(BaseModel):

    def __init__(self, 
        provider=getInstanceByString('Provider')
    ):

        self.provider = provider

But to no avail. Does anybody have experience with this? Is this even possible within Python? Is my structure just wrong? Any help is appreciated. Thanks.


Solution

  • This code might not mean what you want it to mean:

    class RateResponse(BaseModel):
    
        def __init__(self, 
            provider=Provider()
        ):
            ...
    

    This code is saying that when this class is declared you want to make an instance of Provider which will be the default value for the provider parameter.

    You may have meant that the default argument should be a new instance of Provider for each client that makes an instance of RateResponse.

    You can use the Mutable Default Argument pattern to get the latter:

    class RateResponse(BaseModel):
    
        def __init__(self, provider=None):
            if provider is None:
                provider = Provider()
            ...
    

    However, if you really do want a single instance when the client wants the default you could add a single instance below the Provider definition:

    class Provider(ObjectListModel):
        ...
    Singleton_Provider = Provider()
    

    Then the RateResponse class could still use the current pattern, but instead perform this assignment inside the if:

            if provider is None:
                provider = Singleton_Provider
    

    At the time that the assignment is performed, the Singleton_Provider will have been created.