execute Python within ConTeXt
There's a quick and dirty solution using buffers. Some remarks:
- I've defined (1)
\startpython ... \stoppython
to process Python content immediately, (2)\processpythonbuffer
to process a buffer as a Python file withprint
functions, and (3)\processpythonfile
to do the same as (1), but using a file instead of a buffer. (1) serves to get the result ipso facto, whereas the last ones are useful to delay or reuse a Python piece of code. I realized you asked for the first, so better late... - For buffers I'm creating a temporary file named
ctxpythonbuffer.py
which is executed and then removed. Unless you have a file with such a name, you won't have any problem. I'd use\savebuffer
, but it seems to be slower. - Both for buffers and files I'm using
io.popen
to capture command line output. I don't know if Python has bindings to Lua or vice versa, but you could do further research. Btw if you want to go deeper, you may find Luigi Scarso's experiment, LuaTeX lunatic, interesting. Details here and here. I won't, as I don't like Python. python3
is hardcoded, but you may change it topython
,python2
or whatever command your OS recognizes as a valid call to Python. If you want to add, say, a switch between versions or other settings, CLD manual is a nice starting point to write your own commands via Lua. Bonus: you learn another programming language, if you haven't embraced Lua yet;)
#abc.py
#Dummy file
print("a")
print("b")
print("c")
%\setuppapersize[A6]
\startluacode
userdata = userdata or {}
local implement = interfaces.implement
local suffix = ".py"
--Adapted from:
--https://gist.github.com/dukeofgaming/453cf950abd99c3dc8fc
local pythonbuffer =
function(file)
local handle = assert(io.popen(string.format("python3 %s", file), 'r'))
local output = assert(handle:read('*a'))
handle:close()
return output
end
userdata.processpythonbuffer =
function (content)
local name = "ctxpythonbuffer.py"
io.savedata(name,content)
result = pythonbuffer(name)
os.remove(name)
return result
end
userdata.processpythonfile =
function (name)
assert(name ~= "", "File name needed")
name = name:find(suffix.."$") and name or name..suffix
return pythonbuffer(name)
end
implement{
name = "processpythonbuffer",
public = true,
arguments = {"string"},
actions = function(s)
context(userdata.processpythonbuffer(buffers.getcontent(s)))
end
}
implement{
name = "processpythonfile",
public = true,
arguments = {"string"},
actions = function(s)
context(userdata.processpythonfile(s))
end
}
\stopluacode
%Buffer name isn't really important
%You could use another, less verbose...
\def\startpython%
{\def\stoppython{\processpythonbuffer{ctx_python_buffer}}%
\dostartbuffer[ctx_python_buffer][startpython][stoppython]}
\starttext
\startimath
\startpython
import math
print(math.cos(3.141592))
\stoppython
\stopimath
\startpython
Sentence1 = "{fmt} is {adj}".format(fmt="Con\\TeX t", adj="great")
print(Sentence1)
\stoppython
%https://www.programiz.com/python-programming/examples
\startbuffer[hcf]
# Python program to find H.C.F of two numbers
# define a function
def compute_hcf(x, y):
# choose the smaller number
if x > y:
smaller = y
else:
smaller = x
for i in range(1, smaller+1):
if((x % i == 0) and (y % i == 0)):
hcf = i
return hcf
num1 = 54
num2 = 24
print("The H.C.F. is", compute_hcf(num1, num2))
\stopbuffer
\startbuffer[powersof2]
# Display the powers of 2 using anonymous function
terms = 10
# Uncomment code below to take input from the user
# terms = int(input("How many terms? "))
# use anonymous function
result = list(map(lambda x: 2 ** x, range(terms)))
print("The total terms are:",terms)
for i in range(terms):
print("2 raised to power",i,"is",result[i])
\stopbuffer
%Adapted from
%https://www.w3schools.com/python/
\startbuffer[anotherpython]
b = "I prefer Lua over Python"
print(b[9:])
a = ("d", "e", "k", "n", "u", "t", "h")
x = slice(2)
print(a[x])
\stopbuffer
\processpythonbuffer{hcf}
\processpythonbuffer{anotherpython}
\processpythonfile{abc}
%\startlines ... \stoplines is the rough equivalent of Plain/LaTeX \obeylines
\startlines
\processpythonbuffer{powersof2}
\stoplines
\stoptext
For comparison, here is how you would implement something similar to Jairo's solution using the t-filter module.
\usemodule[filter]
\defineexternalfilter
[python]
[
filtercommand={python \externalfilterinputfile\space > \externalfilteroutputfile},
output={\externalfilterbasefile.tex},
cache=yes,
% directory=temp, if you want to redirect all temp files to a subdir
]
Yup, that's it! The option cache=yes
caches the results, so python is rerun only if the content of the buffer or environment has changed. You can also store the output in a temp directory so that the results are out of sight. See the documentation of the filter module for other features (including proper XML export!)
The t-filter module writes everything to external files and as such might be slightly slower that the pipe.io
method proposed by Jario.
The example given by Jairo works with a little change: \process<filter>buffer
provided by the filter module uses square brackets instead of curly brackets. For completeness, here is the complete example:
\usemodule[filter]
\defineexternalfilter
[python]
[
filtercommand={python \externalfilterinputfile\space > \externalfilteroutputfile},
output={\externalfilterbasefile.tex},
cache=yes,
% directory=temp, if you want to redirect all temp files to a subdir
]
\starttext
\startimath
\startpython
import math
print(math.cos(3.141592))
\stoppython
\stopimath
\startpython
Sentence1 = "{fmt} is {adj}".format(fmt="Con\\TeX t", adj="great")
print(Sentence1)
\stoppython
%https://www.programiz.com/python-programming/examples
\startbuffer[hcf]
# Python program to find H.C.F of two numbers
# define a function
def compute_hcf(x, y):
# choose the smaller number
if x > y:
smaller = y
else:
smaller = x
for i in range(1, smaller+1):
if((x % i == 0) and (y % i == 0)):
hcf = i
return hcf
num1 = 54
num2 = 24
print("The H.C.F. is", compute_hcf(num1, num2))
\stopbuffer
\startbuffer[powersof2]
# Display the powers of 2 using anonymous function
terms = 10
# Uncomment code below to take input from the user
# terms = int(input("How many terms? "))
# use anonymous function
result = list(map(lambda x: 2 ** x, range(terms)))
print("The total terms are:",terms)
for i in range(terms):
print("2 raised to power",i,"is",result[i])
\stopbuffer
%Adapted from
%https://www.w3schools.com/python/
\startbuffer[anotherpython]
b = "I prefer Lua over Python"
print(b[9:])
a = ("d", "e", "k", "n", "u", "t", "h")
x = slice(2)
print(a[x])
\stopbuffer
\processpythonbuffer[hcf]
\processpythonbuffer[anotherpython]
\processpythonfile{abc.py}
\startlines
\processpythonbuffer[powersof2]
\stoplines
\stoptext