Bringing python into Front-End with Pyodide

브라우저에서 파이썬 코드를 실행해본다
Running ...
bGV0IHB5b2RpZGUgPSBhd2FpdCBsb2FkUHlvZGlkZSh7IGluZGV4VVJMOiAnaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L3B5b2RpZGUvdjAuMjAuMC9mdWxsLycgfSk7CmF3YWl0IHB5b2RpZGUubG9hZFBhY2thZ2UoIm1pY3JvcGlwIik7CmF3YWl0IHB5b2RpZGUucnVuUHl0aG9uQXN5bmMoYAogICBpbXBvcnQgbWljcm9waXAsIGpzCiAgIGF3YWl0IG1pY3JvcGlwLmluc3RhbGwoJ2Vtb2ppJykKICAgaW1wb3J0IGVtb2ppCiAgIHJlc3VsdCA9IGVtb2ppLmVtb2ppemUoJ1B5dGhvbiBpcyA6dGh1bWJzX3VwOicpCiAgIGpzLmRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyNjb250YWluZXInKS5pbm5lclRleHQ9cmVzdWx0CmApOw==
let pyodide = await loadPyodide({ indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.20.0/full/' }); await pyodide.loadPackage("micropip"); await pyodide.runPythonAsync(` import micropip, js await micropip.install('emoji') import emoji result = emoji.emojize('Python is :thumbs_up:') js.document.querySelector('#container').innerText=result `);
간략히 정리한 사용 예시 코드
bGV0IHB5b2RpZGU7CnsKICAgIC8vIFB5b2RpZGXripQg67Cx7JeU65OcIOyXhuydtCDsm7nruIzrnbzsmrDsoIDsl5DshJwg7YyM7J207I2sIOy9lOuTnOulvCDqtazrj5nqsIDriqXtlZjqsowg7ZW07KSA64ukCiAgICAvLyBDUHl0aG9uIOydhCDsm7nslrTshYjruJTrpqzroZwg7Y+s7YyF7ZWc6rKD7J2064ukCiAgICAvLyDsm7ntjpjsnbTsp4Ag7IOB7JeQIGh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9weW9kaWRlL3YwLjIwLjAvZnVsbC9weW9kaWRlLmpzIOy2lOqwgO2VmOqzoCDslYTrnpjsmYAg6rCZ7J20IOyLpO2Wie2VmOuptCDrkJzri6QKICAgIC8vIOyCrOyaqeydtCDrhIjrrLQg6rCE64uo7ZWY64ukCiAgICAvLyDtjIzsnbTsjazsnZggcHJpbnQg7ZWo7IiY64qUIGNvbnNvbGUubG9nIOulvCDtlZzqsoPsspjrn7wg7Lac66Cl65Cc64ukCiAgICAvLyBsb2FkUHlvZGlkZSDripQg65GQ67KI7J207IOBIOuhnOuUqeydtCDslYjrkJjripTqsoPsnLzroZwg7ZmV7J2465Cc64ukCiAgICBweW9kaWRlID0gYXdhaXQgbG9hZFB5b2RpZGUoeyBpbmRleFVSTDogJ2h0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9weW9kaWRlL3YwLjIwLjAvZnVsbC8nIH0pOwogICAgYXdhaXQgcHlvZGlkZS5ydW5QeXRob25Bc3luYyhgCiAgICAgICAgIGZvciBpIGluIHJhbmdlKDEwKToKICAgICAgICAgICAgIHByaW50KGkpCiAgICAgYCk7Cn0KewogICAgLy8gcnVuUHl0aG9uQXN5bmMg66W8IO2Gte2VtCDsi6TtlontlZwg7YyM7J207I2sIOy9lOuTnOydmCDqsrDqs7zripQg7JWE656Y7JmAIOqwmeydtCDrsJvsnYQg7IiYIOyeiOuLpAogICAgLy8g7YyM7J207I2sIOy9lOuTnCDsiJjtlonqsrDqs7zroZwg7Ja77J2AIOuNsOydtO2EsOulvCDtlITroaDtirjsl5Trk5zsuKHsl5DshJwg7IKs7Jqp6rCA64ql7ZWcIOuqqOyKteydtOuLpAogICAgbGV0IHJlc3VsdCA9IGF3YWl0IHB5b2RpZGUucnVuUHl0aG9uQXN5bmMoYAogICAgICAgICBpbXBvcnQganNvbgogICAgICAgICBqc29uLmR1bXBzKGxpc3QoJ3cnICogMTApKQogICAgIGApOwogICAgY29uc29sZS5sb2cocmVzdWx0KQp9CnsKICAgIC8vIO2UhOuhoO2KuOyXlOuTnOy4oeydmCDrjbDsnbTthLDrpbwg7YyM7J207I2s7Jy866GcIOuEmOq4sOuKlCDrsKnrspXsnYAg7JWE656Y7JmAIOqwmeydtCDtlbTso7zrqbQg65Cc64ukCiAgICBweW9kaWRlLmdsb2JhbHMuc2V0KCJkYXRhZnJvbWpzIiwgMTApOwogICAgbGV0IHJlc3VsdCA9IGF3YWl0IHB5b2RpZGUucnVuUHl0aG9uQXN5bmMoYAogICAgICAgICBpbXBvcnQganNvbgogICAgICAgICBqc29uLmR1bXBzKGxpc3QoJ2QnICogZGF0YWZyb21qcykpCiAgICAgYCk7CiAgICBjb25zb2xlLmxvZyhyZXN1bHQpCn0KewogICAgLy8g7JWE656Y7JmAIOqwmeydtCBKU+ydmCDtlajsiJjrj4Qg64SY6ri4IOyImCDsnojri6QKICAgIHB5b2RpZGUuZ2xvYmFscy5zZXQoImpzRnVuY3Rpb24iLCAoKSA9PiBNYXRoLnJhbmRvbSgpKTsKICAgIGxldCByZXN1bHQgPSBhd2FpdCBweW9kaWRlLnJ1blB5dGhvbkFzeW5jKGAKICAgICAgICAganNGdW5jdGlvbigpCiAgICAgYCk7CiAgICBjb25zb2xlLmxvZyhyZXN1bHQpCn0KewogICAgLy8g7JWE656Y7JmAIOqwmeydtCBKU+ydmCDtgbTrnpjsiqTrj4Qg64SY6ri4IOyImCDsnojri6QKICAgIC8vIOq3uOufrOuCmCDtgbTrnpjsiqTripQgbmV3IOyXhuydtCDtlajsiJjsspjrn7wg7Iuk7ZaJ7ZWgIOyImCDsl4bri6QKICAgIC8vIOq3uOufsOuNsCDtjIzsnbTsjazsl5DshJzripQgbmV3IOqwgCDsl4bri6TripTqsoPsnbQg65Sc66CI66eI7J2064ukCiAgICAvLyBuZXfrpbwg67aZ7J2066m0IO2MjOydtOyNrOyXkCDsl4bripTtgqTsm4zrk5zrnbwg7JeQ65+sCiAgICAvLyBuZXfrpbwg7JWI67aZ7J2066m0IEpTQ2xhc3PripQg7YG0656Y7Iqk6528IO2VqOyImOyymOufvCDsi6TtlontlaDsiJgg7JeG7Ja07IScIOyXkOufrAogICAgY2xhc3MgSlNDbGFzcyB7IH0KICAgIHB5b2RpZGUuZ2xvYmFscy5zZXQoIkpTQ2xhc3MiLCBKU0NsYXNzKTsKICAgIHRyeSB7CiAgICAgICAgYXdhaXQgcHlvZGlkZS5ydW5QeXRob25Bc3luYyhgCiAgICAgICAgICAgICBKU0NsYXNzKCkKICAgICAgICAgYCk7CiAgICB9IGNhdGNoIChlKSB7CiAgICAgICAgY29uc29sZS5sb2coZSkKICAgIH0KfQp7CiAgICAvLyDtjIzsnbTsjawg7L2U65OcIOyViOyXkOyEnCBqcyDrnbzripQg66qo65OI7J2EIOyehO2PrO2KuCDtlaAg7IiYIOyeiOuKlOuNsAogICAgLy8ganMg64qUIOu4jOudvOyasOyggOyDgeydmCB3aW5kb3fsnbTri6QKICAgIC8vIOymiSB3aW5kb3cg6rCd7LK066W8IO2Gte2VtCDrqqjrk6AgV2ViQVBJ7JeQIOygkeq3vO2VoCDsiJgg7J6I64ukCiAgICAvLyDsl4Tssq0g66mL7KeE6rGw64ukLi4KICAgIGxldCByZXN1bHQgPSBhd2FpdCBweW9kaWRlLnJ1blB5dGhvbkFzeW5jKGAKICAgICAgICAgZnJvbSBqcyBpbXBvcnQgY29uc29sZSwgZmV0Y2gsIGRvY3VtZW50CiAgICAgICAgIGltcG9ydCBqcwogICAgICAgICBhc3luYyBkZWYgcG9zdF9kYXRhKCk6CiAgICAgICAgICAgICByZXNwb25zZSA9IGF3YWl0IGZldGNoKCfsnKDslYzsl5jso7zshownKQogICAgICAgICAgICAganNvbiA9IGF3YWl0IHJlc3BvbnNlLmpzb24oKQogICAgICAgICAgICAgcmV0dXJuIGpzb24KICAgICAgICAgCiAgICAgICAgIGF3YWl0IHBvc3RfZGF0YSgpCiAgICAgIGApOwogICAgY29uc29sZS5sb2cocmVzdWx0KQp9CnsKICAgIC8vIG1pY3JvcGlw652864qU6rKMIOyeiOuLpCDsnbTqsbgg7J207Jqp7ZW07IScIO2MjOydtOyNrCDsvZTrk5zslYjsl5DshJwg66qo65OI7J2EIOyEpOy5mO2VoCDsiJgg7J6I64ukCiAgICAvLyBqbWVzcGF0aOuekSBlbW9qaeuqqOuTiOydhCDshKTsuZjtlbTshJwg7IKs7Jqp7ZW067O4IOuqqOyKteydtOuLpAogICAgYXdhaXQgcHlvZGlkZS5sb2FkUGFja2FnZSgibWljcm9waXAiKTsKICAgIGxldCByZXN1bHQgPSBhd2FpdCBweW9kaWRlLnJ1blB5dGhvbkFzeW5jKGAKICAgICAgICAgaW1wb3J0IG1pY3JvcGlwCiAgICAgICAgIGF3YWl0IG1pY3JvcGlwLmluc3RhbGwoJ2ptZXNwYXRoJykKICAgICAgICAgYXdhaXQgbWljcm9waXAuaW5zdGFsbCgnZW1vamknKQogICAgICAgICBpbXBvcnQgam1lc3BhdGgsIGVtb2ppCiAgICAgICAgIHByaW50KGptZXNwYXRoLnNlYXJjaCgnZm9vLmJhcicsIHsiZm9vIjogeyJiYXIiOiAiYmF6In19KSkKICAgICAgICAgcHJpbnQoam1lc3BhdGguc2VhcmNoKCdmb28uYmFyWypdLm5hbWUnLCB7ImZvbyI6IHsiYmFyIjogW3sibmFtZSI6ICJvbmUifSwgeyJuYW1lIjogInR3byJ9XX19KSkKICAgICAgICAgcHJpbnQoZW1vamkuZW1vaml6ZSgnUHl0aG9uIGlzIDp0aHVtYnNfdXA6JykpCiAgICAgYCk7CiAgICBjb25zb2xlLmxvZyhyZXN1bHQpCn0KewogICAgLy8g7YyM7J207I2s7JeQ7IScIOyWu+ydgCDrjbDsnbTthLDrpbwg7ZSE66Gg7Yq47JeQ7IScIOyCrOyaqe2VmOq4sAogICAgLy8g7Ja77J2AIOuNsOydtO2EsOqwgCDsm5Dsi5ztmJXsnbgg6rK97JqwCiAgICBsZXQgcmVzdWx0MCA9IGF3YWl0IHB5b2RpZGUucnVuUHl0aG9uQXN5bmMoYAogICAgICAgMTIzCiAgICBgKTsKICAgIGNvbnNvbGUubG9nKHJlc3VsdDApCiAgICAvLyDslrvsnYAg642w7J207YSw6rCAIOybkOyLnO2YleydtCDslYTri4zqsr3smrDripQgdG9KcygpIOyCrOyaqQogICAgbGV0IHJlc3VsdDEgPSBhd2FpdCBweW9kaWRlLnJ1blB5dGhvbkFzeW5jKGAKICAgICAgIGRlZiBhYmMoKToKICAgICAgICAgIHJldHVybiAxMjMKICAgICAgIGFiYwogICAgYCk7CiAgICBjb25zb2xlLmxvZyhyZXN1bHQxLnRvSnMoKSgpKQogICAgLy8g7JWE656Y7JmAIOqwmeydgOqyjCDrkJjripTqsIAg7ZW067Sk64qU642wIOyViOuQnOuLpAogICAgbGV0IHJlc3VsdDIgPSBhd2FpdCBweW9kaWRlLnJ1blB5dGhvbkFzeW5jKGAKICAgICAgIGltcG9ydCBqc29uCiAgICAgICBqc29uCiAgICBgKTsKICAgIGNvbnNvbGUubG9nKHJlc3VsdDIudG9KcygpLmR1bXBzKHsgdGVzdDogMTIzIH0pKQogICAgLy8g7JWE656Y7JmAIOqwmeydgOqyjCDrkJjripTqsIAg7ZW067Sk64qU642wIOyViOuQnOuLpAogICAgbGV0IHJlc3VsdDMgPSBhd2FpdCBweW9kaWRlLnJ1blB5dGhvbkFzeW5jKGAKICAgICAgIHsidGVzdCI6MTIzfQogICAgYCk7CiAgICBjb25zb2xlLmxvZyhyZXN1bHQyLmR1bXBzKHJlc3VsdDMpKQp9
let pyodide; { // Pyodide는 백엔드 없이 웹브라우저에서 파이썬 코드를 구동가능하게 해준다 // CPython 을 웹어셈블리로 포팅한것이다 // 웹페이지 상에 https://cdn.jsdelivr.net/pyodide/v0.20.0/full/pyodide.js 추가하고 아래와 같이 실행하면 된다 // 사용이 너무 간단하다 // 파이썬의 print 함수는 console.log 를 한것처럼 출력된다 // loadPyodide 는 두번이상 로딩이 안되는것으로 확인된다 pyodide = await loadPyodide({ indexURL: 'https://cdn.jsdelivr.net/pyodide/v0.20.0/full/' }); await pyodide.runPythonAsync(` for i in range(10): print(i) `); } { // runPythonAsync 를 통해 실행한 파이썬 코드의 결과는 아래와 같이 받을 수 있다 // 파이썬 코드 수행결과로 얻은 데이터를 프론트엔드측에서 사용가능한 모습이다 let result = await pyodide.runPythonAsync(` import json json.dumps(list('w' * 10)) `); console.log(result) } { // 프론트엔드측의 데이터를 파이썬으로 넘기는 방법은 아래와 같이 해주면 된다 pyodide.globals.set("datafromjs", 10); let result = await pyodide.runPythonAsync(` import json json.dumps(list('d' * datafromjs)) `); console.log(result) } { // 아래와 같이 JS의 함수도 넘길 수 있다 pyodide.globals.set("jsFunction", () => Math.random()); let result = await pyodide.runPythonAsync(` jsFunction() `); console.log(result) } { // 아래와 같이 JS의 클래스도 넘길 수 있다 // 그러나 클래스는 new 없이 함수처럼 실행할 수 없다 // 그런데 파이썬에서는 new 가 없다는것이 딜레마이다 // new를 붙이면 파이썬에 없는키워드라 에러 // new를 안붙이면 JSClass는 클래스라 함수처럼 실행할수 없어서 에러 class JSClass { } pyodide.globals.set("JSClass", JSClass); try { await pyodide.runPythonAsync(` JSClass() `); } catch (e) { console.log(e) } } { // 파이썬 코드 안에서 js 라는 모듈을 임포트 할 수 있는데 // js 는 브라우저상의 window이다 // 즉 window 객체를 통해 모든 WebAPI에 접근할 수 있다 // 엄청 멋진거다.. let result = await pyodide.runPythonAsync(` from js import console, fetch, document import js async def post_data(): response = await fetch('유알엘주소') json = await response.json() return json await post_data() `); console.log(result) } { // micropip라는게 있다 이걸 이용해서 파이썬 코드안에서 모듈을 설치할 수 있다 // jmespath랑 emoji모듈을 설치해서 사용해본 모습이다 await pyodide.loadPackage("micropip"); let result = await pyodide.runPythonAsync(` import micropip await micropip.install('jmespath') await micropip.install('emoji') import jmespath, emoji print(jmespath.search('foo.bar', {"foo": {"bar": "baz"}})) print(jmespath.search('foo.bar[*].name', {"foo": {"bar": [{"name": "one"}, {"name": "two"}]}})) print(emoji.emojize('Python is :thumbs_up:')) `); console.log(result) } { // 파이썬에서 얻은 데이터를 프론트에서 사용하기 // 얻은 데이터가 원시형인 경우 let result0 = await pyodide.runPythonAsync(` 123 `); console.log(result0) // 얻은 데이터가 원시형이 아닌경우는 toJs() 사용 let result1 = await pyodide.runPythonAsync(` def abc(): return 123 abc `); console.log(result1.toJs()()) // 아래와 같은게 되는가 해봤는데 안된다 let result2 = await pyodide.runPythonAsync(` import json json `); console.log(result2.toJs().dumps({ test: 123 })) // 아래와 같은게 되는가 해봤는데 안된다 let result3 = await pyodide.runPythonAsync(` {"test":123} `); console.log(result2.dumps(result3)) }