0

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

Community
  • 1
  • 1
andrea
  • 525
  • 1
  • 5
  • 21
  • The behavior hasn't changed. You have a bug. – user2357112 Nov 28 '16 at 17:25
  • Could you post your code? – Draconis Nov 28 '16 at 17:27
  • 1
    Can you post a [MCVE](http://stackoverflow.com/help/mcve) that demonstrates the problem? The late binding behavior of lambdas hasn't changed (nor do I expect it to any time in the future), so it's quite possible that you have a bug that is unrelated to that. Building an MCVE might make it more obvious what the actual problem is in your code. – mgilson Nov 28 '16 at 17:28
  • Please expand on *"not working"*. – jonrsharpe Nov 28 '16 at 17:37
  • my MWE is quite crappy, but the small explanation of my true code should highlight what I'm trying to do – andrea Nov 28 '16 at 17:56
  • Your error message is pretty clear - your function takes one argument (`p`) but you are giving it 2. I think you need to use full variable definition to use a variable-length argument list. – juanpa.arrivillaga Nov 28 '16 at 17:58
  • So, in your MWE: try `func = lambda p=params : test(*p)` then try `func(3,2)`. This isn't an issue with `lambda`, you need to really grok [Python's function argument system](https://docs.python.org/2/tutorial/controlflow.html#more-on-defining-functions). – juanpa.arrivillaga Nov 28 '16 at 18:05
  • Thank you. forget my MWE (I noticed the mistake after having posted it) and look at in my real code (the second part I posted). Imagine that `freeparams = ['a','b']`. If I try to determine the signature of func, with `getasrgspec(func)`, I get `['p']`, instead of `['a','b']`. Is this normal? If so, is there a way around it? I want to build a function object with signature given by the parameters contained in `self.loglike.get_args()` (a and b in this example). Please remember: I cannot hardcode it – andrea Nov 28 '16 at 18:10
  • Is there a reason why you an't just use the most general signature: `def func(*args,**kwargs): ...`? That is normally how you would handle this situation. If you need to dynamically create an exact function signature programmatically, then you likely will have to do something super hackey. Regardless, your question doesn't really have to do with variable scope (or it's late-binding behavior) in lambdas, rather, you want to something like [this](http://stackoverflow.com/questions/26987418/programmatically-create-function-specification) – juanpa.arrivillaga Nov 28 '16 at 18:16
  • And yes, of course that is normal. Look how you defined the function: `func = lambda p=params : ...` you used `p` as a parameter, not `lambda a,b`. You shouldn't be using `lambda` if your function is going to have a name, btw. If you are naming your `lambda` (AKA an anonymous funciton) then you should just use a full function definition. There is nothing you can do with lambdas that you can't do with normal definitions (other than making them anonymous), and there is a lot you can't do with lambdas that you can do with normal function definitions. – juanpa.arrivillaga Nov 28 '16 at 18:21
  • thank you for the detailed answers @juanpa.arrivillaga. The naming of the lambda function was meant only to save space. Then, I cannot 'def func(*args,**kwargs)` because this syntax allows for generic signature, whereas I need a function with a specific signature. From my example above, I was hoping to do something like `func = lambda self.loglike.get_args() : self.loglike(self.loglike.get_args())` but clearly python doesn't like it. I'm afraid there's no solution and I will have to find a workaround. Thank you all (PS: how should I label this?) – andrea Nov 29 '16 at 08:55

1 Answers1

0

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.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • Thank you for you answer @Blckknght. Last week I came across the `decorator.FunctionMaker` class. Its documentation says that the `create()` method allows to create a function with desired signature. however, the documentation isn't as detailed as Python's standard documentations, so I was able to create a function of the desired signature, but it returned a `None`. Any thoughts on this? – andrea Nov 29 '16 at 09:00