BSchlinker
BSchlinker

Reputation: 3481

Passing dicts with non-string keywords

It appears that one cannot pass dicts with non-string keywords to functions in Python. (I see this has been brought up before here)

However, before coming up with some sort of convoluted solution to my problem, I figured I would ask the experts and see if there is something I'm missing. Unfortunately, I cannot change this paradigm easily without significant re-factoring, as a previous author never anticipated this type of action (this is a very simple example).

I have objects called services that can perform actions on nodes. Some nodes require different service configurations. Instead of a service instance per node, I have one instance of each service, which I then combine with configuration parameters per node. The service and it's corresponding configuration parameters are stored in a dict.

Both the node along with its services and their corresponding configuration parameters are passed to a function (setupServices) which then processes everything. This function must be able to call functions which are part of the service object. Unfortunately, this fails with the error:

TypeError: setupServices() keywords must be strings

If you're wondering what's passing it as **nodeServices instead of just nodeServices, it is the code segment below (value would be nodeServices in this case). I'm hesitant to make this code specific for an individual case, as it acts as a backbone for many other things.

if type( value ) is list:
    result = f( *value )
elif type( value ) is dict:
    result = f( **value )
else:
    result = f( value )
results[ name ] = result
return result

Are there any other ways I can accomplish this with minimal modification?

"A node can be associated with many services"
class Node(object):
    pass

"""Setup services routine (outside of the node due to an existing design choice)
   I could rewrite, but this would go against the existing author's paradigms"""
def setupServices ( node, **nodeServices ):
    for object, objOverrides in nodeServices.items():
        "do something with the service object"

"2 simple services that can be perform actions on nodes"
class serviceA(object):
    pass

class serviceB(object):
    pass

"instantiate an object of each service"
a = serviceA()
b = serviceB()

"Create a node, assign it two services with override flags"
node = Node()
nodeServices = {}
nodeServices[a] = { 'opt1' : True }
nodeServices[b] = { 'opt2' : False }

"Pass the node, and the node's services for setup"
setupServices ( node, **nodeServices )

Upvotes: 1

Views: 208

Answers (1)

BrenBarn
BrenBarn

Reputation: 251428

The simplest way to change that is to change setupServices so it no longer accepts keyword arguments, but just a dict of options. That is, change its definition to:

def setupServices (node, nodeServices):

(removing the **). You can then call it with setupServices(node, nodeServices).

The only downside to this is that you must pass in a dict, and cannot pass literal keyword arguments. With the keyword-argument setup, you could call setupServices(node, opt1=True), but without keyword arguments, if you wanted to specify that option "inline" you would have to do setupServices(node, {'opt1': True}), passing in a literal dict. However, if you're always programmatically creating a dict of options anyway and then passing it with **, this downside won't really matter. It only matters if you actually have code that is passing literal keyword arguments like setupServices(node, foo=bar).

As the answer in the question you linked to says, there is no way to make functions accept non-string keyword arguments. So there is no way you can get foo(**nodeServices) to work when the keys of nodeServices aren't strings. (Given that, it's a little hard to understand how this scheme could have ever worked, since in your example it seems like setupServices was always expecting a dict whose keys were service objects, which means passing them as keyword arguments never could have worked.)

Upvotes: 2

Related Questions