1/* Part of SWI-Prolog 2 3 Author: Jan Wielemaker 4 E-mail: jan@swi-prolog.org 5 WWW: http://www.swi-prolog.org 6 Copyright (c) 2023, SWI-Prolog Solutions b.v. 7 All rights reserved. 8 9 Redistribution and use in source and binary forms, with or without 10 modification, are permitted provided that the following conditions 11 are met: 12 13 1. Redistributions of source code must retain the above copyright 14 notice, this list of conditions and the following disclaimer. 15 16 2. Redistributions in binary form must reproduce the above copyright 17 notice, this list of conditions and the following disclaimer in 18 the documentation and/or other materials provided with the 19 distribution. 20 21 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 24 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 25 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 26 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 27 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 30 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 31 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 32 POSSIBILITY OF SUCH DAMAGE. 33*/ 34 35:- module(janus, 36 [ py_version/0, 37 38 py_call/1, % +Call 39 py_call/2, % +Call, -Return 40 py_call/3, % +Call, -Return, +Options 41 py_iter/2, % +Call, -Return 42 py_iter/3, % +Call, -Return, +Options 43 py_setattr/3, % +On, +Name, +Value 44 py_free/1, % +Obj 45 py_is_object/1, % @Term 46 py_is_dict/1, % @Term 47 py_with_gil/1, % :Goal 48 py_gil_owner/1, % -ThreadID 49 50 py_func/3, % +Module, +Func, -Return 51 py_func/4, % +Module, +Func, -Return, +Options 52 py_dot/4, % +Module, +ObjRef, +Meth, ?Ret 53 py_dot/5, % +Module, +ObjRef, +Meth, -Ret, +Options 54 55 values/3, % +Dict, +Path, ?Val 56 keys/2, % +Dict, ?Keys 57 key/2, % +Dict, ?Key 58 items/2, % +Dict, ?Items 59 60 py_shell/0, 61 62 py_pp/1, % +Term 63 py_pp/2, % +Stream, +Term 64 py_pp/3, % +Stream, +Term, +Options 65 66 py_object_dir/2, % +ObjRef, -List 67 py_object_dict/2, % +ObjRef, -Dict 68 py_obj_dir/2, % +ObjRef, -List (deprecated) 69 py_obj_dict/2, % +ObjRef, -Dict (deprecated) 70 py_type/2, % +ObjRef, -Type:atom 71 py_isinstance/2, % +ObjRef, +Type 72 py_module_exists/1, % +Module 73 py_hasattr/2, % +Module, ?Symbol 74 75 py_module/2, % +Module:atom, +Source:string 76 77 py_initialize/3, % +Program, +Argv, +Options 78 py_lib_dirs/1, % -Dirs 79 py_add_lib_dir/1, % +Dir 80 py_add_lib_dir/2, % +Dir,+Where 81 82 op(200, fy, @), % @constant 83 op(50, fx, #) % #Value 84 ]). 85:- meta_predicate py_with_gil( ). 86 87:- use_module(library(apply_macros), []). 88:- autoload(library(lists), [append/3, member/2]). 89:- autoload(library(apply), [maplist/2, exclude/3, maplist/3]). 90:- autoload(library(error), [must_be/2, domain_error/2]). 91:- autoload(library(dicts), [dict_keys/2]). 92:- autoload(library(option), [dict_options/2]). 93:- autoload(library(prolog_code), [comma_list/2]). 94:- autoload(library(readutil), [read_line_to_string/2]). 95:- autoload(library(wfs), [call_delays/2, delays_residual_program/2]). 96:- autoload(library(dcg/high_order), [sequence//2, sequence//3]). 97 98:- if(\+current_predicate(py_call/1)). 99:- if(current_prolog_flag(windows, true)). 100:- use_module(library(shlib), [win_add_dll_directory/1]). 101 102% Just having the Python dir in PATH seems insufficient. We also need to 103% add the directory to the DLL search path. 104add_python_dll_dir :- 105 absolute_file_name(path('python3.dll'), DLL, [access(read)]), 106 file_directory_name(DLL, Dir), 107 win_add_dll_directory(Dir). 108:- initialization(add_python_dll_dir, now). 109:- endif. 110 111:- use_foreign_library(foreign(janus), [visibility(global)]). 112:- endif. 113 114:- predicate_options(py_call/3, 3, 115 [ py_object(boolean), 116 py_string_as(oneof([string,atom])) 117 ]). 118:- predicate_options(py_func/4, 4, 119 [ pass_to(py_call/3, 3) 120 ]). 121:- predicate_options(py_dot/5, 5, 122 [ pass_to(py_call/3, 3) 123 ]). 124 125:- public 126 py_initialize/0, 127 py_call_string/3, 128 py_write/2, 129 py_readline/4. 130 131:- create_prolog_flag(py_backtrace, true, [type(boolean), keep(true)]). 132:- create_prolog_flag(py_backtrace_depth, 4, [type(integer), keep(true)]). 133:- create_prolog_flag(py_argv, [], [type(term), keep(true)]).
sys.version
. If a Python virtual environment (venv) is
active, indicate this with the location of this environment found.
155py_version :-
156 py_call(sys:version, PythonVersion),
157 py_call(janus_swi:version_str(), JanusVersion),
158 print_message(information, janus(version(JanusVersion, PythonVersion))),
159 ( py_venv(VEnvDir, EnvSiteDir)
160 -> print_message(information, janus(venv(VEnvDir, EnvSiteDir)))
161 ; true
162 ).
Arguments to Python functions use the Python conventions. Both
positional and keyword arguments are supported. Keyword
arguments are written as Name = Value
and must appear after the
positional arguments.
Below are some examples.
% call a built-in ?- py_call(print("Hello World!\n")). true. % call a built-in (alternative) ?- py_call(builtins:print("Hello World!\n")). true. % call function in a module ?- py_call(sys:getsizeof([1,2,3]), Size). Size = 80. % call function on an attribute of a module ?- py_call(sys:path:append("/home/bob/janus")). true % get attribute from a module ?- py_call(sys:path, Path) Path = ["dir1", "dir2", ...]
Given a class in a file dog.py
such as the following example from
the Python documentation
class Dog: tricks = [] def __init__(self, name): self.name = name def add_trick(self, trick): self.tricks.append(trick)
We can interact with this class as below. Note that $Doc
in the
SWI-Prolog toplevel refers to the last toplevel binding for the
variable Dog.
?- py_call(dog:'Dog'("Fido"), Dog). Dog = <py_Dog>(0x7f095c9d02e0). ?- py_call($Dog:add_trick("roll_over")). Dog = <py_Dog>(0x7f095c9d02e0). ?- py_call($Dog:tricks, Tricks). Dog = <py_Dog>(0x7f095c9d02e0), Tricks = ["roll_over"]
If the principal term of the first argument is not Target:Func
,
The argument is evaluated as the initial target, i.e., it must be an
object reference or a module. For example:
?- py_call(dog:'Dog'("Fido"), Dog), py_call(Dog, X). Dog = X, X = <py_Dog>(0x7fa8cbd12050). ?- py_call(sys, S). S = <py_module>(0x7fa8cd582390).
Options processed:
true
(default false
), translate the return as a Python
object reference. Some objects are always translated to
Prolog, regardless of this flag. These are the Python constants
None
, True
and False
as well as instances of the
Python base classes int
, float
, str
or tuple
. Instances
of sub classes of these base classes are controlled by this
option.atom
(default), translate a Python String into a
Prolog atom. If Type is string
, translate into a Prolog string.
Strings are more efficient if they are short lived.dict
(default) to map a Python dict to a SWI-Prolog
dict if all keys can be represented. If {}
or not all keys
can be represented, Return is unified to a term {k:v, ...}
or py({})
if the Python dict is empty.Obj:Attr = Value
construct is not accepted.__iter__
on the result to get the iterator itself.__next__
function of the iterator.
The example below uses the built-in iterator range()
:
?- py_iter(range(1,3), X). X = 1 ; X = 2.
Note that the implementation performs a look ahead, i.e., after successful unification it calls `__next__()` again. On failure the Prolog predicate succeeds deterministically. On success, the next candidate is stored.
Note that a Python generator is a Python iterator. Therefore,
given the Python generator expression below, we can use
py_iter(squares(1,5),X)
to generate the squares on backtracking.
def squares(start, stop): for i in range(start, stop): yield i * i
py_setattr(Target, Name, Value) :- py_call(Target, Obj, [py_object(true)]), py_call(setattr(Obj, Name, Value)).
eval
, file
(default) or single
.359py_is_dict(Dict), is_dict(Dict) => true. 360py_is_dict(py({})) => true. 361py_is_dict(py({KV})) => is_kv(KV). 362py_is_dict({KV}) => is_kv(KV). 363 364is_kv((K:V,T)) => ground(K), ground(V), is_kv(T). 365is_kv(K:V) => ground(K), ground(V).
existence_error
. Note that by decrementing the
reference count, we make the reference invalid from Prolog. This may
not actually delete the object because the object may have
references inside Python.
Prolog references to Python objects are subject to atom garbage collection and thus normally do not need to be freed explicitly.
once(Goal)
while holding the Phyton GIL (Global
Interpreter Lock). Note that all predicates that interact with
Python lock the GIL. This predicate is only required if we wish to
make multiple calls to Python while keeping the GIL. The GIL is a
recursive lock and thus calling py_call/1,2 while holding the GIL
does not deadlock.Note that this predicate returns the Prolog threads that locked the GIL. It is however possible that Python releases the GIL, for example if it performs a blocking call. In this scenario, some other thread or no thread may hold the gil.
409 /******************************* 410 * COMPATIBILIY * 411 *******************************/
py_call(Module:Function, Return)
. See py_call/2 for
details.
425py_func(Module, Function, Return) :- 426 py_call(Module:Function, Return). 427py_func(Module, Function, Return, Options) :- 428 py_call(Module:Function, Return, Options).
py_call(ObjRef:MethAttr,
Return)
. See py_call/2 for details.
440py_dot(_Module, ObjRef, MethAttr, Ret) :- 441 py_call(ObjRef:MethAttr, Ret). 442py_dot(_Module, ObjRef, MethAttr, Ret, Options) :- 443 py_call(ObjRef:MethAttr, Ret, Options). 444 445 446 /******************************* 447 * PORTABLE ACCESS TO DICTS * 448 *******************************/
458values(Dict, Key, Val), is_dict(Dict), atom(Key) => 459 get_dict(Key, Dict, Val). 460values(Dict, Keys, Val), is_dict(Dict), is_list(Keys) => 461 get_dict_path(Keys, Dict, Val). 462values(py({CommaDict}), Key, Val) => 463 comma_values(CommaDict, Key, Val). 464values({CommaDict}, Key, Val) => 465 comma_values(CommaDict, Key, Val). 466 467get_dict_path([], Val, Val). 468get_dict_path([H|T], Dict, Val) :- 469 get_dict(H, Dict, Val0), 470 get_dict_path(T, Val0, Val). 471 472comma_values(CommaDict, Key, Val), atom(Key) => 473 comma_value(Key, CommaDict, Val). 474comma_values(CommaDict, Keys, Val), is_list(Keys) => 475 comma_value_path(Keys, CommaDict, Val). 476 477comma_value(Key, Key:Val0, Val) => 478 Val = Val0. 479comma_value(Key, (_,Tail), Val) => 480 comma_value(Key, Tail, Val). 481 482comma_value_path([], Val, Val). 483comma_value_path([H|T], Dict, Val) :- 484 comma_value(H, Dict, Val0), 485 comma_value_path(T, Val0, Val).
494keys(Dict, Keys), is_dict(Dict) => 495 dict_keys(Dict, Keys). 496keys(py({CommaDict}), Keys) => 497 comma_dict_keys(CommaDict, Keys). 498keys({CommaDict}, Keys) => 499 comma_dict_keys(CommaDict, Keys). 500 501comma_dict_keys((Key:_,T), Keys) => 502 Keys = [Key|KT], 503 comma_dict_keys(T, KT). 504comma_dict_keys(Key:_, Keys) => 505 Keys = [Key].
515key(Dict, Key), is_dict(Dict) => 516 dict_pairs(Dict, _Tag, Pairs), 517 member(Key-_, Pairs). 518key(py({CommaDict}), Keys) => 519 comma_dict_key(CommaDict, Keys). 520key({CommaDict}, Keys) => 521 comma_dict_key(CommaDict, Keys). 522 523comma_dict_key((Key:_,_), Key). 524comma_dict_key((_,T), Key) :- 525 comma_dict_key(T, Key).
534items(Dict, Items), is_dict(Dict) => 535 dict_pairs(Dict, _, Pairs), 536 maplist(pair_item, Pairs, Items). 537items(py({CommaDict}), Keys) => 538 comma_dict_items(CommaDict, Keys). 539items({CommaDict}, Keys) => 540 comma_dict_items(CommaDict, Keys). 541 542pair_item(K-V, K:V). 543 544comma_dict_items((Key:Value,T), Keys) => 545 Keys = [Key:Value|KT], 546 comma_dict_items(T, KT). 547comma_dict_items(Key:Value, Keys) => 548 Keys = [Key:Value]. 549 550 551 /******************************* 552 * SHELL * 553 *******************************/
janus
as below.
from janus import *
So, we can do
?- py_shell. ... >>> query_once("writeln(X)", {"X":"Hello world"}) Hello world {'truth': True}
If possible, we enable command line editing using the GNU readline library.
When used in an environment where Prolog does not use the file
handles 0,1,2 for the standard streams, e.g., in swipl-win
,
Python's I/O is rebound to use Prolog's I/O. This includes Prolog's
command line editor, resulting in a mixed history of Prolog and
Pythin commands.
579py_shell :- 580 import_janus, 581 py_call(janus_swi:interact(), _). 582 583import_janus :- 584 py_call(sys:hexversion, V), 585 V >= 0x030A0000, % >= 3.10 586 !, 587 py_run("from janus_swi import *", py{}, py{}, _, []). 588import_janus :- 589 print_message(warning, janus(py_shell(no_janus))). 590 591 592 /******************************* 593 * UTILITIES * 594 *******************************/
pformat()
from the Python module
pprint
to do the actual formatting. Options is translated into
keyword arguments passed to pprint.pformat()
. In addition, the
option nl(Bool)
is processed. When true
(default), we use
pprint.pp()
, which makes the output followed by a newline. For
example:
?- py_pp(py{a:1, l:[1,2,3], size:1000000}, [underscore_numbers(true)]). {'a': 1, 'l': [1, 2, 3], 'size': 1_000_000}
616py_pp(Term) :- 617 py_pp(current_output, Term, []). 618 619py_pp(Term, Options) :- 620 py_pp(current_output, Term, Options). 621 622py_pp(Stream, Term, Options) :- 623 select_option(nl(NL), Options, Options1, true), 624 ( NL == true 625 -> Method = pp 626 ; Method = pformat 627 ), 628 opts_kws(Options1, Kws), 629 PFormat =.. [Method, Term|Kws], 630 py_call(pprint:PFormat, String), 631 write(Stream, String). 632 633opts_kws(Options, Kws) :- 634 dict_options(Dict, Options), 635 dict_pairs(Dict, _, Pairs), 636 maplist(pair_kws, Pairs, Kws). 637 638pair_kws(Name-Value, Name=Value).
650py_object_dir(ObjRef, List) :- 651 py_call(ObjRef:'__dir__'(), List). 652 653py_object_dict(ObjRef, Dict) :- 654 py_call(ObjRef:'__dict__', Dict).
661py_obj_dir(ObjRef, List) :- 662 py_object_dir(ObjRef, List). 663 664py_obj_dict(ObjRef, Dict) :- 665 py_object_dict(ObjRef, Dict).
type(ObjRef).__name__
in Python.
675py_type(ObjRef, Type) :-
676 py_call(type(ObjRef):'__name__', Type).
isinstance(ObjRef)
in
Python.
689py_isinstance(Obj, Module:Type) => 690 py_call(isinstance(Obj, eval(Module:Type)), @true). 691py_isinstance(Obj, Type) => 692 py_call(isinstance(Obj, eval(sys:modules:'__getitem__'(builtins):Type)), @true).
701py_module_exists(Module) :- 702 must_be(atom, Module), 703 py_call(sys:modules:'__contains__'(Module), @true), 704 !. 705py_module_exists(Module) :- 706 py_call(importlib:util:find_spec(Module), R), 707 R \== @none, 708 py_free(R).
hasattr()
. If Name is unbound, this enumerates
the members of py_object_dir/2.
721py_hasattr(ModuleOrObj, Name) :- 722 var(Name), 723 !, 724 py_object_dir(ModuleOrObj, Names), 725 member(Name, Names). 726py_hasattr(ModuleOrObj, Name) :- 727 must_be(atom, Name), 728 ( atom(ModuleOrObj) 729 -> py_call(ModuleOrObj:'__name__'), % force loading 730 py_call(hasattr(eval(sys:modules:'__getitem__'(ModuleOrObj)), Name), @true) 731 ; py_call(hasattr(ModuleOrObj, Name), @true) 732 ).
string
quasi quotation that supports long
strings in SWI-Prolog. For example:
:- use_module(library(strings)). :- py_module(hello, {|string|| | def say_hello_to(s): | print(f"hello {s}") |}).
Calling this predicate multiple times with the same Module and Source is a no-op. Called with a different source creates a new Python module that replaces the old in the global namespace.
756:- dynamic py_dyn_module/2 as volatile. 757 758py_module(Module, Source) :- 759 variant_sha1(Source, Hash), 760 ( py_dyn_module(Module, Hash) 761 -> true 762 ; py_call(janus:import_module_from_string(Module, Source)), 763 ( retract(py_dyn_module(Module, _)) 764 -> py_update_module_cache(Module) 765 ; true 766 ), 767 asserta(py_dyn_module(Module, Hash)) 768 ). 769 770 771 /******************************* 772 * INIT * 773 *******************************/ 774 775:- dynamic py_venv/2 as volatile. 776:- dynamic py_is_initialized/0 as volatile. 777 778% py_initialize is det. 779% 780% Used as a callback from C for lazy initialization of Python. 781 782py_initialize :- 783 getenv('VIRTUAL_ENV', VEnv), 784 prolog_to_os_filename(VEnvDir, VEnv), 785 atom_concat(VEnvDir, '/pyvenv.cfg', Cfg), 786 access_file(Cfg, read), 787 !, 788 current_prolog_flag(executable, Program), 789 current_prolog_flag(py_argv, Argv), 790 py_initialize(Program, ['-I'|Argv], []), 791 py_call(sys:prefix = VEnv), 792 venv_update_path(VEnvDir). 793py_initialize :- 794 current_prolog_flag(executable, Program), 795 current_prolog_flag(py_argv, Argv), 796 py_initialize(Program, Argv, []). 797 798venv_update_path(VEnvDir) :- 799 py_call(sys:version_info, Info), % Tuple 800 Info =.. [_,Major,Minor|_], 801 format(string(EnvSiteDir), 802 '~w/lib/python~w.~w/site-packages', 803 [VEnvDir, Major, Minor]), 804 prolog_to_os_filename(EnvSiteDir, PyEnvSiteDir), 805 ( exists_directory(EnvSiteDir) 806 -> true 807 ; print_message(warning, 808 janus(venv(no_site_package_dir(VEnvDir, EnvSiteDir)))) 809 ), 810 py_call(sys:path, Path0), 811 exclude(is_site_dir, Path0, Path1), 812 append(Path1, [PyEnvSiteDir], Path), 813 py_call(sys:path = Path), 814 print_message(silent, janus(venv(VEnvDir, EnvSiteDir))), 815 asserta(py_venv(VEnvDir, EnvSiteDir)). 816 817is_site_dir(OsDir) :- 818 prolog_to_os_filename(PlDir, OsDir), 819 file_base_name(PlDir, Dir0), 820 downcase_atom(Dir0, Dir), 821 no_env_dir(Dir). 822 823no_env_dir('site-packages'). 824no_env_dir('dist-packages').
py_argv
and an empty
Options list.
Calling this predicate while the Python is already initialized is a no-op. This predicate is thread-safe, where the first call initializes Python.
In addition to initializing the Python system, it
janus.py
to the Python module
search path.
848py_initialize(Program, Argv, Options) :-
849 ( py_initialize_(Program, Argv, Options)
850 -> absolute_file_name(library('python/janus.py'), Janus,
851 [ access(read) ]),
852 file_directory_name(Janus, PythonDir),
853 py_add_lib_dir(PythonDir, first),
854 py_connect_io,
855 repl_add_cwd,
856 asserta(py_is_initialized)
857 ; true
858 ).
865py_connect_io :- 866 maplist(non_file_stream, 867 [0-user_input, 1-user_output, 2-user_error], 868 NonFiles), 869 Call =.. [connect_io|NonFiles], 870 py_call(janus_swi:Call). 871 872non_file_stream(Expect-Stream, Bool) :- 873 ( stream_property(Stream, file_no(Expect)) 874 -> Bool = @false 875 ; Bool = @true 876 ). 877 878 /******************************* 879 * PATHS * 880 *******************************/
889py_lib_dirs(Dirs) :-
890 py_call(sys:path, Dirs0),
891 maplist(prolog_to_os_filename, Dirs, Dirs0).
first
or last
. py_add_lib_dir/1 adds the
directory as last
. The property sys:path
is not modified if it
already contains Dir.
Dir is in Prolog notation. The added directory is converted to an absolute path using the OS notation using prolog_to_os_filename/2.
If Dir is a relative path, it is taken relative to Prolog source file when used as a directive and relative to the process working directory when called as a predicate.
913:- multifile system:term_expansion/2. 914 915systemterm_expansion((:- py_add_lib_dir(Dir0)), 916 (:- initialization(py_add_lib_dir(Dir, first), now))) :- 917 \+ is_absolute_file_name(Dir0), 918 prolog_load_context(directory, CWD), 919 absolute_file_name(Dir0, Dir, [relative_to(CWD)]). 920systemterm_expansion((:- py_add_lib_dir(Dir0, Where)), 921 (:- initialization(py_add_lib_dir(Dir, Where), now))) :- 922 \+ is_absolute_file_name(Dir0), 923 prolog_load_context(directory, CWD), 924 absolute_file_name(Dir0, Dir, [relative_to(CWD)]), 925 absolute_file_name(Dir0, Dir). 926 927py_add_lib_dir(Dir) :- 928 py_add_lib_dir(Dir, last). 929 930py_add_lib_dir(Dir, Where) :- 931 absolute_file_name(Dir, AbsDir), 932 prolog_to_os_filename(AbsDir, OSDir), 933 py_add_lib_dir_(OSDir, Where). 934 935py_add_lib_dir_(OSDir, Where) :- 936 ( py_call(sys:path, Dirs0), 937 memberchk(OSDir, Dirs0) 938 -> true 939 ; Where == last 940 -> py_call(sys:path:append(OSDir), _) 941 ; Where == first 942 -> py_call(sys:path:insert(0, OSDir), _) 943 ; must_be(oneof([first,last]), Where) 944 ). 945 946repl_add_cwd :- 947 current_prolog_flag(break_level, Level), 948 Level >= 0, 949 ( py_call(sys:path:count(''), N), 950 N > 0 951 -> true 952 ; print_message(informational, janus(add_cwd)), 953 py_add_lib_dir_('', first) 954 ). 955 956:- multifile 957 prolog:repl_loop_hook/2. 958 959prologrepl_loop_hook(begin, Level) :- 960 Level >= 0, 961 py_is_initialized, 962 repl_add_cwd. 963 964 965 /******************************* 966 * CALLBACK * 967 *******************************/ 968 969:- dynamic py_call_cache/8 as volatile. 970 971:- meta_predicate py_call_string( , , ). 972 973% py_call_string(:String, +DictIn, -Dict) is nondet. 974% 975% Support janus.query_once() and janus.query(). Parses String into a goal 976% term. Next, all variables from the goal term that appear in DictIn 977% are bound to the value from this dict. Dict is created from the 978% remaining variables, unless they start with an underscore (e.g., 979% `_Time`) and the key `truth. On success, the Dict values contain 980% the bindings from the answer and `truth` is either `true` or 981% `Undefined`. On failure, the Dict values are bound to `None` and the 982% `truth` is `false`. 983% 984% Parsing and distributing the variables over the two dicts is cached. 985 986py_call_string(M:String, Input, Dict) :- 987 py_call_cache(String, Input, TV, M, Goal, Dict, Truth, OutVars), 988 !, 989 py_call(TV, M:Goal, Truth, OutVars). 990py_call_string(M:String, Input, Dict) :- 991 term_string(Goal, String, [variable_names(Map)]), 992 unbind_dict(Input, VInput), 993 exclude(not_in_projection(VInput), Map, OutBindings), 994 dict_create(Dict, bindings, [truth=Truth|OutBindings]), 995 maplist(arg(2), OutBindings, OutVars), 996 TV = Input.get(truth, 'PLAIN_TRUTHVALS'), 997 asserta(py_call_cache(String, VInput, TV, M, Goal, Dict, Truth, OutVars)), 998 VInput = Input, 999 py_call(TV, M:Goal, Truth, OutVars). 1000 1001py_call('NO_TRUTHVALS', M:Goal, Truth, OutVars) => 1002 ( call(M:Goal) 1003 *-> bind_status_no_no_truthvals(Truth) 1004 ; Truth = @false, 1005 maplist(bind_none, OutVars) 1006 ). 1007py_call('PLAIN_TRUTHVALS', M:Goal, Truth, OutVars) => 1008 ( call(M:Goal) 1009 *-> bind_status_plain_truthvals(Truth) 1010 ; Truth = @false, 1011 maplist(bind_none, OutVars) 1012 ). 1013py_call('DELAY_LISTS', M:Goal, Truth, OutVars) => 1014 ( call_delays(M:Goal, Delays) 1015 *-> bind_status_delay_lists(Delays, Truth) 1016 ; Truth = @false, 1017 maplist(bind_none, OutVars) 1018 ). 1019py_call('RESIDUAL_PROGRAM', M:Goal, Truth, OutVars) => 1020 ( call_delays(M:Goal, Delays) 1021 *-> bind_status_residual_program(Delays, Truth) 1022 ; Truth = @false, 1023 maplist(bind_none, OutVars) 1024 ). 1025 1026not_in_projection(Input, Name=Value) :- 1027 ( get_dict(Name, Input, Value) 1028 -> true 1029 ; sub_atom(Name, 0, _, _, '_') 1030 ). 1031 1032bind_none(@none). 1033 1034bind_status_no_no_truthvals(@true). 1035 1036bind_status_plain_truthvals(Truth) => 1037 ( '$tbl_delay_list'([]) 1038 -> Truth = @true 1039 ; py_undefined(Truth) 1040 ). 1041 1042bind_status_delay_lists(true, Truth) => 1043 Truth = @true. 1044bind_status_delay_lists(Delays, Truth) => 1045 py_call(janus:'Undefined'(prolog(Delays)), Truth). 1046 1047bind_status_residual_program(true, Truth) => 1048 Truth = @true. 1049bind_status_residual_program(Delays, Truth) => 1050 delays_residual_program(Delays, Program), 1051 py_call(janus:'Undefined'(prolog(Program)), Truth). 1052 1053py_undefined(X) :- 1054 py_call(janus:undefined, X). 1055 1056unbind_dict(Dict0, Dict) :- 1057 dict_pairs(Dict0, Tag, Pairs0), 1058 maplist(unbind, Pairs0, Pairs), 1059 dict_pairs(Dict, Tag, Pairs). 1060 1061unbind(Name-_, Name-_) :- 1062 sub_atom(Name, 0, 1, _, Char1), 1063 char_type(Char1, prolog_var_start), 1064 !. 1065unbind(NonVar, NonVar). 1066 1067 1068 /******************************* 1069 * SUPPORT PYTHON CALLS * 1070 *******************************/ 1071 1072:- public 1073 px_cmd/3, 1074 px_call/4, 1075 px_comp/7. 1076 1077% These predicates are helpers for the corresponding Python functions 1078% in janus.py. 1079 1080 1081% px_call(+Input:tuple, +Module, -Pred, -Ret) 1082% 1083% Supports px_qdet() and apply(). Note that these predicates 1084% explicitly address predicates in a particular module. For meta 1085% predicates, this implies they also control the context module. This 1086% leads to ``janus.cmd("consult", "consult", file)`` to consult _file_ 1087% into the module `consult`, which is not what we want. Therefore we 1088% set the context module to `user`, which is better, but probably also 1089% not what we want. 1090 1091px_call(-(), Module, Pred, Ret) => 1092 @(call(Module:Pred, Ret), user). 1093px_call(-(A1), Module, Pred, Ret) => 1094 @(call(Module:Pred, A1, Ret), user). 1095px_call(-(A1,A2), Module, Pred, Ret) => 1096 @(call(Module:Pred, A1, A2, Ret), user). 1097px_call(-(A1,A2,A3), Module, Pred, Ret) => 1098 @(call(Module:Pred, A1, A2, A3, Ret), user). 1099px_call(-(A1,A2,A3,A4), Module, Pred, Ret) => 1100 @(call(Module:Pred, A1, A2, A3, A4, Ret), user). 1101px_call(Tuple, Module, Pred, Ret) => 1102 compound_name_arguments(Tuple, _, Args), 1103 append(Args, [Ret], GArgs), 1104 Goal =.. [Pred|GArgs], 1105 @(Module:Goal, user). 1106 1107px_cmd(Module, Pred, Tuple) :- 1108 ( compound(Tuple) 1109 -> compound_name_arguments(Tuple, _, Args), 1110 Goal =.. [Pred|Args] 1111 ; Goal = Pred 1112 ), 1113 @(Module:Goal, user). 1114 1115px_comp(Module, Pred, Tuple, Vars, Set, TV, Ret) :- 1116 length(Out, Vars), 1117 ( compound(Tuple) 1118 -> compound_name_arguments(Tuple, _, Args), 1119 append(Args, Out, GArgs), 1120 Goal =.. [Pred|GArgs] 1121 ; Goal =.. [Pred|Out] 1122 ), 1123 compound_name_arguments(OTempl0, -, Out), 1124 tv_goal_and_template(TV, @(Module:Goal, user), FGoal, OTempl0, OTempl), 1125 findall(OTempl, FGoal, Ret0), 1126 ( Set == @true 1127 -> sort(Ret0, Ret) 1128 ; Ret = Ret0 1129 ). 1130 1131:- meta_predicate 1132 call_delays_py( , ). 1133 1134% 0,1,2: TruthVal(Enum) from janus.py 1135tv_goal_and_template('NO_TRUTHVALS', 1136 Goal, Goal, Templ, Templ) :- !. 1137tv_goal_and_template('PLAIN_TRUTHVALS', 1138 Goal, ucall(Goal, TV), Templ, -(Templ,TV)) :- !. 1139tv_goal_and_template('DELAY_LISTS', 1140 Goal, call_delays_py(Goal, TV), Templ, -(Templ,TV)) :- !. 1141tv_goal_and_template(Mode, _, _, _, _) :- 1142 domain_error("px_comp() truth", Mode). 1143 1144:- public 1145 ucall/2, 1146 call_delays_py/2. 1147 1148ucall(Goal, TV) :- 1149 call(Goal), 1150 ( '$tbl_delay_list'([]) 1151 -> TV = 1 1152 ; TV = 2 1153 ). 1154 1155call_delays_py(Goal, PyDelays) :- 1156 call_delays(Goal, Delays), 1157 ( Delays == true 1158 -> PyDelays = [] 1159 ; comma_list(Delays, Array), 1160 maplist(term_string, Array, PyDelays) 1161 ). 1162 1163 1164 /******************************* 1165 * PYTHON I/O * 1166 *******************************/ 1167 1168% py_write(+Stream, -String) is det. 1169% py_readline(+Stream, +Size, +Prompt, +Line) is det. 1170% 1171% Called from redefined Python console I/O to write/read using the 1172% Prolog streams. 1173 1174:- '$hide'((py_write/1, 1175 py_readline/4)). 1176 1177py_write(Stream, String) :- 1178 notrace(format(Stream, '~s', [String])). 1179 1180py_readline(Stream, Size, Prompt, Line) :- 1181 notrace(py_readline_(Stream, Size, Prompt, Line)). 1182 1183py_readline_(Stream, _Size, Prompt, Line) :- 1184 prompt1(Prompt), 1185 read_line_to_string(Stream, Read), 1186 ( Read == end_of_file 1187 -> Line = "" 1188 ; string_concat(Read, "\n", Line), 1189 py_add_history(Read) 1190 ). 1191 1192py_add_history(Line) :- 1193 ignore(catch(prolog:history(user_input, add(Line)), _, true)). 1194 1195 1196 /******************************* 1197 * COMPILING * 1198 *******************************/ 1199 1200% py_consult(+File, +Data, +Module) is det. 1201% 1202% Support janus.consult(file, data=None, module='user'). 1203 1204:- public py_consult/3. 1205py_consult(File, @none, Module) => 1206 consult(Module:File). 1207py_consult(File, Data, Module) => 1208 setup_call_cleanup( 1209 open_string(Data, In), 1210 load_files(Module:File, [stream(In)]), 1211 close(In)). 1212 1213 1214 /******************************* 1215 * MESSAGES * 1216 *******************************/ 1217 1218:- multifile 1219 prolog:error_message//1, 1220 prolog:message_context//1, 1221 prolog:message//1. 1222 1223prologerror_message(python_error(Class, Value)) --> 1224 { py_str(Value, Message) 1225 }, 1226 [ 'Python ', ansi(code, "'~w'", [Class]), ':', nl, 1227 ' ~w'-[Message] 1228 ]. 1229 1230prologmessage_context(context(_, PythonCtx)) --> 1231 { nonvar(PythonCtx), 1232 PythonCtx = python_stack(Stack), 1233 current_prolog_flag(py_backtrace, true), 1234 py_is_object(Stack), 1235 !, 1236 current_prolog_flag(py_backtrace_depth, Depth), 1237 py_call(traceback:format_tb(Stack, Depth), Frames) 1238 }, 1239 [ nl, 'Python stack:', nl ], 1240 sequence(py_stack_frame, Frames). 1241 1242py_stack_frame(String) --> 1243 { split_string(String, "\n", "", Lines) 1244 }, 1245 sequence(msg_line, [nl], Lines). 1246 1247msg_line(Line) --> 1248 [ '~s'-[Line] ]. 1249 1250prologmessage(janus(Msg)) --> 1251 message(Msg). 1252 1253message(version(Janus, Python)) --> 1254 [ 'Janus ~w embeds Python ~w'-[Janus, Python] ]. 1255message(venv(Dir, _EnvSiteDir)) --> 1256 [ 'Janus: using venv from ~p'-[Dir] ]. 1257message(venv(no_site_package_dir(VEnvDir, Dir))) --> 1258 [ 'Janus: venv dirrectory ~p does not contain ~p'-[VEnvDir, Dir] ]. 1259message(py_shell(no_janus)) --> 1260 [ 'Janus: py_shell/0: Importing janus into the Python shell requires Python 3.10 or later.', nl, 1261 'Run "', ansi(code, 'from janus import *', []), '" in the Python shell to import janus.' 1262 ]. 1263message(add_cwd) --> 1264 [ 'Interactive session; added `.` to Python `sys.path`'-[] ]
Call Python from Prolog
This library implements calling Python from Prolog. It is available directly from Prolog if the janus package is bundled. The library provides access to an embedded Python instance. If SWI-Prolog is embedded into Python using the Python package
janus-swi
, this library is provided either from Prolog or from the Python package.Normally, the Prolog user can simply start calling Python using py_call/2 or friends. In special cases it may be needed to initialize Python with options using py_initialize/3 and optionally the Python search path may be extended using py_add_lib_dir/1. */