6

Is there any way to execute Python code in ConTeXt document ? In LaTeX we have pythontex.

For example :

\starttext
\startpython
print("text")
\stoppython
text
\stoptext
2
  • Can you be more precise? If you want to execute commands passed via python3 -c, it's very easy to catch an output using io.popen in Lua (which ConTeXt uses). If you want to run files instead, the filter module could help.
    – user226564
    Feb 1, 2021 at 1:37
  • Both will be welcome. Thank you
    – Aviroum
    Feb 1, 2021 at 1:50

2 Answers 2

4

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
4
  • 1
    Oh, cool :D I'd use optional instead of string to stick to the bracket syntax, but it seems it's only available in LMTX. I think I could have used array, but whatever. Elegant solution :)
    – user226564
    Feb 3, 2021 at 22:55
  • Thank you @Aditya, your answer is elegant !
    – Aviroum
    Feb 6, 2021 at 19:34
  • @Aditya Can we put result of python in math equation? Eg: \startpython from sympy import apart from sympy.abc import x print(apart( (x+1)/(x-1), x)) I need to put the result of the print within $...$ Jun 5, 2021 at 19:40
  • The filter module just reads the output file. So you can format the output of python as you want..
    – Aditya
    Jun 6, 2021 at 14:46
6

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 with print 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 to python, 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

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .