Dynamic Keyword Arguments in Python? - python

Does python have the ability to create dynamic keywords?
For example:
qset.filter(min_price__usd__range=(min_price, max_price))
I want to be able to change the usd part based on a selected currency.

Yes, It does. Use **kwargs in a function definition.
Example:
def f(**kwargs):
print kwargs.keys()
f(a=2, b="b") # -> ['a', 'b']
f(**{'d'+'e': 1}) # -> ['de']
But why do you need that?

If I understand what you're asking correctly,
qset.filter(**{
'min_price_' + selected_currency + '_range' :
(min_price, max_price)})
does what you need.

You can easily do this by declaring your function like this:
def filter(**kwargs):
your function will now be passed a dictionary called kwargs that contains the keywords and values passed to your function. Note that, syntactically, the word kwargs is meaningless; the ** is what causes the dynamic keyword behavior.
You can also do the reverse. If you are calling a function, and you have a dictionary that corresponds to the arguments, you can do
someFunction(**theDictionary)
There is also the lesser used *foo variant, which causes you to receive an array of arguments. This is similar to normal C vararg arrays.

Yes, sort of.
In your filter method you can declare a wildcard variable that collects all the unknown keyword arguments. Your method might look like this:
def filter(self, **kwargs):
for key,value in kwargs:
if key.startswith('min_price__') and key.endswith('__range'):
currency = key.replace('min_price__', '').replace('__range','')
rate = self.current_conversion_rates[currency]
self.setCurrencyRange(value[0]*rate, value[1]*rate)

Related

Dynamically add keywords and their values to a python function from supplied strings

I want to create a method, which can dynamically add keyword arguments to another function that I intend to call within this method, something like:
def func(query1: str, query2: str):
func2(query1, query2)
where, the query1, query2 in the func are strings of the type "a=3", "b=4", the func should essentially take these strings and then call them in a keyword argument way like:
func2(a=3, b=4)
The a, b here are not fixed, they may vary, I mean to say I want to be able to take the a and b from the LHS of the string "a=3"
So my function takes the string "a=3" and parses the "a" as the keyword for the other function and the 3 as the value and calls the other function like func2(a=3)
I want to know if this kind of implementation is even possible or if there are any workarounds for achieving the same goal.
The reason why I want this is because I have a django application in which I want to create a model where I can store some filter rules and apply them each time that view is called, so I want to allow the user to select a bunch of filters and then I want to store them in the database and call those rules to get a queryset specific to that user's preferences.
def some_view(request):
# Get the filter rule strings from the model
queryset = some_model.objects.filter(<Pass all the rules here from those strings>)
return Response
edit: Add more details on the use case
Not sure I really understand what you want. You could translate those query strings to a dict, then use that to call the other function with **kwargs:
def f(a, b):
print(a, b)
def g(*queries):
return f(**dict((q.split("=", 1) for q in queries)))
>>> g("a=4", "b='string with = in the middle'")
4 'string with = in the middle'
Note: just split("=") might fail if the parameter is a string containing =; thus you might want to use split("=", 1) instead.
However, this passes all the arguments as raw strings; you might want to eval, or rather ast.literal_eval to the values, but that may also pose some risks. Something like this:
import ast
def g(*queries):
return f(**{k: ast.literal_eval(v)
for k, v in (q.split("=", 1) for q in queries)})

Using a dynamic variable name, as an argument for a function/method

Using a dynamic variable name, as an argument for a function/method:
I need something like:
PObjectName.update(f'add_{attr_name}_tek'=data)
When you want to dynamically build a set of named arguments for a function call, you can use a dictionary and pass it using the ** operator to expand it in the function call.
concretely, you can do this way:
kwargs = {f'add_{attr_name}_tek': data}
PObjectName.update(**kwargs)
It is possible with unpacking a dict of keyword arguments:
def foo(add_attr_name_tek):
print(add_attr_name_tek)
attr_name = 'attr_name'
foo(**{f'add_{attr_name}_tek': 'value'})

Get a dictionary of current function parameters

I recently had to use a function conditionally dispatching tasks to other functions, with a lot of mandatory and optional named arguments (e.g. manipulating connection strings, spark connectors configs and so on), and it occurred to me that It would have been really much "cleaner" (or "pythonesque") to have a syntax allowing me to pass every arguments from a function to another similar to this :
def sisterFunction(**kwargs) : # Doing things with a bunch of mandatory and optional args
<do various things/>
def motherFunction(a,b,**kwargs) :
<do various things/>
sisterFunction(**allArgs)
where allArgs would be a dictionary containing keys a,b, and everything in kwargs. This sounds like something python would be inclined to allow and ease but I can't seem to find something similar to a "super kwargs" implemented. Is there a straightforward way to do this ? Is there an obvious good reason it's not a thing ?
def sisterFunction(**kwargs):
pass
def motherFunction(a, b, **kwargs):
sisterFunction(a=a, b=b, **kwargs)
kwargs in sisterFunction will contain a and b keys with corresponding values.
UPDATE
If you don't want to pass long list of function parameters via a=a, there is some workaround to get allArgs:
def motherFunction(a, b, **kwargs):
allArgs = locals().copy()
allArgs.update(allArgs.pop('kwargs', {}))
sisterFunction(**allArgs)
I would probably go with just using kwargs
def sisterFunction(**kwargs):
pass
def motherFunction(**kwargs):
# use the values directly from 'kwargs'
print(kwargs['a'])
# or assign them to local variables for this function
b = kwargs['b']
sisterFunction(**kwargs)
This will probably be the option with the least code in your function signatures (the definitions of all the parameters to the function).
A KeyError will be raised if some parameters were not passed to the function and the function tries to use them.

forcing value of lambda inner scope variable to outer variable - python

I have already found various answers to this question (eg. lambda function acessing outside variable) and all point to the same hack, namely (eg.) lambda n=i : n*2 with i a variable in the external scope of lambda (hoping I'm not misusing the term scope). However, this is not working and given that all answers I found are generally from couple of years ago, I thought that maybe this has been deprecated and only worked with older versions of python. Does anybody have an idea or suggestion on how to solve this?
SORRY, forgot the MWE
from inspect import getargspec
params = ['a','b']
def test(*args):
return args[0]*args[1]
func = lambda p=params : test(p)
I expected the signature of func to be ['a','b'] but if I try
func(3,2)
I get a Type error (TypeError: <lambda>() takes at most 1 argument (2 given) )
and it's true signature (from getargspec(func)[0] ) is ['p']
In my real code the thing is more complicated. Shortly:
def fit(self, **kwargs):
settings = self.synch()
freepars = self.loglike.get_args()
func = lambda p=freeparams : self.loglike(p)
minuit = Minuit(func,**settings)
I need lambda because it's the only way I could think to create inplace a function object depending on a non-hardcoded list of variables (extracted via a method get_params() of the instance self.loglike). So func has to have the correct signature, to match the info inside the dict settings
The inspector gives ['p'] as argument of func, not the list of parameters which should go in loglike. Hope you can easily spot my mistake. Thank you
There's no way to do exactly what you want. The syntax you're trying to use to set the signature of the function you're creating doesn't do what you want. It instead sets a default value for the argument you've defined. Python's function syntax allows you to define a function that accepts an arbitrary number of arguments, but it doesn't let you define a function with argument names in a variable.
What you can do is accept *args (or **kwargs) and then do some processing on the value to match it up with a list of argument names. Here's an example where I turn positional arguments in a specific order into keyword arguments to be passed on to another function:
arg_names = ['a', 'b']
def foo(*args):
if len(args) != len(arg_names):
raise ValueError("wrong number of arguments passed to foo")
args_by_name = dict(zip(arg_names, args))
some_other_function(**args_by_name)
This example isn't terribly useful, but you could do more sophisticated processing on the args_by_name dict (e.g. combining it with another dict), which might be relevant to your actual use case.

Python building multiple arg parameters

I want to convert certain values of a dictionary as multiple args and pass it to a method. The key of the dict needs to be the name of the variable passed to the method.
eg:
myDict={'one':1,'two':2,'three':3,'four':4}
#call myMethod as
myMethod(one=1,two=2)
def myMeth(self,*args):
do somthing with args
You can use the unpacking notation. If you have a method like this
def function(arg1,arg2,arg3):
# do something
and a dictionary dct = {'arg1':3,'arg2':3,'arg3':6}, you can call the function like
function(**dct)
which is equivalent to doing function(arg1=3,arg2=3,arg3=6).
Notice the double stars. This means to take the dictionary and pass its values as named parameters where the keys are the names. A single star would unpack a list, passing its values in order as unnamed parameters. These can be combined.
See section 4.7.4 of the python documentation for more detail. Here is another article discussing these as well.
Use ** for passing or accepting dictionary arguments. The following code
d={'foo': 'bar','test': 123}
def my_method(**kwargs):
print kwargs
my_method(**d)
would print the contents of d, {'foo': 'bar','test': 123}
EDIT: #Matthew 's answer sums it up nicely, via the comparison of *args.

Categories

Resources