Home > Blockchain >  How to create a params dict from **kwargs in an if chain in Python?
How to create a params dict from **kwargs in an if chain in Python?

Time:03-10

I'm working on an API wrapper for a weather API. Here are two snippets of my code, which don't quite work:

def payload_helper(self, *args, **kwargs):
    if args:
        paylouad = args
    else:
        payload = {
            "units": "metric",
        }
    payload.update(kwargs)
    return payload
def weather(self, lat="", lon="", zip_code=""):
    payload = self.payload_helper()
    if lat:
        payload = self.payload_helper(payload, lat=lat)
    elif lon:
        payload = self.payload_helper(payload, lon=lon)
    elif zip_code:
        payload = self.payload_helper(payload, zip_code=zip_code)

The goal of payload helper is to create a dict that adds a set to:

  1. if no payload is passed: a new payload dict
  2. if a payload is passed: update the payload and return the new (merged) one

I know I could solve this if I would just make weather(self, *args, **kwargs) but I don't want that. I always find it nice if the user that uses the API wrapper also has intelli sense (see what variables the method expects), which is not possible with **kwargs. I don't have to do this with the if elif else chain. This was just my approach. There is probably a mew python way of doing this.

CodePudding user response:

The only thing the helper should do is filter out None-valued arguments to weather. The "base" payload should be created in weather itself, then updated using the return value of the helper.

def weather(self, lat=None, lon=None, zip_code=None):
    def helper(**kwargs):
        return {k: v for k, v in kwargs.items() if v is not None}

    payload = {'units': 'metric'}
    payload.update(helper(lat=lat, lon=lon, zip_code=zip_code))
    
    ...

CodePudding user response:

Your main goal seems to be that you want to create a dictionary that contains all the keyword arguments that were included in the call to your original function.

A very straightforward way to achieve what you want:

def func(a="", b="", c=""):
    payload = locals()
    # do something else
    print(payload)


func(a=1, b='2', c=3.)

Result:

{'a': 1, 'b': '2', 'c': 3.0}

But note that your idea to define the keyword parameters as empty strings means they are implicitly strings. This will lead to a decent IDE giving you all warnings about the types of the arguments being passed.

A better way would be to specify their type, but set them to be None initially. Optional helps to allow the None value in spite of their type:

from typing import Optional


def func(a: Optional[int] = None, b: Optional[str] = None, c: Optional[float] = None):
    payload = {name: value for name, value in locals().items() if value is not None}
    # do something else
    print(payload)


func(b='2', c=3.)  # no warnings

Result:

{'b': '2', 'c': 3.0}

Note that the solution above does have the (perhaps unwanted) side effect of including any positional arguments as well. For example:

def func(a: int, b: Optional[int] = None):
    payload = locals()
    # do something else
    print(payload)


func(1, 2)

Result:

{'a': 1, 'b': 2}

It's relevant, because your functions appear to be methods, so they will at least have the positional parameter self, which would need to be removed:

from typing import Optional


class MyClass:
    def func(self, x: Optional[int] = None):
        payload = {k: v for k, v in locals().items() if v is not None and k != 'self'}
        # do something else
        print(payload)


mc = MyClass()
mc.func()
mc.func(1)

Result:

{}
{'x': 1}

Also note that, if you need your keyword arguments to actually be able to have the value None assigned, the above still need some extra work, and you might look into __defaults__.

On a more general note: have you looked at dataclasses at all? Because it seems that what you're ultimately after might be better achieved using those.

  • Related