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. :-)
CodePudding user response:
Turns out it is not supported: https://rt.cpan.org/Public/Bug/Display.html?id=91360
CodePudding user response:
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.
CodePudding user response:
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])
Then your code could go as
use warnings;
use strict;
use feature 'say';
use Inline::Python;
adict( {a => 1, b => 2} );
use Inline Python => <<'END_PY'
def adict(d):
for key in d:
print("key:", key, "value:", d[key])
END_PY
This prints output from the python function.
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.
† 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.