Guide¶
The Wolfram Client Library is structured in submodules all located in wolframclient
:
evaluation
provides convenient methods to evaluate Wolfram Language expressions directly from Python. There are many ways to evaluate code, including evaluation by a local kernel, direct evaluation by a public or private Wolfram Cloud or calling a deployed API.language
provides a Python representation of Wolfram Language symbols and functions.serializers
provides serialization methods to various formats such as string InputForm and binary WXF format.deserializers
contains a parser for WXF.exception
regroups the exceptions and errors that the library may raise.
Expression Representation¶
- wolframclient.language.wl
A factory of
WLSymbol
instances without any particular context.This instance of
WLSymbolFactory
is conveniently used by calling its attributes. The following code represents various Wolfram Language expressions:# Now wl.Now # Quantity[3, "Hours"] wl.Quantity(3, "Hours") # Select[PrimeQ, {1,2,3,4}] wl.Select(wl.PrimeQ, [1, 2, 3, 4])
Represent symbols in various contexts:
>>> wl.Developer.PackedArrayQ Developer`PackedArrayQ >>> wl.Global.f Global`f
Specify a context and a subcontext:
>>> wl.MyContext.MySubContext.SymbolName MyContext`MySubContext`SymbolName
- wolframclient.language.wlexpr = <class 'wolframclient.language.expression.WLInputExpression'>
Represent Wolfram Language expressions with input form strings.
Convenient alias for
WLInputExpression
.Represent an expression:
>>> wlexpr('Select[Range[10], EvenQ]') (Select[Range[10], EvenQ])
Represent a pure function that squares an input argument:
>>> wlexpr('# ^ 2 &' ) (# ^ 2 &)
- wolframclient.language.System = System
A factory of
WLSymbol
instances havingSystem`
context.See
WLSymbolFactory
for more details.Represent a symbol in the System context:
>>> System.ImageIdentify System`ImageIdentify
- wolframclient.language.Global = Global
A factory of
WLSymbol
instances havingGlobal`
context.See
WLSymbolFactory
andWLSymbolFactory
for more details.Represent a symbol in the Global context:
>>> Global.mySymbol Global`mySymbol
Represent a function call to a function:
>>> Global.myFunction('foo') Global`myFunction['foo']
- class wolframclient.language.expression.WLSymbolFactory(name=None)[source]
Provide a convenient way to build objects representing arbitrary Wolfram Language expressions through the use of attributes.
This class is conveniently instantiated at startup as
wl
,Global
andSystem
. It should be instantiated only to represent many symbols belonging to the same specific context.Example:
>>> dev = WLSymbolFactory('Developer') >>> dev.PackedArrayQ Developer`PackedArrayQ
Alternative:
>>> wl.Developer.PackedArrayQ Developer`PackedArrayQ
Serialization¶
- wolframclient.serializers.export(data, stream=None, target_format='wl', **options)[source]
Serialize input data to a target format.
Input data can be any supported Python type, including
list
,dict
or any serializable Python object.Serializable python objects are class extending
WLSerializable
and types declared in an encoder.The default format is InputForm string:
>>> export(wl.Range(3)) b'Range[3]'
Specify WXF format by setting target_format:
>>> export([1,2,3], target_format='wxf') b'8:fsListCCC'
Note
WXF is a binary format for serializing Wolfram Language expression. Consult the format specifications for in depth format description.
WXF byte arrays are deserialized with
binary_deserialize()
:>>> wxf = export([1,2,3], target_format='wxf') >>> binary_deserialize(wxf) [1, 2, 3]
If stream is specified with a string, it is interpreted as a file path and the serialized form is written directly to the specified file. The file is opened and closed automatically:
>>> export([1, 2, 3], stream='file.wl') 'file.wl'
If stream is specified with an output stream, the serialization bytes are written to it.
Any object that implements a write method, e.g.
file
,io.BytesIO
orio.StringIO
, is a valid value for the stream named parameter:>>> with open('file.wl', 'wb') as f: ... export([1, 2, 3], stream=f) ... <open file 'file.wl', mode 'wb' at 0x10a4f01e0>
Formats¶
InputForm is the default format and is the most readable one:
>>> export([1, 2, 3], target_format = "wl")
'{1, 2, 3}'
Serialized output can be imported in a kernel using ToExpression.
WXF is also available as an efficient binary representation of Wolfram Language expressions:
>>> export([1, 2, 3], target_format = "wxf")
'8:f\x03s\x04ListC\x01C\x02C\x03'
The WXF format supports compression using zlib; the compression is disabled by default:
>>> export([1, 2, 3], target_format = "wxf", compress = True)
'8C:x\x9cKc.f\xf1\xc9,.qftfrf\x06\x00\x1b\xf8\x03L'
Serialized output can be imported in a kernel using BinaryDeserialize.
Supported Types¶
Built-in Data Types¶
Built-in data structures are all supported list
, set
, frozenset
and dict
.
Example:
>>> export({"list": [1, 2, 3], "set": set([1, 2, 2, 4]), "frozenset": frozenset([1, 2, 2, 4]), "dict": dict(a = 2)})
'<|"list" -> {1, 2, 3}, "set" -> {1, 2, 4}, "frozenset" -> {1, 2, 4}, "dict" -> <|"a" -> 2|>|>'
If no converter is defined for a class that has an __iter__ method, it is converted to a Wolfram Language List:
>>> export((i + 2 for i in range(10)))
'{2, 3, 4, 5, 6, 7, 8, 9, 10, 11}'
Python generators are also serialized as List:
>>> def gen():
... yield 1
... yield 2
...
>>> export(gen())
'{1, 2}'
Note
Python generators should preferably be used when serializing big data to avoid running out of memory.
To preserve ordering in associations use collections.OrderedDict
:
>>> from collections import OrderedDict
>>> export(OrderedDict([(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd')]))
'<|0 -> "a", 1 -> "b", 2 -> "c", 3 -> "d"|>'
Numeric Types¶
Numeric types are natively supported; int
, float
, complex
and Decimal
serialize to
their Wolfram Language counterpart:
>>> export({'int': 1, 'float':2.3, 'decimal': decimal.Decimal(1), 'complex': complex(3, 4)})
'<|"int" -> 1, "float" -> 2.300000, "decimal" -> 1, "complex" -> Complex[3.000000, 4.000000]|>'
IEEE exceptions infinity and NaN are converted respectively to DirectedInfinity and Indeterminate:
>>> export({'+inf': float('inf'), '-inf': - float('inf'), 'nan': float('NaN')})
'<|"+inf" -> DirectedInfinity[1], "-inf" -> DirectedInfinity[-1], "nan" -> Indeterminate|>'
Fraction
serializes to Rational:
>>> export(fractions.Fraction(2, 3))
'(2 / 3)'
DateObject Serialization¶
datetime
, time
and date
are serialized to DateObject and all assume that the time zone to use is the current one at evaluation time:
>>> import datetime
>>> now = datetime.datetime.now()
>>> export([now.time(), now.date(), now])
'{TimeObject[{16, 1, 19.993822}, TimeZone -> $TimeZone], DateObject[{2018, 3, 16}], DateObject[{2018, 3, 16, 16, 1, 19.993822}, "Instant", "Gregorian", $TimeZone]}'
timedelta
is serialized to Quantity:
>>> export(datetime.timedelta(seconds = 340))
Specify a time zone in Python using pytz.timezone()
and serialize the date to a DateObject:
>>> from pytz import timezone
>>> export(timezone('US/Eastern').localize(datetime.datetime.now()))
'DateObject[{2018, 3, 16, 16, 4, 17.712409}, "Instant", "Gregorian", "US/Eastern"]'
Extensible Mechanism¶
The serializers
module provides mechanisms to extend built-in core functions and to define custom class serializations. There are three ways to extend serialization:
Extend
WLSerializable
and override itsto_wl()
method.Call
export()
with normalizer set to a normalizer function. This function will be applied to each object prior to the serialization process.Declare a type encoder.
Serializable Classes¶
- class wolframclient.serializers.serializable.WLSerializable[source]
A class that can be serialized using
export()
.Custom serialization of a class is done by subclassing this class:
from wolframclient.serializers.serializable import WLSerializable from wolframclient.language import wl from wolframclient.serializers import export class MyPythonClass(WLSerializable): def __init__(self, *arguments): self.arguments = arguments def to_wl(self): return wl.MyWolframFunction(*self.arguments)
Serialize
MyPythonClass
usingexport()
:>>> export(MyPythonClass('foo', 'bar')) b'MyWolframFunction["foo", "bar"]'
Serialization is applied recursively; arguments are also serialized:
>>> export(MyPythonClass(1, 2, MyPythonClass(2, 3))) 'MyWolframFunction[1, 2, MyWolframFunction[2, 3]]'
- to_wl()[source]
Return the serialized form of a given Python class.
The returned value must be a combination of serializable types.
Normalizer¶
A normalizer is a function that takes one argument and returns one python object. It can either return a new object or pass the input if it can’t deal with that type.
Define a class:
class MyPythonClass(object):
def __init__(self, *arguments):
self.arguments = arguments
Define a normalizer function:
from wolframclient.language import wl
from wolframclient.serializers import export
def normalizer(o):
if isinstance(o, MyPythonClass):
return wl.MyWolframFunction(*o.arguments)
# don't forget to return the input if we can't deal with the type.
return o
Serialize an instance of MyPythonClass
using the normalizer function defined previously:
>>> export(MyPythonClass(1,2), normalizer=normalizer)
b'MyWolframFunction[1, 2]'
Encoder¶
The serialization of a Python object relies on encoder functions. Each encoder is attached to a set of Python types. Encoders are generators of bytes. The library defines encoders for most built-in Python types and for the core components of some popular libraries such as PIL Image
, NumPy arrays and Pandas Series
.
- wolframclient.serializers.encoder.wolfram_encoder = <wolframclient.serializers.encoder.WolframDispatch object>
Mapping between Python types and encoders used during serializations.
This instance of
Dispatch
is used inexport()
to serialize Python expressions and produce a stream of bytes.Register new encoders:
The annotation
dispatch()
applied to a function, defines an encoder and associates it to the types passed as argument of the annotation.Define a new class:
class MyPythonClass(object): def __init__(self, *arguments): self.arguments = arguments
Specify its encoder:
from wolframclient.serializers import wolfram_encoder from wolframclient.language import wl from wolframclient.serializers import export @wolfram_encoder.dispatch(MyPythonClass) def my_encoder(serializer, o): return serializer.encode(wl.MyWolframFunction(*o.arguments))
Serialize an expression:
>>> export(MyPythonClass(1,2)) b'MyWolframFunction[1, 2]'
Alternatively, apply
register()
to a function and its associated type(s) achieves the same result.It is not possible to associate two encoders with the same type, but it’s possible to remove a mapping. First, unregister the previous encoder:
wolfram_encoder.unregister(MyPythonClass)
And register it again with
register()
:wolfram_encoder.register(my_encoder, MyPythonClass)
Update with a dispatcher:
Another way to extend supported types is to create a new
Dispatch
, map various types and encoders and ultimately updatewolfram_encoder
usingupdate()
.Create a new dispatcher and register
MyPythonClass
:from wolframclient.utils.dispatch import Dispatch dispatch = Dispatch() dispatch.register(my_encoder, MyPythonClass)
Update the main encoder with the new dispatch instance:
wolfram_encoder.update(dispatch)
Serialize an expression:
>>> export(MyPythonClass(1,2)) b'MyWolframFunction[1, 2]'
Define plugins:
The library supports an entry point dedicated to new encoders: wolframclient_serializers_encoder. The library uses this entry point to loads plugins at runtime as separated libraries. For more information about entry points, refer to the documentation page about entry points.
The plugin name must be unique and the value must reference a dispatcher instance. This instance is loaded and used to update
wolfram_encoder
. A plugin is a simple way to distribute encoders as a separate library.One type must have a unique encoder associated to it; as a consequence, two plugins registering an encoder for the same type are incompatible. It is strongly advised to create one plugin for each existing Python library, e.g. have one plugin dedicated to NumPy and one to Pandas, which makes heavy use of NumPy arrays.
Deserialization¶
- wolframclient.deserializers.binary_deserialize = <function binary_deserialize>[source]
Deserialize binary data and return a Python object.
Serialize a Python object to WXF:
>>> wxf = export({'key' : [1,2,3]}, target_format='wxf')
Retrieve the input object:
>>> binary_deserialize(wxf) {'key': [1, 2, 3]}
A stream of
WXFToken
is generated from the WXF input by a instance ofWXFParser
.The consumer must be an instance of
WXFConsumer
. If none is provided,WXFConsumerNumpy
is used. To disable NumPy array support, useWXFConsumer
.Named parameters are passed to the consumer. They can be any valid parameter of
next_expression()
, namely:dict_class: map WXF Association to dict_class in place of a regular
dict
Evaluating Expressions¶
Cloud API¶
Exceptions¶
- class wolframclient.exception.WolframLanguageException(payload, exec_info=None)[source]
The most generic exception raised by the Wolfram Client Library.
This class is
WLSerializable
and will automatically serialize to a failure box when evaluated in Wolfram Desktop.- to_wl(*args, **opts)[source]
Return the serialized form of a given Python class.
The returned value must be a combination of serializable types.
- class wolframclient.exception.AuthenticationException(response, msg=None)[source]
Error in an authentication request.
- class wolframclient.exception.WolframEvaluationException(error, result=None, messages=[])[source]
Error after an evaluation raising messages.
- class wolframclient.exception.RequestException(response, msg=None)[source]
Error in an HTTP request.
- class wolframclient.exception.SocketException(payload, exec_info=None)[source]
Error while operating on socket.