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
> (pylist 1 2 3) (obj "list" : [1, 2, 3])
> (pylist? (pylist 1 2 3)) #t
> (pylist? (pylist)) #t
> (pylist? '(1 2 3)) #f
> (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?
This function takes constant time.
> (pylist-ref (pylist "a" "b" "c" "d") 1) "b"
> (pyfirst (pylist "a" "b" "c" "d")) "a"
> (pysecond (pylist "a" "b" "c" "d")) "b"
procedure
(pylist-set! xs i v) → any/c
xs : pylist? i : exact-nonnegative-integer? v : any/c
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?
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?
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?
> (pylist-length (pylist 1 2 3)) 3
procedure
(pylist->list xs) → list?
xs : pylist?
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?
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?
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'))
> (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
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
> (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?
> (define xs (pylist 1 2 3 4)) > (pylist-reverse! xs) > xs (obj "list" : [4, 3, 2, 1])
procedure
(pylist-sort! xs) → void?
xs : pylist?
> (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?
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 "putuple" 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
> (pytuple 1 2 3) (obj "tuple" : (1, 2, 3))
> (pytuple? (pytuple 1 2 3)) #t
> (pytuple? (pytuple)) #t
> (pytuple? '(1 2 3)) #f
> (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?
This function takes constant time.
> (pytuple-ref (pytuple "a" "b" "c" "d") 1) "b"
procedure
(list->pytuple xs) → pytuple?
xs : list?
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?
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?
> (pytuple-length (pytuple 1 2 3)) 3
procedure
(pytuple->list xs) → list?
xs : pytuple?
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?
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?
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?
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?
> (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?
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
> (pydict? (hash->pydict (hash "a" 1 "b" 2))) #t
procedure
(hash->pydict x [#:convert convert]) → pydict?
x : hash? convert : procedure? = 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
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
convert : procedure? = rp key : any/c val : any/c
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 ....)))
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
> (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
> (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?
> (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) → void?
d : pydict? key : any/c
> (define d (pydict "a" 1 "b" 2)) > (pydict-contains? d "a") #t
> (pydict-contains? d "x") #f
procedure
(pydict-copy d) → pydict?
d : pydict?
> (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?
> (define d (pydict "a" 1 "b" 2)) > (pydict-keys d) (obj "list" : ['a', 'b'])
procedure
(pydict-values d) → pylist?
d : pydict?
> (define d (pydict "a" 1 "b" 2 "c" 1)) > (pydict-values d) (obj "list" : [1, 2, 1])
procedure
(pydict-count d) → integer?
d : pydict?
> (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
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})
> (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
> (string->pystring "foo") (obj "str" : 'foo')
> (pystring? (string->pystring "foo")) #t
> (pystring #\f #\o #\o) (obj "str" : 'foo')
procedure
(string->pystring x) → pystring?
x : string?
> (string->pystring "foo") (obj "str" : 'foo')
procedure
(pystring->string x) → string?
x : pystring?
> (pystring->string (pystring #\f #\o #\o)) "foo"
procedure
(pystring-length x) → integer?
x : pystring?
> (pystring-length (pystring #\f #\o #\o)) 3
procedure
(pystring-ref x k) → char?
x : pystring? k : exact-nonnegative-integer?
> (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)
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
> (define x (string->pystring "foo"))
> (for/list ([c (in-pystring x)]) c) '(#\f #\o #\o)
8.5 Python Generators
Python generator 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?
(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)) |