Zum Hauptinhalt springen
Version: Material

pyodide-code

```py live_pyo id=b26cde2c-5b0b-4acb-814e-9f2b3bdd29a9
print("Hello from Pyodide!")
```
http://localhost:3000
print("Hello from Pyodide!")

Sleep​

from time import sleep
for i in range(10):
print(i, i*i, i**3)
sleep(1)

Fibonacci​

def fibonacci(n):
if n <= 0:
return []
elif n == 1:
return [0]
elif n == 2:
return [0, 1]
else:
seq = [0, 1]
for i in range(2, n):
seq.append(seq[-1] + seq[-2])
return seq
n = int(input("fib von? "))
print(fibonacci(n))

Mit Memoization​

from functools import lru_cache

@lru_cache
def fibonacci(n):
if n in {0, 1}:
return n
return fibonacci(n-1) + fibonacci(n-2)

for n in range(1, 200):
print(f"Fibonacci({n}) = {fibonacci(n)}")

Minimierung mit SciPy​

import numpy as np
from scipy import optimize
# Function to minimize: f(x) = (x - 3)^2
def func(x):
return (x - 3) ** 2

# Minimize using scipy.optimize
result = optimize.minimize(func, x0=0)
print("Minimum at x =", result.x[0])
print("Function value at minimum =", result.fun)

Interaktive Komponenten hinzufügen​

Das pyodide-code-Package kann erweitert werden - sowohl mit neuen, lokalen Pythin-Bibliotheken als auch mit interaktiven Komponenten, die in React geschrieben sind und mit Python kommunizieren können. Ein Beispiel dafür ist die interaktive Uhr-Komponente:

Interaktive Uhr​

<Clock clockId="sbb-uhr" />

```py live_pyo id=d5240fb9-2aab-4e02-8e88-770a117b2068 title=Interaktive_Uhr
from clock import use_clock
from time import sleep
uhr = use_clock("sbb-uhr")

while True:
uhr.set_seconds(uhr.seconds + 1)
sleep(0.01)
```
http://localhost:3000

Uhr: sbb-uhr

from clock import use_clock
from time import sleep
uhr = use_clock("sbb-uhr")

while True:
uhr.set_seconds(uhr.seconds + 1)
if uhr.seconds % 360 == 0:
sleep(0.99)
uhr.set_minutes(uhr.minutes + 6)
if uhr.minutes % 360 == 0:
uhr.set_hours(uhr.hours + 30)
sleep(0.01)

Implementations-Details​

Die Implementierung kann im Seitenspezifischen 👉 ./website-Ordner nachvollzogen werden. Die wesentlichen Teile sind:

  • Nachrichten, die von Pyodide empfangen werden, werden an den siteStore#handleMessage weitergeleitet und können so beliebig verarbeitet werden.
  • Die Python clock-Bibliothek wird ĂĽber ./website/packages/pyodide-code/pyodideJsModules/siteModules.ts registriert und stellt die use_clock-Funktion zur VerfĂĽgung.

    siteModules.ts

    import type { ModuleType } from '@tdev/pyodide-code/pyodideJsModules';
    /**
    * this file is to add custom pyodide js modules for the teaching-dev website
    * ensure to remove this file from the updateTdev.config.yaml to avoid overwriting
    */

    declare module '@tdev/pyodide-code/pyodideJsModules' {
    export interface MessageTypeMap {
    clock: {
    type: 'clock';
    id: string;
    clockType: 'hours' | 'minutes' | 'seconds';
    value: number;
    timeStamp: number;
    };
    }
    }

    export const siteModules: Partial<ModuleType> = {
    clock: (ctx) => {
    const { sendMessage, getTime } = ctx;
    return {
    use_clock: (id: string) => {
    let hours = 0;
    let minutes = 0;
    let seconds = 0;
    return {
    get minutes() {
    return minutes;
    },
    get hours() {
    return hours;
    },
    get seconds() {
    return seconds;
    },
    reset: () => {
    hours = 0;
    minutes = 0;
    seconds = 0;
    ['hours', 'minutes', 'seconds'].forEach((clockType) => {
    sendMessage({
    type: 'clock',
    clockType: clockType as 'hours' | 'minutes' | 'seconds',
    value: 0,
    id: id,
    timeStamp: getTime()
    });
    });
    },
    set_time: (h: number, m: number, s: number) => {
    hours = h;
    minutes = m;
    seconds = s;
    [
    ['hours', h],
    ['minutes', m],
    ['seconds', s]
    ].forEach(([clockType, value]) => {
    sendMessage({
    type: 'clock',
    clockType: clockType as 'hours' | 'minutes' | 'seconds',
    value: value as number,
    id: id,
    timeStamp: getTime()
    });
    });
    },
    set_hours: (deg: number) => {
    hours = deg;
    sendMessage({
    type: 'clock',
    clockType: 'hours',
    value: deg,
    id: id,
    timeStamp: getTime()
    });
    },
    set_minutes: (deg: number) => {
    minutes = deg;
    sendMessage({
    type: 'clock',
    clockType: 'minutes',
    value: deg,
    id: id,
    timeStamp: getTime()
    });
    },
    set_seconds: (deg: number) => {
    seconds = deg;
    sendMessage({
    type: 'clock',
    clockType: 'seconds',
    value: deg,
    id: id,
    timeStamp: getTime()
    });
    }
    };
    }
    };
    }
    };

  • In ./website/packages/pyodide-code/models/Clock.ts wird ein MobX-Modell fĂĽr die Uhr definiert. Es kann ĂĽber @tdev/pyodide-code/models/Clock importiert und in React-Komponenten verwendet werden.
  • In ./website/packages/pyodide-code/components/Clock wird die eigentliche Uhr-Komponente implementiert, die das Clock-Modell verwendet, um die Zeiger zu positionieren.
  • In ./website/stores/ClockStore.ts wird ein MobX-Store definiert, der die verschiedenen Uhren verwaltet und die Nachrichten von Pyodide verarbeitet. Dieser Store wird im ./website/stores/SiteStore.ts registriert und initialisiert.

Installation​

packages/tdev/pyodide-code

Kopiere des packages/tdev/pyodide-code-Verzeichnis in das tdev-website/website/packages-Verzeichnis oder ĂĽber updateTdev.config.yaml hinzufĂĽgen.

  1. HinzufĂĽgen des pyodide-code-Package zu den apiDocumentProviders im siteConfig.ts:
    siteConfig.ts
    const getSiteConfig: SiteConfigProvider = () => {
    return {
    apiDocumentProviders: [
    require.resolve('@tdev/pyodide-code/register'),
    ]
    };
    };
  2. ServiceWorker hinzufĂĽgen:
    Damit Eingaben von Benutzer:innen abgewartet werden können, braucht es einen Servie-Worker. Dieser muss direkt in den statischen Ordner 👉 static/pyodide.sw.js kopiert werden.

    static/pyodide.sw.js

    pyodide.sw.js
    const DOCUSAURUS_SW_SCOPE = '/';
    const PY_INPUT = 'PY_INPUT';
    const PY_AWAIT_INPUT = 'PY_AWAIT_INPUT';
    const PY_STDIN_ROUTE = `${DOCUSAURUS_SW_SCOPE}py-get-input/`;
    const PY_CANCEL_INPUT = 'PY_CANCEL_INPUT';

    self.addEventListener('install', () => {
    self.skipWaiting();
    });

    self.addEventListener('activate', () => {
    self.clients.claim();
    });

    const resolvers = new Map();

    self.addEventListener('message', (event) => {
    switch (event.data.type) {
    case PY_INPUT: {
    const resolverArray = resolvers.get(event.data.id);
    const resolver = resolverArray && resolverArray.shift();
    if (!resolver) {
    console.error('Error handling input: No resolver');
    return;
    }
    resolver(new Response(event.data.value, { status: 200 }));
    break;
    }
    case PY_CANCEL_INPUT: {
    const rejecterArray = resolvers.get(event.data.id);
    const rejecter = rejecterArray && rejecterArray.shift();
    if (!rejecter) {
    console.error('Error handling input: No resolver');
    return;
    }
    rejecter(new Response('Run cancelled', { status: 410 }));
    break;
    }
    default:
    return;
    }
    });

    self.addEventListener('fetch', (event) => {
    const url = new URL(event.request.url);

    if (url.pathname !== PY_STDIN_ROUTE) {
    return;
    }

    const id = url.searchParams.get('id');
    if (!id) {
    console.error('Error handling input: No id');
    return;
    }
    const prompt = url.searchParams.get('prompt');

    event.waitUntil(
    self.clients
    .matchAll()
    .then((clients) => {
    clients.forEach((client) => {
    if (client.type === 'window') {
    client.postMessage({
    type: PY_AWAIT_INPUT,
    id,
    prompt
    });
    }
    });
    })
    .catch((err) => console.error('Error matching clients', err))
    );

    const promise = new Promise((resolve) => {
    const resolverArray = resolvers.get(id) || [];
    resolverArray.push(resolve);
    resolvers.set(id, resolverArray);
    });
    event.respondWith(promise);
    });

Danach muss erneut installiert werden:

yarn install