Search code examples
pythonjsonpython-requests

What is the best practice for Python class structures where each class has the same initialization function but different class attributes?


I am working in Python (using the requests module) with an API communicating via JSON data structures. I could build my project on a web server using Javascript but I simply prefer to use Python at the moment.

The API is well documented and when I send a GET request I know exactly the shape of the response I'm going to receive. In order to better work with this received data, I'm creating class objects for each necessary schema so I can associate class methods/attributes to the given response I receive from the API.

What I found, as a way of tightening up my code and saving myself some time, is that I can create a class object, declare its attributes, and use the following code to set those attributes; so long as my attribute names match the expected keys in the JSON data object:

    def __init__(self, json: json) -> None:
        keys = self.__annotations__.keys()
        for key in keys:
            self.__setattr__(key, json[key])

This allows me to simply accept the JSON data object into the class initialization method and set the corresponding attributes.

Now for my question. Each schema has the exact same initialization method; the one above. They only have different attributes that match the given keys in the JSON objects.

So I have this kind of class structure as an example:

class ClassExample1:
    attribute1: float
    attribute2: float
    attribute3: str

    def __init__(self, json: json) -> None:
        keys = self.__annotations__.keys()
        for key in keys:
            self.__setattr__(key, json[key])

class ClassExample2:
    attribute1: bool
    attribute2: list

    def __init__(self, json: json) -> None:
        keys = self.__annotations__.keys()
        for key in keys:
            self.__setattr__(key, json[key])

I'm wondering if there isn't a better practice in which to structure my classes so that I'm not just copy/pasting the same code over and over again, whether that be inheritance or some other option I'm unaware of. I have several years of various types of development experience but unfortunately its been mostly in a position where I'm the sole developer for my company. I don't have a lot of knowledge of "standard/best practices" and am constantly trying to educate myself in these topics while being outside of a standard corporate development environment. So when it comes to situations where I see myself using some redundant method, or using the same code blocks over and over, or what have you; it feels like I might be missing some other way of completing the task.

Thank you for you all who stayed for the whole post, and I appreciate any feedback anyone can offer me.

Cheers.


Solution

  • That is what inheritance is for.

    Just create a base class with your __init__ code (and maybe you could do __repr__ as well), and do the subclasses just declaring the attributes :

    class Base:
        def __init__(self, json: json) -> None:
            keys = self.__annotations__.keys()
            for key in keys:
                self.__setattr__(key, json[key])
    
    
    class ClassExample1(Base):
        attribute1: float
        attribute2: float
        attribute3: str
    
    
    class ClassExample2(Base):
        attribute1: bool
        attribute2: list
    

    Also, note that if your classes will expose all attributes in the JSON mapping as they are, you could use dataclasses, and expand your JSON key/values as kwargs with the ** syntactic feature when instantiating then:

    
    
    from dataclasses import dataclass
    
    @dataclass
    class ClassExample1(Base):
        attribute1: float
        attribute2: float
        attribute3: str
    
    @dataclass
    class ClassExample2(Base):
        attribute1: bool
        attribute2: list
    
    ...
    def wherever():
        ...
        json = whatvercall()
        data1 = ClassExample1(**json)
    

    If you need any other shared capability across your classes, those could be implemented as methods, and inheritance should beat the dataclass use - otherwise, the dataclass approach will also give you a free __repl__ method, and other niceties (like dataclasses.asdict when re-serializing your values)