How do I call a named parameter Python function from Perl?

Viewed 174

I am using Inline::Python 0.56, trying to call a Python function with named parameters from Perl. I have replaced the original code fragments with an example that can be run, as requested:

use strict;
use warnings;
use 5.16.3;

use Inline Python => <<'END_OF_PYTHON_CODE';
# Interface
def get_sbom_of_package (
    package_name = None,
    target_path  = None,
):
    assert package_name is not None
    assert target_path is not None

    print ( package_name, target_path )
    return package_name

END_OF_PYTHON_CODE

my $package = 'thePackage';
my $target_path  = $ENV{HOME};

my $sbp = get_sbom_of_package(
    package_name => $package ,
    target_path  => $target_path
);
say $sbp;

and my error is:

TypeError: get_sbom_of_package() takes from 0 to 2 positional arguments but 8 were given at (eval 3) line 3.

Is there something else I need to do the inline Python to understand I am feeding it named parameters? I have replaced the call in Perl with

my $sbp = get_sbom_of_package(
    $package ,
    $target_path
);

and it works just fine. I am thinking it's either

  • bad Python on my part or
  • an Inline::Python configuration issue
  • an Inline::Python limitation

sorted from most likely to least. :-)

3 Answers

If I am reading the XS correctly and remembering my Python extension module work, this is not directly possible.

Inline::Python appears to invoke py callables using PyObject_CallObject which passes a tuple of arguments — without any named/keyword args.

So, you'd have to do it yourself, perhaps by putting a wrapper around the py callable, which wrapper understands what you mean by parameter => value, ... and constructs the right tuple to pass along. You'd need to redundantly know default values, too, of course. You might rev Inline::Python to support interrogating the callable's signature to look for keyword args ... but then you'd still have to adapt those semantics that to ordinary Perl subroutine calls yourself.

Note   This is meant to sketch the idea that "named arguments" be implemented as a hash(ref) in Perl --> dictionary in Python, and then do whatever need be done on the Python side, with named arguments in hand. By all means add other needed arguments to the list (or to the dictionary), like function names, arrayrefs, or whatnot.

The ultimate purpose isn't explained but if that is clarified I can add more specific code. (A guess at how this might be used, plus another fairly generic way, are given in a footnote.)


The attempted code passes four (4) arguments to Perl's function call, while Python's function is written to take two (each with a default value).

If you want to pass named parameters to a Python function, one way would be to pass a dictionary. Then this incidentally mimics your Perl use, as well

def adict(d):
    for key in d:
        print("key:", key, "value:", d[key])

This way one maps named arguments from the caller in Perl (hash) to Python (dictionary), leaving it to normal python->python calls to be built in this wrapper._

Then your code could go as

use warnings;
use strict;
use feature 'say';

use Inline::Python;

my $package = 'thePackage';
my $target_path  = $ENV{HOME};

call_py( {  # note -- pass a hashref
    package_name => $package, target_path  => $target_path
} );

use Inline Python => <<'END_PY'
def call_py(d):
    for key in d:
        print(key + " => " + d[key])
    # ...
    # prepare and make other (python-to-python) calls as needed
END_PY

If specific further calls from the Python function need be made (by named parameters?) please show how they'd look and I can add to this bare-bones example.

See below on my guesses of what could be intended with this question. Please clarify?

This prints (with my path redacted)

target_path => /...
package_name => thePackage

Or one could devise a system for passing Perl's four arguments and taking them in Python and interpreting them as name => value pairs, but why.

Once arguments in the Python function are unpacked from the dictionary (and whatever else may have been added to the argument list along with the dictionary for named arguments sent from Perl via a hash(ref)), one can make further python -> python calls as needed.


A function in Perl takes a list of scalars. Period. So this

func(package_name => $package, target_path  => $target_path)

or even this

my %ph = (package_name => $package, target_path  => $target_path);

func( %ph );

is really this

func('package_name', $package, 'target_path', $target_path)

That "fat comma" (=>) is a comma, whereby its left argument also gets quoted.


Imagine that the purpose of this is to be able to call from Perl a great py_lib in Python, somehow via "named parameters" (please clarify). Here are a couple of guesses

use warnings;
use strict;
use feature 'say';

use Inline::Python;

my $package = 'thePackage';
my $target_path  = $ENV{HOME};

call_py({ package_name => $package, target_path => $target_path });

use Inline Python => <<'END_PY'
def call_py(d):
    print(d)
    # prepare and make other (python-to-python) calls as needed

    print("\nArgs with these names passed from Perl:")
    py_lib_kw( pack=d['package_name'], path=d['target_path'] )

    print("\nVariable number of named args passed from Perl:")
    py_lib_kwargs( **d )

def py_lib_kw(path=None, pack=None):
   print ("package is " + pack + " and path is " + path)

def py_lib_kwargs(**data):
    for val in data:
        print(val + " => " + data[val])

END_PY
Related