Category Archives: Python

Coconut – operatori II

1892_5

Ci sono argomenti istituzionali come Octave (sì siamo in ritardo ma il caldo, le ferie, …) e ci sono argomenti che forse poi –chissà– come Coconut. Poi ce ne sono anche altri ancora, come SICP, che meriterebbero più attenzione. Avendo tempo. OK, oggi Coconut, continuo da qui a copiare qui.

Iterator Slicing
Coconut uses a $ sign right after an iterator before a slice to perform iterator slicing. Coconut’s iterator slicing works much the same as Python’s sequence slicing, and looks much the same as Coconut’s partial application, but with brackets instead of parentheses. It has the same precedence as subscription.
Iterator slicing works just like sequence slicing, including support for negative indices and slices, and support for slice objects in the same way as can be done with normal slicing. Iterator slicing makes no guarantee, however, that the original iterator passed to it be preserved (to preserve the iterator, use Coconut’s tee function).
Coconut’s iterator slicing is very similar to Python’s itertools.islice, but unlike itertools.islice, Coconut’s iterator slicing supports negative indices, and is optimized to play nicely with custom or built-in sequence types as well as Coconut’s map, zip, range, and count objects, only computing the elements of each that are actually necessary to extract the desired slice. This behavior can also be extended to custom objects if they define their __getitem__ method lazily and set __coconut_is_lazy__ to True.

c24

Nota: il manuale usa 10**100 ma mi da overflow😳

With Python can’t be done without a complicated iterator slicing function and inspection of custom objects. The necessary definitions in Python can be found in the Coconut header.

Alternative Unicode
Coconut supports Unicode alternatives to many different operator symbols. The Unicode alternatives are relatively straightforward, and chosen to reflect the look and/or meaning of the original symbol.

→ (\u2192)                  => "->"
↦ (\u21a6)                  => "|>"
*↦ (*\u21a6)                => "|*>"
↤ (\u21a4)                  => "<|" 
↤* (\u21a4*)                => "<*|" 
⋅ (\u22c5)                  => "*"
↑ (\u2191)                  => "**"
÷ (\xf7)                    => "/"
÷/ (\xf7/)                  => "//"
∘ (\u2218)                  => ".."
− (\u2212)                  => "-" (only subtraction)
⁻ (\u207b)                  => "-" (only negation)
¬ (\xac)                    => "~"
≠ (\u2260) or ¬= (\xac=)    => "!="
≤ (\u2264)                  => "<=" 
≥ (\u2265)                  => ">="
∧ (\u2227) or ∩ (\u2229)    => "&"
∨ (\u2228) or ∪ (\u222a)    => "|"
⊻ (\u22bb) or ⊕ (\u2295)    => "^"
« (\xab)                    => "<<" 
» (\xbb)                    => ">>"
… (\u2026)                  => "..."
× (\xd7)                    => "@" (only matrix multiplication)

c25

OK, ma forse –sono vecchio– perché cambiare. E poi non è comodissimo introdurre il codice dopo C-S-u. Non sono riuscito a usare quelli con il codice esadecimale, per esempio xf7.

Keywords

data
The syntax for data blocks is a cross between the syntax for functions and the syntax for classes. The first line looks like a function definition, but the rest of the body looks like a class, usually containing method definitions. This is because while data blocks actually end up as classes in Python, Coconut automatically creates a special, immutable constructor based on the given arguments.

Coconut data blocks create immutable classes derived from collections.namedtuple and made immutable with __slots__. Coconut data statement syntax looks like:

data <name>(<args>):
    <body>

<name> is the name of the new data type, <args> are the arguments to its constructor as well as the names of its attributes, and <body> contains the data type’s methods.Subclassing data types can be done easily by inheriting from them in a normal Python class, although to make the new subclass immutable, the line

__slots__ = ()

Rationale
A mainstay of functional programming that Coconut improves in Python is the use of values, or immutable data types. Immutable data can be very useful because it guarantees that once you have some data it won’t change, but in Python creating custom immutable data types is difficult. Coconut makes it very easy by providing data blocks.

Python Docs
Returns a new tuple subclass. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable. Instances of the subclass also have a helpful docstring (with type names and field names) and a helpful __repr__() method which lists the tuple contents in a name=value format.

Any valid Python identifier may be used for a field name except for names starting with an underscore. Valid identifiers consist of letters, digits, and underscores but do not start with a digit or underscore and cannot be a keyword such as class, for, return, global, pass, or raise.

Named tuple instances do not have per-instance dictionaries, so they are lightweight and require no more memory than regular tuples.

c26

This will fail because data objects are immutable

c27

la versione Python

c28

Showcases the syntax, features, and immutable nature of data types.

c29

Nota: la riga dopo il decoratore viene scritta in modo bizzarro nella REPL, quella corretta è def size(Node(left, right)) = size(left) + size(right) ovviamente.

la versione Python

c30

Pausa, devo digerire il tutto; poi continuo sempre sulle keywords😀
:mrgreen:

pdb – debugger per Python

tapacolo

Doug Hellmann (che trovate qui e qui e forse anche da altre parti) mi dice che c’è pdb, debugger interattivo per Python, cosa che non sapevo.

E pensare che il modulo arriva preinstallato con Python (sia 2.7 che 3.x). Vero che:

This is currently undocumented but easily understood by reading the source.

Siccome Doug spiega tutto per bene potrei chiudere qui ma il post sarebbe troppo corto😳 e allora metto giù un paio di screenshots, quelli delle mie prove iniziali.
Finora mi sono limitato a mettere dei print dove verosimilmente dovrebbero esserci breakpoints: entrata delle finzioni che sto testando, valori che ritornano e così via.
Con i debuggers ho passato parecchio tempo, prima su Prime poi su Apollo e Unix. Finché ho scoperto la meraviglia dell’ambiente integrato (inizialmente con Turbo Pascal 3.0) e poi con i vari Basic di Microsoft.

Ma nel frattempo avevo visto che conveniva –di molto– scrivere in modo da minimizzare la necessità di ricorrere al debug; anche perché non sempre c’è (o non so che c’è). Che è poi quello che dice Daniel Lemire: I do not use a debugger.

Ma mai dire mai, un tool in più che può sempre servire.
A me piace partire al volo con l’equivalente di “Hello World!” che per il debug è qualcosa di simile (d0.py):

#!/usr/bin/python3

m = 7
n = 5
s = m + n
print("somma", m, n , s)
d = m - n
r = m / n
ri = m // n
print("- & /", d, r, ri)

d0

Il comando w (where) visualizza la linea corrente, per il codice si usa l (list), q per quit.

d1

Hey! mi sembra di essere tornato indietro di 35 anni, il comando s (step) e la sessione è com’era su Prime; vero che allora dovevi compilare con l’opzione -d e linkare forse con una libreria apposita (non ricordo, è passato un po’ di tempo).

Dentro all’interprete ha senso solo per i moduli, uso l’esempio di Doug (ddh.py):

#!/usr/bin/env python3
# encoding: utf-8
#
# Copyright (c) 2010 Doug Hellmann.  All rights reserved.
#

class MyObj:

    def __init__(self, num_loops):
        self.count = num_loops

    def go(self):
        for i in range(self.num_loops):
            print(i)
        return

d2

e così via.

OK, adesso a fare le cose come si deve dovrei fare l’elenco dei comandi, far vedere come settare un breakpoint, andarci, esaminare lo stack e roba simile. Una cosa tremendamente poco sexy che spero davvero di non dover fare mai. Molto diversa dall’IDE del Borland Pascal (e simili)😀

:mrgreen:

Coconut – 2 – intermezzo per qualche riflessione

Wiz

Non ho ancora deciso se Coconut diventerà uno dei miei linguaggi; devo approfondire.
Ma sospendo un attimo –da qui— per qualche considerazione –banale lo so!– su come comportarmi.

c13

Nella REPL di Coconut si possono dare i comandi sia con la sua propria sintassi che con quella di Python. Può essere comodo😀

c14

Con Python (versione 2.x che è comunque quella usata da Coconut) sono più limitato; notare p.es. la print senza importarla dal futuro. Per contro è molto più veloce.

c15-0

IPython (a proposito ho creato l’alias per risparmiare i diti):

c15-1

Mi accorgo che devo approfondire, chissà …

Finora ho trattato comandi semplici; con le funzioni ho

c16

notare che si devono inserire gli spazi richiesti da Python; anche la riga vuota per terminare la funzione è richiesta.

c17

Stessa cosa con IPython.

c18

Coconut fa le bizze con funzioni Python

c19

le vuole con la sua sintassi. Ed è più lento.

Basta, ho deciso che devo vedere bene IPyton; pausa😀

:mrgreen:

Coconut – 1 – prime prove con un case study

sg4

Continuo l’esame di Coconut, iniziato qui, per vedere se può essere utile per estendere Python, premettendo che è tutto da un punto di vista strettamente personale. Oggi qualche case study, sempre dal tutorial.

Fattoriale

In the first case study we will be defining a factorial function, that is, a function that computes n! where n is an integer >= 0.
To start off with, we’re going to have to decide what sort of an implementation of factorial we want. There are many different ways to tackle this problem, but for the sake of concision we’ll split them into four major categories: imperative, recursive, iterative, and addpattern.

Metodo imperativo

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    if n `isinstance` int and n >= 0:
        acc = 1
        for x in range(1, n+1):
            acc *= x
        return acc
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
-1 |> factorial |> print # TypeError
0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6

c6

OOPS! non come volevo, provo dentro IPython:

c7

No, continua a non funzionare. Intanto ho dovuto cambiare il nome al file, il carattere – viene interpretato. Ma resta l’errore sulla metaclasse. Solo per verifica provo a togliere dal codice .coco e casi che danno errore e ricompilare:

def factorial(n):
    """Compute n! where n is an integer >= 0."""
    if n `isinstance` int and n >= 0:
        acc = 1
        for x in range(1, n+1):
            acc *= x
        return acc
    else:
        raise TypeError("the argument to factorial must be an integer >= 0")

# Test cases:
### -1 |> factorial |> print # TypeError
### 0.5 |> factorial |> print # TypeError
0 |> factorial |> print # 1
3 |> factorial |> print # 6

c8

Sì ma così non ha senso; supponiamo che il parametro a factorial sia dato interattivamente c’è il crash. Insomma no😦

Metodo ricorsivo
Continuo ad avere errori; salto al prossimo case study: quick_sort.

Case study 2: quick_sort

Sortare una sequenza

def quick_sort([]):
    return []

@addpattern(quick_sort)
def quick_sort([head] + tail):
    """Sort the input sequence using the quick sort algorithm."""
    return (quick_sort([x for x in tail if x < head])         + [head]         + quick_sort([x for x in tail if x >= head]))

# Test cases:
[] |> quick_sort |> print 
[3] |> quick_sort |> print 
[0,1,2,3,4] |> quick_sort |> print 
[4,3,2,1,0] |> quick_sort |> print 
[3,0,4,2,1] |> quick_sort |> print

c9

OK, provo a compilare…

c10

OK, dentro IPython ho ***errori*** devo approfondire come si usa.

Sortare un iteratore

def quick_sort(l):
    """Sort the input iterator, using the quick sort algorithm, and
       without using any data until necessary."""
    match [head] :: tail in l:
        tail, tail_ = tee(tail)
        yield from (quick_sort((x for x in tail if x < head))             :: (head,)             :: quick_sort((x for x in tail_ if x >= head))
            )

# Test cases:
[] |> quick_sort |> list |> print 
[3] |> quick_sort |> list |> print 
[0,1,2,3,4] |> quick_sort |> list |> print 
[4,3,2,1,0] |> quick_sort |> list |> print 
[3,0,4,2,1] |> quick_sort |> list |> print

c11

e compilando:

c12

Pausa😉 perplesso😳 devo vedere cli altri casi prima di giungere a una conclusione:mrgreen:

Coconut – 0 – e se…

banner

E se Coconut riesce a estendere Python come dice…
Da provare, installo

c0

e provo subito, seguendo le dritte di Coconut Tutorial:

c1


OK
, c’è; avanti😀

Usare il compilatore

Of course, while being able to interpret Coconut code on-the-fly is a great thing, it wouldn’t be very useful without the ability to write and compile larger programs. To that end, it’s time to write our first Coconut program: “hello, world!” Coconut-style.

First, we’re going to need to create a file to put our code into. The recommended file extension for Coconut source files is .coco, so let’s create the new file hello_world.coco.

Prima sorpresa: non serve la shebang, non servono gli usuali soliti import e la sintassi è (come già visto nel test sopra) un po’ diversa, insomma ecco hello_world.coco:

"hello, world!" |> print

compilo:

c2

OK, produce hello_world.py decisamente più lungo di quel che mi aspettavo.
C’è la shebang, gli import, tante definizioni di funzioni e alla fine

# Compiled Coconut: ------------------------------------------------------------

(print)("hello, world!")

che si può lanciare:

c3

Usare IPython o Jupyter

uhmmm…
Non ho nessuno dei due installati; prima o poi Jupyter potrebbe tentarmi, per intanto inizio con IPython.

c4

e poi …

c5

OK, lento ma è un caso scemo; poi sono niubbassay😳

nome

If you don’t get the reference, the image above is from Monty Python and the Holy Grail, in which the Knights of the Round Table bang Coconuts together to mimic the sound of riding a horse. The name was chosen to reference the fact that Python is named after Monty Python as well.

evan

Chi è stato?

Evan Hubinger is an undergraduate student studying mathematics and computer science at Harvey Mudd College. Fa anche altre cose, tante, rockz😀

Pausa, ma devo continuare a indagare.

:mrgreen:

Dov’è? ovvero differenze con Windows

n2Ieri ho scoperto che un piccolo tool che avevo scritto nel 2006 e completamente dimenticato è ancora vivo e usato😀

w1
È una cosa semplicissima, rileva la lista dei dischi montati (quelli con una lettera seguita da due-punti) e restituisce quello che nella radice ha il file con il nome specificato. Siccome allora ero giovane l’avevo scritto in Delphi e dev’essere stata una cosa volante, l’ho completamente dimenticato. Fino a ieri.

Ora per cose normali con Linux c’è whereis:

w2

che potrebbe anche essere configurato per casi speciali:

w3

Cose che con Windows sono (o erano?) possibili qui sono giustamente vietate, per elementari ragioni di sicurezza:

w4

Per il resto non dovrebbero esserci problemi

w5

e il nome dell’utente corrente può essere rilevato con uno di questi due modi (o altri ancora, forse)

w6

Programmatically (or scrptically), disks.py:

import os

mdir = "/media/" + os.getenv("USER")
print("mdir =", mdir)
disks = os.listdir(mdir)
print("disks =", disks)

w7

:mrgreen:

Python – la concorrenza resa davvero facile con i decoratori

CYd

Ieri ho pubblicato un post dicendo che non sono capace a raccontare certe cose e, inaspettatamente almeno per me, ha avuto un pakko di visite.
Allora mi viene la voglia di insistere, chissà se anche oggi…😉
Anche perché serendipicamente, dallo stesso ambiente del post di ieri, mi arriva una segnalazione davvero sexy invitante😀 Ovviamente su qualcosa di cui più volte ho detto cose diverse da quelle di oggi, cambio idea, mi aggiorno😀

Viene tutto da Decorated Concurrency – Python multiprocessing made really really easy sul blog di Peter Bengtsson, è un blog da seguire, imho.

Sì, finora dicevo di lasciar perdere i decoratori, poco intuitivi e difficilissimi da fare. Ma poi capita questo post che illustra un caso realistico reso facilissimo (davvero facilissimissimo)😀
Nel post trovate la fonte e la documentazione, tutto OK.
Non mi resta che provarlo.

Per Ubuntu occorre installare il package deco con

sudo pip3 install deco

Ma probabilmente pip3 non è installato e allora

sudo apt-get install python3-pip

OK, tutto pronto, la versione non concorrente dell’esempio di Peter (nc.py):

import time

def slow(index):
    time.sleep(5)

def run():
    for index in list('123'):
        slow(index)
run()

nc

sì, troppo semplice, non si vede il consumo di CPU.

Ecco la versione concorrente (cd.py):

import time
from deco import concurrent, synchronized

@concurrent
def slow(index):
    time.sleep(5)

@synchronized
def run():
    for index in list('123'):
        slow(index)

run()

cd

OK, visto? tutto come previsto.

Cosa succede se i processi sono tanti, più delle CPU presenti? (tcd.py):

import time
from deco import concurrent, synchronized

@concurrent
def slow(index):
    time.sleep(5)

@synchronized
def run():
    for index in list('123456789'):
        slow(index)

run()

tcd

ovvio; conviene visualizzare il thread attivo con una piccola modifica (tcdp.py):

import time
from deco import concurrent, synchronized

@concurrent
def slow(index):
    print(index)
    time.sleep(5)

@synchronized
def run():
    for index in list('123456789'):
        slow(index)

run()

tcdp

ecco le scritte compaiono a gruppi di 4 (per 4 CPU); si dall’immagine non si vede ma provate, dai😉

Quindi, conclusione personale, a volte servono anche i decoratori, non vale quanto detto in precedenza.

:mrgreen:

Il test di Jake

past

Cosa non si trova a seguire le persone giuste su Twitter; prendi Jake VanderPlas, un über-nerd che lascio si definisca da solo, paura di dimenticare qualcosa. Oggi twitta una cosa su Python (chi ha detto che è il nuovo Basic, semplice-semplice?). Il test è troppo bello per non riportarlo qui sul blog, senza spiegare il perché perché non ne sono capace voglio lasciare a voi il piacere di trovare la soluzione😉

Poi chi è vecchio –come me– si ricorderà di tanti casi in cui cambiando compilatore (o versione dello stesso) le cose cambiavano. If it’s working tuca nen! come da detto inglese: if it ain’t broke, don’t fix it.

OK, tornando a Jake ecco il cinguettio.

Da provare subito:

class A: 
    def b(self): 
        pass 

if A.b is A.b: 
    print("Python 3") 
else: 
    print("Python 2")

j0

OK, come previsto. Notare che l’interprete Python guarda anche dove non deve, approfittando del cambiamento di print che nel 2.x è un’istruzione e nel 3.x una funzione faccio una piccola modifica, supposta innocente:

class A: 
    def b(self): 
        pass 

if A.b is A.b: 
    print("Python 3") 
else: 
    print "Python 2"

j1

Uh! ma mica finisce qui:

class A: 
    def b(self): 
        print("hi!")

if A.b is A.b: 
    print("Python 3")
    print(A.b) 
    A.b(True)
else: 
    print("Python 2")
    print(A.b) 
    A.b(True)

j2

Quasi panico!😳 Cose che capitano.

Io intanto torno con le operazioni di post-installazione dello Scoiattolo Ospitale di Xenial Xerus.

:mrgreen:

Rust e Python – 2

n3Continuo da qui l’esame della serie di post di TheK3nger sull’uso di Rust all’interno di codice Python e viceversa; oggi in particolare copio da How to use Rust in Python (Part 3).
Inoltre –per comodità– il Git è qui: THeK3nger/rust-python-integration-examples.

[W]e have seen how to pass not trivial data to Rust functions such as a Python list. It is still not enough, though. In many cases we need to pass complex data structure back and forth from a Rust library. We may need to pass quaternions [davvero qualcuno usa quella roba lì?], 3D points, trees, a list of “books”… In short: anything.

Lato Rust

As usual, the first thing to do is to implement the Rust library function. This time we need to design first a struct that will represent our complex object. We chose to represent 2D points.

#[repr(C)]
pub struct Point {
    x: f64,
    y: f64,
}

This is a classic rust struct declaration, but we need to use #[repr(C)] to explain to the compiler that Point is a C struct.

Now, we can simply implement the desired function. In this example we will implement a function that, given two Point  representing a segment, returns the midpoint of the segment.

#[no_mangle]
pub extern fn middle(p1: Point, p2: Point) -> Point {
    Point { x: (p1.x + p2.x)/2.0, y: (p1.y + p2.y)/2.0 }
}

Lato Python

Python side is slightly more complicated. Also in Python, we need first to declare the Point.

class Point(ctypes.Structure):
    _fields_ = [("x", ctypes.c_double), ("y", ctypes.c_double)]
    
    def __str__(self):
        return "Point ({},{})".format(self.x, self.y)

The class representing our C/Rust struct is different from the usual one:

  • The class must extend the ctypes.Structure class defined in ctypes.
  • The fields of the C struct must be defined using the builtin _fields_ attribute. This attribute must contain a list of tuples. Each tuple must contain the 1) name of the field and 2) the type of the field according the ctypes declaration. In our case Point has two fields, “x” and “y”, both doubles.

Now it is time to setup our Rust function. We specify the type declaration for our function.

lib.middle.argtypes = (Point, Point)
lib.middle.restype = Point

Then we can use this function as usual!

p1 = Point(1.0, 5.0)
p2 = Point(1.0, 10.0)

res_point = lib.middle(p1, p2)
print(res_point)

And we have done. It is not so difficult, isn’t it? Certo, semplicissimo, adesso provo, ecco il file rp_points.py:

import ctypes

lib = ctypes.CDLL("./target/release/librustypython.so")

class Point(ctypes.Structure):
    _fields_ = [("x", ctypes.c_double), ("y", ctypes.c_double)]
    
    def __str__(self):
        return "Point ({},{})".format(self.x, self.y)

# Then we specify as usual the type declaration of the Rust function.
lib.middle.argtypes = (Point, Point)
lib.middle.restype = Point

# And then we can easily use it as a native python function!
p1 = Point(1.0, 5.0)
p2 = Point(1.0, 10.0)

res_point = lib.middle(p1, p2)
print(res_point)

k6

OK😀 come già detto TheK3nger rockz!😀 Rust & Python too😀

Devo davvero imparare bene Rust, da usarsi per le operazioni impegnative.

:mrgreen:

Rust e Python – 1

b0

Davide Aversa, TheK3nger ha postato la trilogia How to use Rust in Python, qui part 1, part 2 e part 3.

Ora:

  • Python lo conosco e consiglio;
  • Rust lo sto studiando;
  • TheK3nger rockz😀

e allora rileggendo i post citati faccio gli esercizi proposti😀
Quando ci arriverò nel mio studio di Rust qualcosa di simile è trattato nella guida in Foreign Function Interface [/usr/local/share/doc/rust/html/book/ffi.html] ma usa il C come altro linguaggio. Per me Python è più usato e allora benvenuto a questi post k3ngerosi😉

Python è lento, Rust è molto promettente e allora, Rust is the perfect language for embedding fast-binary libraries in Python!

Anche perché [w]riting Rust code that can be executed in Python is stupidly easy.

Partiamo con Rust e Cargo:

k0

In lib.rs inseriamo il seguente codice:

use std::thread;

fn process() {
    let handles: Vec = (0..10).map(|_| {
        thread::spawn(|| {
            let mut x = 0;
            for _ in 0..5_000_000 {
                x += 1
            }
            x
        })
    }).collect();

    for h in handles {
        println!("Thread finished with count={}",
        h.join().map_err(|_| "Could not join a thread!").unwrap());
    }
}

Nota: in realtà c’è nel Git il file finale, completo e commentato, probabilmente si userà quello.
In order to make this piece of code working as an external library we need to change the function definition with the following lines:

#[no_mangle]
pub extern fn process() {
...

Don’t worry about the meaning of this for now. Just note that extern means that we want to call this function from the outside, from a C-like interface, and  pub means that the function is “public”.

The second change we need is to add these two lines at the end of the Cargo.toml file.

[lib]
name = "rustbomb"
crate-type = ["dylib"]

This say to the compiler to compile the library in a “standard” way so that can be used as a C library, and not in the “rusty” way. We can now compile this crate and we are ready to embed this library in a Python script.

Provo a compilare ma…

k1

provo a sostituire i files lib.rs e Cargo.toml con quelli del Git:

k2

OK!😀

Scrivere il lato Python

We need now to write the Python part of the application. Create a new file client.py (for instance) in the crate root [cioè rustbomb]. To attach the Python script to the library we will use the standard interface ctypes.

import ctypes

# Wind DLL get as parameter the path to the brand new compiled library!
# corretto per Linux
lib = ctypes.CDLL("./target/release/librustypython.so")

lib.process()

print("done")

We finished in just 3 lines of code. Try to run this Python script and enjoy some rusty threads running on your cores!

k3

😀 OK😀 Thek3nger rockz! (forse già detto ma repetita Juventus (& Moggi birikinassaye)).

Passare parametri alle funzioni Rust

What if we want to pass as parameter the number of threads we want to spawn?
The first naive implementation will be to just add the parameters in Rust in the standard way.

#[no_mangle]
pub extern fn process2(threads_number: i32) {
let handles: Vec<_> = (0..threads_number).map(|_| {
thread::spawn(|| {
...

And then call this new function with a number. And… Well.. It works! However, is not a wise to use “Rust’s specific data types” when we are writing an FFI interface. We need something more C-friendly.

Aggiungiamo a Cargo.toml:

[dependencies]
libc = "0.2.0"

This will include the libc crate that will add a lot of C-friendly types you can use. Then we need to add these two lines at the beginning of the Rust library.

extern crate libc;

use libc::{size_t,int32_t};

Now we are ready to write the function declaration with the right type:

#[no_mangle]
pub extern fn process2(threads_number: int32_t) {
let handles: Vec<_> = (0..threads_number).map(|_| {
thread::spawn(|| {
...

Note that we are using int32_t instead of i32. It is the same thing, but now your code is bulletproof, even if you change architecture or implementations. It is formally correct and it safer to use.

Passo a part 2

[W]e have seen how to run simple Rust functions with integer arguments. This is not enough, of course. We need to go further by passing Python lists to Rust functions.

The problem is that it is not possible to pass directly a Python list to a C interface. Python lists (we can call them Plists) are complicated beasts, you can easily see that they are objects full of methods, and attributes and… Stuff.

k4

We need first to convert this in something edible from a Rust library. But first things first.

La funzione Rust somma di una lista di interi

The first step to do is to write the Rust function in our library. This time we want a function that, given a list of integers as input, returns the sum of the list. The final code is something like this:

#[no_mangle]
pub extern fn process2(threads_number: int32_t) {
    let handles: Vec = (0..threads_number).map(|_| {
        thread::spawn(|| {
            let mut x = 0;
            for _ in 0..5_000_000 {
                x += 1
            }
            x
        })
    }).collect();

    for h in handles {
        println!("Thread finished with count={}",
        h.join().map_err(|_| "Could not join a thread!").unwrap());
    }
}

It is not a complicated function, but you can see that there are some interesting points:

The function takes two arguments: a pointer to an int32_t which represent the first item on our list; and a size_t value representing the size of the list. We cannot pass a list directly, but we need to pass the list in its “primordial form”: an array of data in memory and the length of that array.
Because the list is given as raw data, we need to “assemble” the list according the Rust fashion. This is what I’m doing in the unsafe block: create a slice from the raw parts.

Then the function continues as usual, folding the list in order to get the final sum. (Remember that no semi-colon at the end of a statement is just like a “return” cioè l’ultima espressione valutata è il valore della funzione, proprio come in Lisp; se metto il punto-virgola l’ultima espressione vale null.

Il lato Python

Ecco la funzione sum_list, per me il file sl.py:

import ctypes

lib = ctypes.CDLL("./target/release/librustypython.so")
lib.sum_list.argtypes = (ctypes.POINTER(ctypes.c_int32), ctypes.c_size_t)
print("Summing in Rust the list of first 1000 numbers.")
number_list = list(range(1001))
c_number_list = (ctypes.c_int32 * len(number_list))(*number_list)
result = lib.sum_list(c_number_list, len(number_list))
print("Result is {}. Expected 500500.".format(result))

k5

Cosa succede:

  • First line. This explicitly defines the type of the arguments of sum_list. To be honest the script works even without this line, but I think it is better to use it anyway.
  • c_number_list is where we explode the pythonic list into a C friendly list. It is a bit cryptic, but in short this mean: create a new Python object representing an array of len(number_list) blocks of c_int32; then initialize this object with the contents  of the desired list (number_list).
  • Then we simply call the Rust function with this object (representing the raw data) and the length (given by the length of the list).

A nice side effect. Because we are using just the raw content of the list by “unpacking” the list (the asterisk before number_list on line 4), the same code works even if number_list is a tuple or a set.

Già detto che TheK3nger rockz?😀 E non finisce qui, c’è in arrivo una seconda puntata.

:mrgreen:

Iscriviti

Ricevi al tuo indirizzo email tutti i nuovi post del sito.

Segui assieme ad altri 93 follower