Calling method with complex arguments Whats the best practice for calling a function with a complex set of arguments?

I've seen a 1 and 2 in most codebases, but I haven't seen 3. 
Since it's almost impossible to search for **{ in codebases, does anyone know why this pattern isn't used more often? 

I'm using Google's Sheet's API v4 as a demonstration, which has a complex API.



1. Calling with arguments (Most commonly observed)


Requires building the inner structure first


body = {
    "body": {
        "requests": [
            {
                "addSheet": {
                    "properties": {
                        "title": "some title"
                    }
                }
            }
        ]
    }
}
request = service.spreadsheets().batchUpdate(
    spreadsheetId=spreadsheetId, body=body)


2. Define a dict first (Similar vertical parsing to 1)


Cleaner to see on invocation
May allow for manipulation of values


params = {
    "spreadsheetId": spreadsheetId
    "body": {
        "requests": [
            {
                "addSheet": {
                    "properties": {
                        "title": "some title"
                    }
                }
            }
        ]
    }
}
request = service.spreadsheets().batchUpdate(**params)


3. Inline a dict (Not seen in practice, why?)


Clear intent (single-source, fixed properties)
Single pass vertical parsing


request = service.spreadsheets().batchUpdate(**{
    "spreadsheetId": spreadsheetId
    "body": {
        "requests": [
            {
                "addSheet": {
                    "properties": {
                        "title": "some title"
                    }
                }
            }
        ]
    }
})
As general practice, I prefer using positional arguments simply because they're easier to typecheck with mypy.  In the case of using some external API that doesn't have python3 type annotations (booo) I'd create a stub file that includes all of the types that I'm using and then code against that.

For your example, a quick search didn't turn up any existing mypy stubs, so I assume I'm stuck writing my own.  Here's how I'm gonna do it for the code sample you've provided:

from typing import List, TypedDict

class _Properties(TypedDict, total=False):
    title: str

class _AddSheet(TypedDict, total=False):
    properties: _Properties

class _Request(TypedDict, total=False):
    addSheet: _AddSheet

class _Body(TypedDict, total=False):
    requests: List[_Request]

def batchUpdate(spreadsheetId: str, body: _Body) -&gt; None: ...


This is a very incomplete type declaration for that potentially massive _Body object (also, note that I'm assuming that none of these fields are required; omit the total=False if you want to enforce totality), but since I'm probably not going to use all of those fields, my strategy is going to be to add them to my declaration as I use them, and have that be the source of truth (i.e. as long as I declare it correctly once in that file, mypy will make sure I use it correctly everywhere else).

Now I can call that function in my code like this:

batchUpdate(
    spreadsheetId, { 
        'requests': [{ 
            'addSheet': { 
                'properties': { 
                    'title': "some title" 
                } 
            } 
        }]
    }
)


(BTW, if you want to completely ignore my advice about typing, this is the calling syntax I'd use regardless.)

The benefit of having added typing is that if I typo something, e.g. I misspell "properties", I'll get an error from mypy like this:

spreadsheet.py:22: error: Extra key 'prperties' for TypedDict "_AddSheet"


If I forget that requests is a list and I omit the brackets, I get:

spreadsheet.py:21: error: Incompatible types (expression has type "Dict[str, Dict[str, Dict[str, str]]]", TypedDict item "requests" has type "List[_Request]")


Getting a static error like this is a lot easier than having a typo in my server API call that results in a (possibly cryptic) runtime error.

My experience is that using the **kwargs syntax generally makes it harder to enforce good typing, so I tend to avoid it.