1

As the author of Pythonizer, I'm translating some perl code to python that defines a function FETCH in 2 different packages in the same source file, like:

package Env;
sub FETCH {...}
package Env::Array;
sub FETCH {...}

and my generated code needs to insert a special library call (perllib.tie_call) that handles the 'tie' operation at runtime. Here is a sample of my generated code for the above:

builtins.__PACKAGE__ = "Env"
def FETCH(*_args):
    ...
Env.FETCH = lambda *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs)

builtins.__PACKAGE__ = "Env.Array"
def FETCH(*_args):
    ...
Env.Array.FETCH = lambda *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs)

What is happening, when I call Env.FETCH, it's invoking the second def FETCH, as that's the current one defined. I need to invoke the first FETCH for Env.FETCH and the second FETCH for Env.Array.FETCH. How can I modify my generated code to do that? In the situation that I don't need to sneak in a perllib.tie_call, my generated code is:

builtins.__PACKAGE__ = "Env"
def FETCH(*_args):
    ...
Env.FETCH = FETCH

builtins.__PACKAGE__ = "Env.Array"
def FETCH(*_args):
    ...
Env.Array.FETCH = FETCH

which works as expected. With the lambda, the evaluation of FETCH gets delayed and the last one defined is being picked up. I need it to pick up the FETCH defined just above it instead.

snoopyjc
  • 621
  • 4
  • 11
  • I don't think you can put a keyword arg before the positional args, but I like where you are going with this – snoopyjc Feb 27 '23 at 16:22
  • it does work, I just don't know if it does what you want, so I deleted it – python_user Feb 27 '23 at 16:22
  • `Env.Array_.STORE = lambda *_args, **_kwargs, STORE=STORE: perllib.tie_call(STORE, _args, _kwargs) ^^^^^ SyntaxError: invalid syntax` – snoopyjc Feb 27 '23 at 16:28
  • 1
    I meant the one I posted, `Env.FETCH = lambda FETCH=FETCH, *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs)` that is syntactically valid, I was not sure if it did what you want – python_user Feb 27 '23 at 16:30
  • It doesn't work - not sure why: `Env.FETCH = lambda FETCH=FETCH, *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs) File "C:\pythonizer\pythonizer\perllib\__init__.py", line 5050, in tie_call return func(*_args, **_kwargs) TypeError: 'Env' object is not callable`. Here `func` is the first argument of tie_call. – snoopyjc Feb 27 '23 at 16:51
  • 1
    yeah, this is why I thought it would not work, while it does fix your issue with the delayed binding, it is causing some issues with the callers of the lambda, in the `*args` they send, `args[0]` is now assigned to `FETCH` is my guess, sorry I could not be of more help – python_user Feb 27 '23 at 16:58
  • 1
    That's why tried putting the `FETCH=FETCH` (or `STORE=STORE`) at the end of the argument list, but it gave me a syntax error – snoopyjc Feb 27 '23 at 17:02
  • 1
    last guess, can you try `Env.FETCH = partial(lambda FETCH, *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs), FETCH=FETCH)` where `partial` is from `functools`? or `Env.FETCH = partial(perllib.tie_call, FETCH=FETCH)` – python_user Feb 27 '23 at 17:08
  • That doesn't work either - not sure why - it seems to still be eating the arg passed to FETCH which becomes `self`. One thing I came up with is `FETCH0 = FETCH` `Env.FETCH = lambda *_args, **_kwargs: perllib.tie_call(FETCH0, _args, _kwargs)`. Another idea is to generate the code I normally generate for non-tied functions, e.g. `Env.FETCH = FETCH`, then add this code afterwards: `Env.FETCH = lambda *_args, **_kwargs: perllib.tie_call(Env.FETCH, _args, _kwargs)`, but I'm afraid that could do several layers of `tie_call`s. – snoopyjc Feb 28 '23 at 04:15
  • 1
    I am very much interested in knowing an answer to this, if no one answers this, I am willing to set a bounty – python_user Feb 28 '23 at 17:47
  • while I am here, would anything from https://github.com/chrisgrimm/better_partial work for your use case, assuming you are open to a third party library – python_user Feb 28 '23 at 17:49
  • 1
    Ok - here is another idea - I am the author of perllib, which is the run-time library for Pythonizer. How about if I write a `perllib.add_tie_call(func)`, then use it like this: `Env.FETCH = perllib.add_tie_call(FETCH)`. Inside of `add_tie_call` I can create a nested function with `func` bound, then return it. Thoughts? – snoopyjc Feb 28 '23 at 23:24

1 Answers1

1

Here is how I figured out how to fix it. Thanks @python_user for helping me brainstorm a solution!! Since I'm also the author of perllib, the library for Pythonizer, I added a new function to it add_tie_call, defined as follows:

def add_tie_call(func):
    """Add a call to _tie_call for functions defined in a tie package"""
    def tie_call_func(*args, **kwargs):
        return _tie_call(func, args, kwargs)
    return tie_call_func

Then I changed lambda lines to read:

Env.FETCH = perllib.add_tie_call(FETCH)
...
Env.Array.FETCH = perllib.add_tie_call(FETCH)

and now my test case passes:

$ ./runit -P issue_s304
==================== issue_s304.pl ====================
issue_s304.pl - test passed!
issue_s304.py - test passed!
pythonizer 1 tests PASSED!
snoopyjc
  • 621
  • 4
  • 11