pyffi - Use Python from Racket
1 Introduction
2 Release Information
3 Supported Platforms
4 Installation
4.1 Python 3.1x
4.2 One-time Configuration
4.3 Optional:   Num  Py for scientific computing
5 An introduction to pyffi
5.1 Atomic values:   numbers, Booleans and None
5.2 Compound Values:   strings, tuples, lists, and, dictionaries
5.3 Builtin functions and modules
5.4 Objects, Callable objects, Functions, Methods and Properties
5.5 Exceptions
6 Initialization of the Python Interpreter
6.1 The Big Picture
6.2 Reference
initialize
post-initialize
add-initialization-thunk
diagnostics
7 Evaluation
run
run*
8 Datatypes
8.1 Python Lists - pylist
pylist?
pylist
pylist-ref
pyfirst
pysecond
pylist-set!
list->pylist
vector->pylist
pylist-length
pylist->list
pylist->vector
pylist->pytuple
in-pylist
pylist-insert!
pylist-append-item!
pylist-reverse!
pylist-sort!
pylist-get-slice
8.2 Python Tuples - pytuple
pytuple?
pytuple
pytuple-ref
list->pytuple
vector->pytuple
pytuple-length
pytuple->list
pytuple->vector
pytuple->immutable-vector
pytuple->pylist
in-pytuple
pytuple-get-slice
8.3 Python Dictionaries - pydict
pydict?
hash->pydict
pydict->hash
pydict
pydict-ref
pydict-set!
pydict-remove!
pydict-clear!
pydict-contains?
pydict-copy
pydict-keys
pydict-values
pydict-count
pydict-merge!
in-pydict
8.4 Python Strings - pystring
pystring?
pystring
string->pystring
pystring->string
pystring-length
pystring-ref
subpystring
in-pystring
8.5 Python Generators
in-pygenerator
Index
9.1

pyffi - Use Python from Racket🔗ℹ

Jens Axel Søgaard <jensaxel@soegaard.net>

 (require pyffi) package: pyffi-lib

This library pyffi allows you to use Python from Racket.

    1 Introduction

    2 Release Information

    3 Supported Platforms

    4 Installation

      4.1 Python 3.1x

      4.2 One-time Configuration

      4.3 Optional: NumPy for scientific computing

    5 An introduction to pyffi

      5.1 Atomic values: numbers, Booleans and None

      5.2 Compound Values: strings, tuples, lists, and, dictionaries

      5.3 Builtin functions and modules

      5.4 Objects, Callable objects, Functions, Methods and Properties

      5.5 Exceptions

    6 Initialization of the Python Interpreter

      6.1 The Big Picture

      6.2 Reference

    7 Evaluation

    8 Datatypes

      8.1 Python Lists - pylist

      8.2 Python Tuples - pytuple

      8.3 Python Dictionaries - pydict

      8.4 Python Strings - pystring

      8.5 Python Generators

    Index

1 Introduction🔗ℹ

The library pyffi makes it possible to use Python libraries from Racket.

The bridge between Racket and Python is for now one-way only: a Racket program can call Python function and invoke Python methods, but it’s not possible (yet) to pass Racket functions to the Python side.

Python libraries implemented in Python ought to work out of the box.

Python libraries implemented as C extensions might work - if the C extension supports introspection via the Python module ‘inspect‘. For C extensions without introspection you can drop down to a low-level FFI which works in the same style as the Racket C FFI.

Bindings for Numpy, the popular package for numerical calculations, are provided.

2 Release Information🔗ℹ

This is the first release of the library, so be prepared to deal with some rough edges here and there.

Report bugs to the pyffi GitHub repository: github.com/soegaard/pyffi/.

If you run into any questions, use the forum Racket Discourse.

Alternatively, feel free to ask questions on the chat server Racket Discord.

3 Supported Platforms🔗ℹ

For now pyffi supports Python 3.10 and newer on macOS and Linux/Unix. Windows is currently not supported.

4 Installation🔗ℹ

The plan is simple: install Python 3.1x then tell pyffi where Python is installed.

Optional: install NumPy if you want to use pyffi/numpy.

4.1 Python 3.1x🔗ℹ

In order to use pyffi you need Python 3.10 or newer.

The official distribution of Python is here: https://www.python.org/downloads/

If you prefer to use an alternative source of distribution (read your favorite package manager), that’s fine too - as long as it includes both the standard interpreter as well as the shared library libpython.

4.2 One-time Configuration🔗ℹ

The last installation step consists of telling pyffi where your shared library libpython is placed.

The easiest way of doing this is to run the script configure-pyffi.

  • Open a terminal.

  • Check that python3 or python invokes your Python interpreter.

  • Run: raco pyffi configure

If the command python3 is not in your path, then you can write this instead:

  • raco pyffi configure <path-to-your-python-command>

This will find the location of your shared library, print it, and, store it in your Racket preferences under the key pyffi:libdir.

You are now ready to use pyffi.

4.3 Optional: NumPy for scientific computing🔗ℹ

The Python package NumPy has the slogan:

The fundamental package for scientific computing with Python.

If you want to use pyffi/numpy then you need to install NumPy in your Python environment first.

There are many ways of installing NumPy, but the simplest is to use the following in a terminal:

  • python3 -m pip install numpy

5 An introduction to pyffi🔗ℹ

The pyffi library makes it possible to use Python libraries from a Racket program.

A Racket program can start a Python process by requiring pyffi and calling initialize. After the initialization run and run* can be used to evaluate expressions and statements in the Python process.

> (require pyffi)
> (initialize)
> (post-initialize)
> (run "1+2")

3

Here Racket starts an embed Python process. The Python "1+2" is parsed, compiled and evaluated by Python. The resulting Python value 3 is then converted to the Racket value 3.

5.1 Atomic values: numbers, Booleans and None🔗ℹ

Atomic Python values (numbers, Booleans and None) are automatically converted to their corresponding Racket values.

> (require pyffi)
> (initialize)
> (post-initialize)
> (run "12")

12

> (run "34.")

34.0

> (run "5+6j")

5.0+6.0i

> (run "False")

#f

> (run "True")

#t

> (list (run "None"))

'(#<void>)

5.2 Compound Values: strings, tuples, lists, and, dictionaries🔗ℹ

Compound (non-atomic) Python values such as strings, tuples, lists and dicts are not converted to Racket values. Instead they are wrapped in a struct named obj. Due to a custom printer handler these wrapped values print nicely.

> (run "'Hello World'")

(obj "str" : 'Hello World')

> (run "(1,2,3)")

(obj "tuple" : (1, 2, 3))

> (run "[1,2,3]")

(obj "list" : [1, 2, 3])

> (run "{'a': 1, 'b': 2}")

(obj "dict" : {'a': 1, 'b': 2})

The values display nicely too:

> (displayln (run "'Hello World'"))

Hello World

> (displayln (run "(1,2,3)"))

(1, 2, 3)

> (displayln (run "[1,2,3]"))

[1, 2, 3]

> (displayln (run "{'a': 1, 'b': 2}"))

{'a': 1, 'b': 2}

Printing and displaying a Python object use the __repr__ and __str__ methods of the object respectively.

The idea is that Racket gains four new data types: pystring, pytuple, pylist and pydict.

To convert a compound value use pystring->string, pytuple->vector, pylist->list or pydict->hash.

> (pystring->string (run "'Hello World'"))

"Hello World"

> (pytuple->vector (run "(1,2,3)"))

'#(1 2 3)

> (pylist->list (run "[1,2,3]"))

'(1 2 3)

> (pydict->hash (run "{'a': 1, 'b': 2}"))

'#hash(("a" . 1) ("b" . 2))

Similarly, you can convert Racket values to Python ones.

> (string->pystring "Hello World")

(obj "str" : 'Hello World')

> (vector->pytuple #(1 2 3))

(obj "tuple" : (1, 2, 3))

> (list->pylist '(1 2 3))

(obj "list" : [1, 2, 3])

> (hash->pydict (hash "a" 1 "b" 2))

(obj "dict" : {'b': 2, 'a': 1})

It’s important to note that creating Python values using string->pystring, vector->pytuple, list->pylist and hash->pydict is much more efficient than using run. The overhead of run is due to the parsing and compiling of its input string. In contrast string->pystring and friends use the C API to create the Python values directly.

The data types also have constructors:

> (pystring #\H #\e #\l #\l #\o)

(obj "str" : 'Hello')

> (pytuple 1 2 3)

(obj "tuple" : (1, 2, 3))

> (pylist 1 2 3)

(obj "list" : [1, 2, 3])

> (pydict "a" 1 "b" 2)

(obj "dict" : {'a': 1, 'b': 2})

The new types pystring, pytuple, pylist and pydict can be used with for.

> (for/list ([x (in-pystring (string->pystring "Hello"))]) x)

'(#\H #\e #\l #\l #\o)

> (for/list ([x (in-pytuple (vector->pytuple #(1 2 3)))]) x)

'(1 2 3)

> (for/list ([x (in-pylist (list->pylist '(1 2 3)))]) x)

'(1 2 3)

> (for/list ([(k v) (in-pydict (hash->pydict (hash "a" 1 "b" 2)))]) (list k v))

'(((obj "str" : 'b') 2) ((obj "str" : 'a') 1))

5.3 Builtin functions and modules🔗ℹ

Use run for expressions and run* for statements.

The previous sections showed how to evaluate expressions using the Python interpreter. Now we will look at statements.

> (run* "x = 1+2")

Here the statement x = 1+2 is parsed, compiled and executed. The result of the expression 1+2 is stored in the global variable x.

To retrieve the value of the Python variable x we could use run:
> (run "x")

3

But due to the overhead of run it is better to make a direct variable reference.

> main.x

3

Here main is the name we have given to the module used for the global namespace of the Python interpreter. The dotted identifier main.x thus references the variable x in the global namespace.

The import is done with
import builtins

In a standard Python interpreter nothing is imported when the interpreter is started, but with pyffi the module builtins is imported at startup.

Since Python modules are first class values, we can see their printed representations:

> main

(obj "module" : <module '__main__' (<class '_frozen_importlib.BuiltinImporter'>)>)

> builtins

(obj "module" : <module 'builtins' (built-in)>)

The module builtins is also how we access the built-in Python functions.

> (builtins.abs -7)

7

> (builtins.list "Hello")

(obj "list" : ['H', 'e', 'l', 'l', 'o'])

> (builtins.range 2 5)

(obj "range" : range(2, 5))

> (builtins.list (builtins.range 2 5))

(obj "list" : [2, 3, 4])

If you find the name builtins too long, then you can give it a new, shorter name.

> (define b builtins)
> (b.abs -7)

7

If you access the abs functions directly, you get a callable object:

> b.abs

(obj callable "builtin_function_or_method" : <built-in function abs>)

A callable object can be used just like a normal Racket function:

> (map b.abs '(1 -2 3 -4))

'(1 2 3 4)

To use functions from the Python standard library, you need to import it before you can use it. The standard library sys provide a lot of system information. Let’s use it to find the version of the Python interpreter.

> (import sys)
> sys.version_info

(obj "version_info" : sys.version_info(major=3, minor=14, micro=1, releaselevel='final', serial=0))

The list of modules in The Python Standard Library is long, so let’s just try one more.

We want to print a text calendar for the current month.

Documentation for calendar.

> (import calendar)
> (calendar.TextCalendar)

(obj "TextCalendar" : <calendar.TextCalendar object at 0x120f00e10>)

> (displayln ((calendar.TextCalendar) .formatmonth 2022 7))

     July 2022

Mo Tu We Th Fr Sa Su

             1  2  3

 4  5  6  7  8  9 10

11 12 13 14 15 16 17

18 19 20 21 22 23 24

25 26 27 28 29 30 31

The expression (calendar.TextCalendar) instantiates a TextCalendar object. The syntax (object .method arg ...) is used to invoke the method formatmonth with the arguments 2022 and 7 (for July).

The documentation for formatmonth shows its signature:

formatmonth(theyear, themonth, w=0, l=0)

The two first arguments theyear and themonth are positional arguments and the two last arguments w and l are keyword arguments both has 0 has as default value.

The keyword argument w specifies the width of the date columns. We can get full names of the week days with a width of 9.

> (displayln ((calendar.TextCalendar) .formatmonth 2022 7 #:w 9))

                              July 2022

  Monday   Tuesday  Wednesday  Thursday   Friday   Saturday   Sunday

                                             1         2         3

     4         5         6         7         8         9        10

    11        12        13        14        15        16        17

    18        19        20        21        22        23        24

    25        26        27        28        29        30        31

5.4 Objects, Callable objects, Functions, Methods and Properties🔗ℹ

All values in Python are represented as objects. This differs from Racket, where most data types (e.g. numbers and strings) aren’t objects.

In the data model used by Python, all objects have an identity, a type and a value. In practice the identity of a Python object is represented by its address in memory [the CPython implementation never moves objects].

To represent a Python object in Racket it is wrapped in an obj struct. The structure contains the type name as a string and a pointer to the object. The wrapper sets the structure property gen:custom-write to display, write and print the wrapped objects nicely.

The result of repr() is used to write an object.
The result of str() is used to display an object.

> (define s (string->pystring "foo"))
> (repr s)

"'foo'"

> (writeln s)

(obj "str" : 'foo')

> (str s)

"foo"

> (displayln s)

foo

Functions and in general callable objects support the well-known syntax f(a,b,c). Such callable objects are wrapped in an callable-obj struct, which has obj as a super type. The callable-obj use the struct property prop:procedure to make the wrapper applicable.

> (run* "def f(x): return x+1")
> (define f main.f)
> f

(obj callable "function" : <function f at 0x120cbb060>)

> (f 41)

42

Function calls with keywords work as expected. A Python keyword is simply prefixed with #: to turn it into a Racket keyword, as this example shows:

> (run* "def hello(name, title='Mr'): return 'Hello ' + title + ' ' + name")
> (displayln (main.hello "Foo"))

Hello Mr Foo

> (displayln (main.hello #:title "Mrs" "Bar"))

Hello Mrs Bar

In order to illustrate methods, let’s look at the Calendar class in the calendar module.

> (import calendar)
> calendar.Calendar

(obj callable "type" : <class 'calendar.Calendar'>)

Calling the class gives us an instance object. We pass 0 to make Monday the first week day.

> (calendar.Calendar #:firstweekday 0)

(obj "Calendar" : <calendar.Calendar object at 0x120e4dbe0>)

One of the methods of a calendar object is monthdatescalendar.

> (define cal (calendar.Calendar #:firstweekday 0))
> cal.monthdatescalendar

(obj callable "method" : <bound method Calendar.monthdatescalendar of <calendar.Calendar object at 0x120f00e10>>)

The syntax obj.method gives us a bound method, which we can call. Bound methods are wrapped in method-obj to make them applicable.

The use of pyfirst is to reduce the amount of output.

> (define year  2022)
> (define month    9)
> (pyfirst (pyfirst (cal.monthdatescalendar year month)))

(obj "date" : datetime.date(2022, 8, 29))

However, we can also invoke the monthdatescalendar method directly with the help of the syntax (obj .method argument ...).

> (pyfirst (pyfirst (cal .monthdatescalendar year month)))

(obj "date" : datetime.date(2022, 8, 29))

Method invocations can be chained. That is, if a method call returns an object, we can invoke a method on it. The fist element of a list can be retrieved by the pop method, so we can replace the two calls to pyfirst with two invocations of .pop.

> (cal .monthdatescalendar year month  .pop 0 .pop 0)

(obj "date" : datetime.date(2022, 8, 29))

Besides methods an object can have properties (attributes). The syntax is obj.attribute. Most Python objects carry a little documentation in the oddly named __doc__ attribute.

> (displayln cal.__doc__)

Base calendar class. This class doesn't do any formatting. It simply

provides data to subclasses.

5.5 Exceptions🔗ℹ

An exception on the Python side is converted to an exception on the Racket side. The exception will be printed with a full traceback.

> (run "1/0")

run: Python exception occurred;

 ZeroDivisionError: division by zero

 

  File "<string>", line 1, in <module>

6 Initialization of the Python Interpreter🔗ℹ

6.1 The Big Picture🔗ℹ

In order to use the functions in pyffi you need to start a Python interpreter. The call (initialize) does just that. A standard approach would be to make the call in your "main" module of your program.

That the Python interpreter isn’t available until the main module has been instantiated leads to a few complications. The problem is the interpreter instance is not available when the modules required by "main" is instantiated.

As an example: The module pyffi/numpy contains bindings for NumPy. If the main module looks like this:

#lang racket

(require pyffi pyffi/numpy)

(initialize)

Then pyffi/numpy is instantiated before the interpreter is started. This means pyffi/numpy can’t inspect the Python module numpy to get the function signatures it needs.

To solve this problem pyffi/numpy registers a number of initialization thunks to be run after the interpreter has started. The function post-initialize runs these initialization thunks.

To sum up, the typical main module for a program that uses pyffi starts:

#lang racket

(require pyffi pyffi/numpy)

(initialize)

(post-initialize)

6.2 Reference🔗ℹ

procedure

(initialize)  void?

Starts a Python interpreter using libpython.

The precise steps taken are:

  • The locations of libpython and the folder containing the standard library is fetched from the preferences (keys 'pyffi:libdir and 'pyffi:data).

  • Calls Py_Initialize which starts a Python interpreter. Initializes the table of of loaded modules and creates the modules builtins, __main__ and sys.

  • Imports __main__, builtins, operator, traceback and inspect.

  • Creates instances of True, False, and, None.

procedure

(post-initialize)  void?

Run initialization thunks that needs a running Python instance.

procedure

(add-initialization-thunk thunk)  void?

  thunk : thunk?
Add a thunk to be run by post-initialize.

procedure

(diagnostics)  void?

Print important Python paths.

7 Evaluation🔗ℹ

procedure

(run string-to-evaluate)  obj?

  string-to-evaluate : string?
Evaluate the string string-to-evaluate in the running Python interpreter. The string must contain a Python expression. The expression is evaluated in the "main" module.

The resulting Python object is converted to a Racket value via pr.

If an exception is triggered on the Python side, a Racket exception is raised containing the error message and the traceback.

> (run "[1+10, '2'+'20', (3,30), {4: 40}]")

(obj "list" : [11, '220', (3, 30), {4: 40}])

> (run "x = 'this is not an expression'")

run: Python exception occurred;

   File "<string>", line 1

     x = 'this is not an expression'

       ^

 SyntaxError: invalid syntax

procedure

(run* string-to-evaluate)  void?

  string-to-evaluate : string?
Evaluate the string string-to-evaluate in the running Python interpreter. The string must contain a Python statement. The statement is executed in the "main" module.

If an exception is triggered on the Python side, a Racket exception is raised containing the error message and the traceback.

> (run* "x = 1+10")
> main.x

11

8 Datatypes🔗ℹ

In Python all values are stored as objects. An object has an identity, a type and a value.

The identity of an object is given by the object’s address in memory. The is operator in Python corresponds to eq? in Racket.

Objects whose value can change are mutable. If the value of an object can’t change the object is called immutable. Note: In Racket an immutable vector can contain (mutable) boxes. Even though the vector is immutable the contents of the boxes can change. What doesn’t change is the identity of the boxes in the vector. In the same way an immutable Python container might contain mutable objects.

In Python numbers, strings and tuples are immutable. Python dictionaries and lists are mutable.

Python objects aren’t explicitly destroyed. When an object becomes unreachable, the memory of an object is reclaimed by the garbage collector. The current Python interpreter uses reference counting to keep track of an objects reachability.

When a Python object is returned from Python to Racket as a new reference, the Python system won’t deallocate the object until Racket says it’s okay to do so. Since we don’t want manually to keep track of the lifetime of Python objects, the low-level C-bindings register all new references will a will executor. When the Racket garbage collector detects a value is unreachable, the garbage collector will execute any wills associated with the value. The will executor we are using, simply decrements the reference count of the Python object.

If you stick to the high level functions in pyffi you don’t need to worry about reference counting. However it might be relevant if you need to use the low-level C-API.

8.1 Python Lists - pylist🔗ℹ

Despite the name a Python list is not a singly linked list, but an array. Objects with the type "list" will be called pylist to tell them apart from standard Racket lists.

The operations pylist, list->pylist and vector->pylist can be used to construct pylists from Racket values.

In in for-loops, use in-pylist to iterate through the elements.

procedure

(pylist? v)  boolean?

  v : any/c
Returns #t if v is a pylist (an obj with type "list").

> (pylist 1 2 3)

(obj "list" : [1, 2, 3])

> (pylist? (pylist 1 2 3))

#t

> (pylist? (pylist))

#t

> (pylist? '(1 2 3))

#f

procedure

(pylist v ...)  pylist?

  v : any/c
Returns a newly allocated pylist containing the vs as its elements.

> (pylist 1 2 3 4)

(obj "list" : [1, 2, 3, 4])

> (pylist)

(obj "list" : [])

> (pylist (pylist 1 2 3 4) 5 (pylist 6 7))

(obj "list" : [[1, 2, 3, 4], 5, [6, 7]])

procedure

(pylist-ref xs i)  any/c

  xs : pylist?
  i : exact-nonnegative-integer?
Returns the element with index i in the pylist xs. The first element has index 0, and the last elements is one less than (pylist-length xs).

This function takes constant time.

> (pylist-ref (pylist "a" "b" "c" "d") 1)

"b"

procedure

(pyfirst xs)  any/c

  xs : pylist?
Returns the first element of the pylist xs.

> (pyfirst (pylist "a" "b" "c" "d"))

"a"

procedure

(pysecond xs)  any/c

  xs : pylist?
Returns the second element of the pylist xs.

> (pysecond (pylist "a" "b" "c" "d"))

"b"

procedure

(pylist-set! xs i v)  any/c

  xs : pylist?
  i : exact-nonnegative-integer?
  v : any/c
Replace the element with index i of the pylist xs with the value v.

This function takes constant time.

> (define xs (pylist 0 1 2 3 4))
> (pylist-set! xs 1 #t)
> xs

(obj "list" : [0, True, 2, 3, 4])

procedure

(list->pylist xs)  pylist?

  xs : list?
Returns a pylist with the same length and (possibly converted) elements as xs.
The elements are converted with racket->python.

> (list->pylist '(1 2 3 4))

(obj "list" : [1, 2, 3, 4])

> (list->pylist '(1 "foo" #(3 4)))

(obj "list" : [1, 'foo', (3, 4)])

> (list->pylist '(1 (2 3) #(4 (5 6))))

(obj "list" : [1, [2, 3], (4, [5, 6])])

procedure

(vector->pylist xs)  pylist?

  xs : vector?
Returns a pylist with the same length and (possibly converted) elements as xs.
The elements are converted with racket->python.

> (vector->pylist '#(1 2 3 4))

(obj "list" : [1, 2, 3, 4])

> (vector->pylist '#(1 "foo" #(3 4)))

(obj "list" : [1, 'foo', (3, 4)])

> (vector->pylist '#(1 (2 3) #(4 (5 6))))

(obj "list" : [1, [2, 3], (4, [5, 6])])

procedure

(pylist-length xs)  integer?

  xs : pylist?
Returns the length (size) of a pylist (i.e. the number of elements in the pylist).

> (pylist-length (pylist 1 2 3))

3

procedure

(pylist->list xs)  list?

  xs : pylist?
Returns a list with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (pylist->list (pylist 1 2 3 #t #f "a"))

'(1 2 3 #t #f (obj "str" : 'a'))

procedure

(pylist->vector xs)  vector?

  xs : pylist?
Returns a vector with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (pylist->vector (pylist 1 2 3 #t #f "a"))

'#(1 2 3 #t #f (obj "str" : 'a'))

procedure

(pylist->pytuple xs)  pytuple?

  xs : pylist?
Returns a pytuple with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (pylist->pytuple (pylist 1 2 3 #t #f "a"))

(obj "tuple" : (1, 2, 3, True, False, 'a'))

procedure

(in-pylist xs)  stream?

  xs : pylist?
Returns a sequence (that is also a stream) that is equivalent to using xs directly as a sequence.

> (define xs (pylist 0 1 2 3))
> (for/list ([x (in-pylist xs)])
    x)

'(0 1 2 3)

procedure

(pylist-insert! xs i v)  void?

  xs : pylist?
  i : exact-nonnegative-integer?
  v : any/c
Inserts the value v in the pylist xs at index i.

Worst case this function takes time proportional to the size of xs.

> (define xs (pylist 0 1 2 3))
> (pylist-insert! xs 2 #t)
> xs

(obj "list" : [0, 1, True, 2, 3])

procedure

(pylist-append-item! xs v)  void?

  xs : pylist?
  v : any/c
Add the element v to the end of the pylist xs. The length of the pylist becomes 1 greater.

> (define xs (pylist 10 11 12 13))
> (pylist-length xs)

4

> (pylist-append-item! xs 14)
> xs

(obj "list" : [10, 11, 12, 13, 14])

> (pylist-length xs)

5

procedure

(pylist-reverse! xs)  void?

  xs : pylist?
Reverse the order in which the elements in the pylist xs occur.

> (define xs (pylist 1 2 3 4))
> (pylist-reverse! xs)
> xs

(obj "list" : [4, 3, 2, 1])

procedure

(pylist-sort! xs)  void?

  xs : pylist?
Rearrange the order in which the elements in the pylist xs occur. After calling pylist-sort! the elements will be in order with respect to the Python comparison operator <.

> (define xs (pylist 3 2 4 1))
> (pylist-sort! xs)
> xs

(obj "list" : [1, 2, 3, 4])

> (define ys (pylist 3 #t 2 4 #f #f 1))
> (pylist-sort! ys)
> ys

(obj "list" : [False, False, True, 1, 2, 3, 4])

procedure

(pylist-get-slice xs low-index high-index)  pylist?

  xs : pylist?
  low-index : exact-nonnegative-integer?
  high-index : exact-nonnegative-integer?
Returns a new pylist with the elements of xs from index low-index inclusive to index high-index exclusive.

In Python notation: list[low:high].

> (define xs (pylist 1 2 3 #t #f "a"))
> (pylist-get-slice xs 1 3)

(obj "list" : [2, 3])

8.2 Python Tuples - pytuple🔗ℹ

Python tuples correspond to immutable Racket vectors.

Even though there is no datastructure in Racket called "tuple", Python tuples will have the name "pytuple" to match the names of pylist and pydict.

The operations pytuple, list->pytuple and vector->pytuple can be used to construct pytuples from Racket values.

In in for-loops, use in-pytuple to iterate through the elements.

procedure

(pytuple? v)  boolean?

  v : any/c
Returns #t if v is a pytuple (an obj with type "tuple").

> (pytuple 1 2 3)

(obj "tuple" : (1, 2, 3))

> (pytuple? (pytuple 1 2 3))

#t

> (pytuple? (pytuple))

#t

> (pytuple? '(1 2 3))

#f

procedure

(pytuple v ...)  pytuple?

  v : any/c
Returns a newly allocated pytuple containing the vs as its elements.

> (pytuple 1 2 3 4)

(obj "tuple" : (1, 2, 3, 4))

> (pytuple)

(obj "tuple" : ())

> (pytuple (pytuple 1 2 3 4) 5 (pytuple 6 7))

(obj "tuple" : ((1, 2, 3, 4), 5, (6, 7)))

procedure

(pytuple-ref xs i)  any/c

  xs : pytuple?
  i : exact-nonnegative-integer?
Returns the element with index i in the pytuple xs. The first element has index 0, and the last elements is one less than (pytuple-length xs).

This function takes constant time.

> (pytuple-ref (pytuple "a" "b" "c" "d") 1)

"b"

procedure

(list->pytuple xs)  pytuple?

  xs : list?
Returns a pytuple with the same length and (possibly converted) elements as xs.
The elements are converted with racket->python.

> (list->pytuple '(1 2 3 4))

(obj "tuple" : (1, 2, 3, 4))

> (list->pytuple '(1 "foo" #(3 4)))

(obj "tuple" : (1, 'foo', (3, 4)))

> (list->pytuple '(1 (2 3) #(4 (5 6))))

(obj "tuple" : (1, [2, 3], (4, [5, 6])))

procedure

(vector->pytuple xs)  pytuple?

  xs : vector?
Returns a pytuple with the same length and (possibly converted) elements as xs.
The elements are converted with racket->python.

> (vector->pytuple '#(1 2 3 4))

(obj "tuple" : (1, 2, 3, 4))

> (vector->pytuple '#(1 "foo" #(3 4)))

(obj "tuple" : (1, 'foo', (3, 4)))

> (vector->pytuple '#(1 (2 3) #(4 (5 6))))

(obj "tuple" : (1, [2, 3], (4, [5, 6])))

procedure

(pytuple-length xs)  integer?

  xs : pytuple?
Returns the length (size) of a pytuple (i.e. the number of elements in the pytuple).

> (pytuple-length (pytuple 1 2 3))

3

procedure

(pytuple->list xs)  list?

  xs : pytuple?
Returns a list with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (pytuple->list (pytuple 1 2 3 #t #f "a"))

'(1 2 3 #t #f (obj "str" : 'a'))

procedure

(pytuple->vector xs)  vector?

  xs : pytuple?
Returns a vector with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (pytuple->vector (pytuple 1 2 3 #t #f "a"))

'#(1 2 3 #t #f (obj "str" : 'a'))

procedure

(pytuple->immutable-vector xs)  vector?

  xs : pytuple?
Returns an immutable vector with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (define xs (pytuple->immutable-vector (pytuple 1 2 3 #t #f "a")))
> xs

'#(1 2 3 #t #f (obj "str" : 'a'))

> (immutable? xs)

#t

procedure

(pytuple->pylist xs)  pylist?

  xs : pytuple?
Returns a pylist with the same length and elements as xs.

This function takes time proportional to the size of xs.

> (pytuple->pylist (pytuple 1 2 3 #t #f "a"))

(obj "list" : [1, 2, 3, True, False, 'a'])

procedure

(in-pytuple xs)  stream?

  xs : pylist?
Returns a sequence (that is also a stream) that is equivalent to using xs directly as a sequence.

> (define xs (pytuple 0 1 2 3))
> (for/list ([x (in-pytuple xs)])
    x)

'(0 1 2 3)

procedure

(pytuple-get-slice xs low-index high-index)  pytuple?

  xs : pytuple?
  low-index : exact-nonnegative-integer?
  high-index : exact-nonnegative-integer?
Returns a new pytuple with the elements of xs from index low-index inclusive to index high-index exclusive.

In Python notation: list[low:high].

> (define xs (pytuple 1 2 3 #t #f "a"))
> (pytuple-get-slice xs 1 3)

(obj "tuple" : (2, 3))

8.3 Python Dictionaries - pydict🔗ℹ

Dictionaries in Python are associative arrays indexed by keys. Given a key one can lookup a value. Think of dictionaries as sets of key/value pairs.

Any immutable value can be used as a key, so strings, numbers and tuples can always be used as keys.

The corresponding data structure in Racket is the hash table.

Use the operations pydict->hash and hash->pydict to convert back and forth between pydicts and hash tables.

procedure

(pydict? v)  boolean?

  v : any/c
Returns #t if v is a pydict (an obj with type "dict").

> (pydict? (hash->pydict (hash "a" 1  "b" 2)))

#t

procedure

(hash->pydict x [#:convert convert])  pydict?

  x : hash?
  convert : procedure? = rp
Returns a newly allocated pydict with the same key/value-pairs as the hash table x. The function convert is used to convert Racket values to Python ones. The default value for the keyword argument convert is rp.

> (hash->pydict (hash "a" 1  "b" 2))

(obj "dict" : {'b': 2, 'a': 1})

> (hash->pydict (hash 1 "x"  2 "y"))

(obj "dict" : {1: 'x', 2: 'y'})

> (hash->pydict (hash #(1 2) "tuple used as key"))

(obj "dict" : {(1, 2): 'tuple used as key'})

procedure

(pydict->hash x    
  [#:convert-key convert-key    
  #:convert-value convert-value])  hash?
  x : pydict?
  convert-key : procedure? = pr/key
  convert-value : procedure? = pr
Returns a newly allocated hash table with the same key/value-pairs as the pydict x.

The function convert-key is used to convert the keys from Python values to Racket ones.
The function convert-value is used to convert the values.

The default value for the key conversion is pr/key.
The default value for the value conversion is pr.

> (pydict->hash (hash->pydict (hash "a" 1  "b" 2)))

'#hash(("a" . 1) ("b" . 2))

> (pydict->hash (hash->pydict (hash 1 "x"  2 "y")))

'#hash((1 . (obj "str" : 'x')) (2 . (obj "str" : 'y')))

> (pydict->hash (hash->pydict (hash #(1 2) "tuple used as key")))

'#hash((#(1 2) . (obj "str" : 'tuple used as key')))

procedure

(pydict [#:convert convert] key val ... ...)  pydict?

  convert : procedure? = rp
  key : any/c
  val : any/c
Creates a pydict with each given key mapped to the following val; each key must have a val, so the total number of arguments to hash must be even.

The key to val mappings are added to the table in the order that they appear in the argument list, so later mappings can hide earlier mappings if the keys are equal.

The default value for the key and value conversion is rp.

> (pydict "a" 1  "b" 2)

(obj "dict" : {'a': 1, 'b': 2})

> (pydict 1 "x"  2 "y")

(obj "dict" : {1: 'x', 2: 'y'})

> (pydict #(1 2) "tuple used as key")

(obj "dict" : {(1, 2): 'tuple used as key'})

procedure

(pydict-ref d key [failure-result])  any/c

  d : pydict?
  key : any/c
  failure-result : failure-result/c
   = 
(lambda ()
  (raise (make-exn:fail:contract ....)))
Returns the value for key in hash. If no value is found for key, then failure-result determines the result:

If failure-result is a procedure, it is called (through a tail call) with no arguments to produce the result.

Otherwise, failure-result is returned as the result.

> (define d (pydict "a" 1  "b" 2))
> d

(obj "dict" : {'a': 1, 'b': 2})

> (pydict-ref d "a")

1

> (pydict-ref d "b")

2

> (pydict-ref d "c")

pydict-ref: no value found for key

  key: "c"

procedure

(pydict-set! d key val)  void?

  d : pydict?
  key : any/c
  val : any/c
Maps key to val in d, overwriting any existing mapping for key.

> (define d (pydict "a" 1))
> (pydict-set! d "a" 11)
> (pydict-set! d "b" 22)
> d

(obj "dict" : {'a': 11, 'b': 22})

procedure

(pydict-remove! d key)  void?

  d : pydict?
  key : any/c
Removes any existing mapping for key in the pydict d.

> (define d (pydict "a" 1 "b" 2))
> (pydict-remove! d "b")
> (pydict-remove! d "c")
> d

(obj "dict" : {'a': 1})

procedure

(pydict-clear! d)  void?

  d : pydict?
Remove all key/value pairs from the dictionary.

> (define d (pydict "a" 1  "b" 2))
> d

(obj "dict" : {'a': 1, 'b': 2})

> (pydict-clear! d)
> d

(obj "dict" : {})

procedure

(pydict-contains? d key)  boolean?

  d : pydict?
  key : any/c
Returns #t if the pydict x contains the key key.

> (define d (pydict "a" 1  "b" 2))
> (pydict-contains? d "a")

#t

> (pydict-contains? d "x")

#f

procedure

(pydict-copy d)  pydict?

  d : pydict?
Return a new dict with the same key/value pairings as the pydict d.

> (define d1 (pydict "a" 1  "b" 2))
> (define d2 (pydict-copy d1))
> (list d1 d2)

'((obj "dict" : {'a': 1, 'b': 2}) (obj "dict" : {'a': 1, 'b': 2}))

> (pydict-set! d1 "a" 11)
> (list d1 d2)

'((obj "dict" : {'a': 11, 'b': 2}) (obj "dict" : {'a': 1, 'b': 2}))

procedure

(pydict-keys d)  pylist?

  d : pydict?
Return a pylist with all keys in the pydict d.

> (define d (pydict "a" 1  "b" 2))
> (pydict-keys d)

(obj "list" : ['a', 'b'])

procedure

(pydict-values d)  pylist?

  d : pydict?
Return a pylist with all values in the pydict d.

> (define d (pydict "a" 1  "b" 2 "c" 1))
> (pydict-values d)

(obj "list" : [1, 2, 1])

procedure

(pydict-count d)  integer?

  d : pydict?
Returns the number of keys mapped by the pydict hash.

> (define d (pydict "a" 1  "b" 2 "c" 1))
> (pydict-count d)

3

procedure

(pydict-merge! d1 d2 [override?])  void?

  d1 : pydict?
  d2 : pydict?
  override? : boolean? = #t
Computes the union of d1 with the d2 by mutable update, adding each element of d2 to d1 in turn.

If a key k is present in both pydicts and is mapped to values v1 and v2 in d1 and d2 respectively, then k is mapped to v2 if override? is true, and mapped to v1 otherwise.

> (define d1 (pydict "a" 1  "b" 2))
> (define d2 (pydict "b" 22 "c" 33))
> (pydict-merge! d1 d2)
> d1

(obj "dict" : {'a': 1, 'b': 22, 'c': 33})

> (define d1 (pydict "a" 1  "b" 2))
> (define d2 (pydict "b" 22 "c" 33))
> (pydict-merge! d1 d2 #f)
> d1

(obj "dict" : {'a': 1, 'b': 2, 'c': 33})

procedure

(in-pydict d)  stream?

  d : pydict?
Returns a sequence (that is also a stream) equivalent to d directly as a sequence.

> (define d (pydict "a" 1 "b" 2))
> (for/list ([(key value) (in-pydict d)])
    (list key value))

'(((obj "str" : 'a') 1) ((obj "str" : 'b') 2))

8.4 Python Strings - pystring🔗ℹ

Strings in Python are represented as objects with type str. Python strings are immutable sequences of Unicode code points.

There is no character type in Python, so various Python libraries use strings of length 1 to represent characters.

procedure

(pystring? v)  boolean?

  v : any/c
Returns #t if v is a pystring (an obj with type "str").

> (string->pystring "foo")

(obj "str" : 'foo')

> (pystring? (string->pystring "foo"))

#t

procedure

(pystring char ...)  pystring?

  char : char?
Returns a new pystring whose length is the number of provided chars, and whose positions are initialized with the given chars.

> (pystring #\f #\o #\o)

(obj "str" : 'foo')

procedure

(string->pystring x)  pystring?

  x : string?
Return a newly allocated pystring with the same characters as the string x.

> (string->pystring "foo")

(obj "str" : 'foo')

procedure

(pystring->string x)  string?

  x : pystring?
Return a newly allocated string with the same characters as the pystring x.

> (pystring->string (pystring #\f #\o #\o))

"foo"

procedure

(pystring-length x)  integer?

  x : pystring?
Returns the length of x (i.e. the number of characters in string).

> (pystring-length (pystring #\f #\o #\o))

3

procedure

(pystring-ref x k)  char?

  x : pystring?
  k : exact-nonnegative-integer?
Returns the character at position k in the pystring x. The first position in the string corresponds to 0, so the position k must be less than the length of the string, otherwise the exception exn:fail:contract is raised.

> (define foo (pystring #\f #\o #\o))
> (pystring-ref foo 0)

#\f

> (pystring-ref foo 10)

pystring-ref: index 10 out of range for the string "foo"

procedure

(subpystring x start [end])  pystring?

  x : pystring?
  start : exact-nonnegative-integer?
  end : exact-nonnegative-integer? = (pystring-length x)
Returns a pystring that is (- end start) characters long, and that contains the same characters as x from start inclusive to end exclusive.

The first position in a string corresponds to 0, so the start and end arguments must be less than or equal to the length of x, and end must be greater than or equal to start, otherwise the exception exn:fail:contract is raised.

> (subpystring (string->pystring "foobarbaz") 3 6)

(obj "str" : 'bar')

procedure

(in-pystring x)  stream?

  x : pystring
Returns a sequence (that is also a stream) that is equivalent to using x directly as a sequence.

> (define x (string->pystring "foo"))
> (for/list ([c (in-pystring x)])
    c)

'(#\f #\o #\o)

8.5 Python Generators🔗ℹ

Python generators correspond to Racket generators from racket/generator. Think of a generator as a function that can produce a series of values.

If the body of Python function contains a yield statement, calling the function returns a generator object. Use next repeatedly on the generator to generate the series of values.

The yield expr statement both "pauses" the computation of the generator and returns the result of evaluating the expression. The computation is resumed by next.

This example shows a generator that produces the natural numbers.

 (run*  @~a{def f():

                  x=0

                  while 1:

                    x=x+1

                  yield x}

(let ([g (main.f)])

  (list (next g) (next g) (next g))) )

This produces the list (list 1 2 3).

Usually the most convenient way of using such a generator is to use in-pygenerator.

procedure

(in-pygenerator pygen)  stream?

  pygen : pygenerator?
Returns a sequence (that is also a stream) that produces the elements from pygen.

 (let ([g (main.f)])

   (for ([_ 3]

         [x (in-pygenerator g)])

     x))

Generators are automatically wrapped in an generator-obj struct, which has obj as super type. The wrapping allows us to make the in-generator implicit in for loops.

 (let ([g (main.f)])

   (for ([_ 3] [x g])

     x))

Index🔗ℹ

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

 

add-initialization-thunk
An introduction to pyffi
Atomic values: numbers, Booleans and None
Builtin functions and modules
Compound Values: strings, tuples, lists, and, dictionaries
Datatypes
diagnostics
Evaluation
Exceptions
hash->pydict
in-pydict
in-pygenerator
in-pylist
in-pystring
in-pytuple
Initialization of the Python Interpreter
initialize
Installation
Introduction
list->pylist
list->pytuple
Objects, Callable objects, Functions, Methods and Properties
One-time Configuration
Optional: NumPy for scientific computing
post-initialize
pydict
pydict->hash
pydict-clear!
pydict-contains?
pydict-copy
pydict-count
pydict-keys
pydict-merge!
pydict-ref
pydict-remove!
pydict-set!
pydict-values
pydict?
pyffi
pyffi - Use Python from Racket
pyfirst
pylist
pylist->list
pylist->pytuple
pylist->vector
pylist-append-item!
pylist-get-slice
pylist-insert!
pylist-length
pylist-ref
pylist-reverse!
pylist-set!
pylist-sort!
pylist?
pysecond
pystring
pystring->string
pystring-length
pystring-ref
pystring?
Python 3.1x
Python Dictionaries - pydict
Python Generators
Python Lists - pylist
Python Strings - pystring
Python Tuples - pytuple
pytuple
pytuple->immutable-vector
pytuple->list
pytuple->pylist
pytuple->vector
pytuple-get-slice
pytuple-length
pytuple-ref
pytuple?
Reference
Release Information
run
run*
string->pystring
subpystring
Supported Platforms
The Big Picture
vector->pylist
vector->pytuple