تشغيل برامج بايثون في متصفحك

في السنوات الأخيرة، برزت تقنية WebAssembly (والتي غالباً ما يتم اختصارها إلى WASM) كتقنية مثيرة للاهتمام تعمل على توسيع قدرات متصفحات الويب إلى ما هو أبعد بكثير من المجالات التقليدية لـ HTML و CSS و JavaScript.

بصفتي مطور بايثون، أجد أن إحدى التطبيقات المثيرة للاهتمام هي القدرة على تشغيل كود بايثون مباشرةً في المتصفح. في هذه المقالة، سأستكشف ماهية WebAssembly (وعلاقتها بمكتبة Pyodide)، وأتحدث عن مزاياها واستخداماتها اليومية، وأستعرض بعض الأمثلة العملية لكيفية استخدام WebAssembly لتشغيل برامج بايثون على الويب.

يمكن لهذه الأدوات أن تفيد علماء البيانات ومتخصصي التعلم الآلي. يوفر Pyodide جزءًا كبيرًا من حزمة بايثون العلمية (NumPy، Pandas، Scikit-learn، Matplotlib، SciPy، وغيرها) للمتصفح، مما يتيح استخدام الأدوات والمكتبات المألوفة أثناء تطوير البرامج. كما يمكن استخدامه لأغراض العرض التوضيحي. وكما سترى في المثال الأخير، من خلال دمج قوة معالجة البيانات في بايثون مع HTML وCSS وJavaScript لتصميم واجهة المستخدم، يمكنك إنشاء لوحات تحكم أو أدوات تفاعلية بسرعة دون الحاجة إلى خادم خلفي منفصل في العديد من حالات الاستخدام.

ما هو WebAssembly؟

WebAssembly هو تنسيق تعليمات ثنائية منخفض المستوى، مصمم ليكون هدفًا قابلاً للنقل لتجميع لغات برمجة عالية المستوى، مثل C وC++ وRust وحتى Python. وقد تم إنشاؤه لتمكين تطبيقات عالية الأداء على الويب دون بعض عيوب تنفيذ JavaScript التقليدي، مثل سرعة وقت التشغيل. تتضمن بعض الجوانب الرئيسية لـ WebAssembly ما يلي:

  • قابلية النقل: تعمل وحدات WebAssembly بشكل متسق عبر جميع المتصفحات الحديثة.
  • الأداء: يتميز التنسيق الثنائي بأنه مضغوط ويمكن تحليله بسرعة، مما يسمح بسرعة تنفيذ قريبة من السرعة الأصلية.
  • الأمان: يوفر WebAssembly، الذي يعمل في بيئة معزولة، ضمانات أمنية قوية.
  • عدم التقيد بلغة برمجة معينة: على الرغم من أن المتصفحات تدعم في المقام الأول جافا سكريبت، إلا أن WebAssembly يمكّن المطورين من كتابة التعليمات البرمجية بلغات أخرى وتجميعها إلى WebAssembly (wasm).

ما هي استخدامات WebAssembly؟

تتمتع WebAssembly بمجموعة واسعة من التطبيقات. تتضمن بعض حالات الاستخدام الأكثر شيوعًا ما يلي:

  1. تطبيقات الويب عالية الأداء: يمكن لـ WebAssembly أن يساعد التطبيقات مثل الألعاب ومحررات الصور والفيديو والمحاكاة على تحقيق أداء قريب من الأداء الأصلي.
  2. نقل التعليمات البرمجية القديمة: يمكن تجميع التعليمات البرمجية المكتوبة بلغات C أو C++ أو Rust إلى WebAssembly، مما يسمح للمطورين بإعادة استخدام المكتبات وقواعد التعليمات البرمجية الموجودة على الويب.
  3. معالجة الوسائط المتعددة: تستفيد مكتبات معالجة الصوت والفيديو من سرعة WebAssembly، مما يتيح تنفيذ مهام معالجة أكثر تعقيدًا في الوقت الفعلي.
  4. الحوسبة العلمية: يمكن تفريغ العمليات الحسابية الثقيلة مثل التعلم الآلي، أو تصور البيانات، أو المحاكاة العددية إلى وحدات WebAssembly.
  5. تشغيل لغات متعددة: تسمح مشاريع مثل Pyodide بتشغيل لغة بايثون (ونظامها البيئي الواسع) في المتصفح دون الحاجة إلى خادم خلفي.

إذا كنت تبرمج بشكل متكرر بلغة بايثون، فإن هذه النقطة الأخيرة يجب أن تثير اهتمامك، لذا دعونا نتعمق في هذا الجانب أكثر.

تشغيل بايثون على الويب

تقليديًا، يُشغَّل بايثون على الخادم أو في تطبيقات سطح المكتب. ولكن بفضل مبادرات مثل Pyodide، يُمكن تشغيل بايثون في المتصفح عبر WebAssembly. يقوم Pyodide بتحويل كود مترجم CPython إلى WebAssembly، مما يسمح لك بتنفيذ كود بايثون واستخدام العديد من مكتبات الطرف الثالث الشائعة مباشرةً في تطبيق الويب الخاص بك.

وهذا ليس مجرد حيلة. هناك العديد من المزايا للقيام بذلك، بما في ذلك:

  • باستخدام النظام البيئي الواسع لمكتبات بايثون، بما في ذلك حزم علوم البيانات (NumPy، Pandas، Matplotlib) والتعلم الآلي (Scikit-Learn، TensorFlow).
  • تحسين الاستجابة نظرًا لقلة عدد الرحلات ذهابًا وإيابًا إلى الخادم.
  • إنها عملية نشر أبسط حيث يمكن أن توجد منطق التطبيق بالكامل في الواجهة الأمامية.

لقد ذكرنا Pyodide عدة مرات بالفعل، لذا دعونا نلقي نظرة فاحصة على ماهية البيوديد بالضبط.

ما هو Pyodide

انبثقت فكرة Pyodide من الحاجة المتزايدة لتشغيل أكواد بايثون مباشرةً في المتصفح دون الاعتماد على بنية خادم تقليدية. تقليديًا، كانت تطبيقات الويب تعتمد على جافا سكريبت للتفاعلات من جانب العميل، مما جعل بايثون محصورةً في تطبيقات الواجهة الخلفية أو تطبيقات سطح المكتب. ولكن مع ظهور WebAssembly، سنحت فرصة لسد هذه الفجوة.

أدركت مؤسسة موزيلا للأبحاث إمكانات هذا النهج، فشرعت في نقل CPython، وهو التطبيق المرجعي للغة بايثون، إلى WebAssembly باستخدام مجموعة أدوات Emscripten. كان الهدف من هذا الجهد تشغيل بايثون في المتصفح، وفتح آفاق جديدة لتطبيقات تفاعلية من جانب العميل، مدعومة بمجموعة مكتبات بايثون الغنية في علوم البيانات والحوسبة العددية وغيرها.

باختصار، يُعدّ Pyodide في جوهره نسخةً مُعدّلة من CPython مُجمّعة في WebAssembly. هذا يعني أنه عند تشغيل كود بايثون في المتصفح باستخدام Pyodide، فإنك تُشغّل مُفسّر بايثون كامل الوظائف مُحسّن لبيئة الويب.

حسناً، حان الوقت لإلقاء نظرة على بعض التعليمات البرمجية.

إعداد بيئة التطوير

قبل أن نبدأ البرمجة، دعونا نُهيئ بيئة التطوير. من الأفضل إنشاء بيئة بايثون منفصلة حيث يمكنك تثبيت أي برامج ضرورية وتجربة البرمجة، مع العلم أن أي شيء تفعله في هذه البيئة لن يؤثر على بقية نظامك.

أستخدم برنامج conda لهذا الغرض، ولكن يمكنك استخدام أي طريقة تراها مناسبة لك. مع العلم أنني أستخدم نظام لينكس (WSL2 على ويندوز).

#create our test environment
(base) $ conda create -n wasm_test python=3.12 -y

# Now activate it
(base) $ conda activate wasm_test

الآن وقد تم إعداد بيئتنا، يمكننا تثبيت المكتبات والبرامج المطلوبة.

# 
#
(wasm_test) $ pip install jupyter nest-asyncio

الآن، اكتب الأمر jupyter notebook في موجه الأوامر. من المفترض أن يظهر لك دفتر Jupyter مفتوحًا في متصفحك. إذا لم يحدث ذلك تلقائيًا، فسترى على الأرجح شاشة مليئة بالمعلومات بعد كتابة الأمر. ستجد في أسفل الشاشة رابطًا (URL) انسخه والصقه في متصفحك لتشغيل دفتر Jupyter.

سيكون عنوان URL الخاص بك مختلفًا عن عنوان URL الخاص بي، ولكن يجب أن يبدو كالتالي:

http://127.0.0.1:8888/tree?token=3b9f7bd07b6966b41b68e2350721b2d0b6f388d248cc69da

مثال برمجي 1 – ما يعادل برنامج Hello World باستخدام Pyodide

لنبدأ بأبسط مثال ممكن. أسهل طريقة لإضافة مكتبة Pyodide إلى صفحة HTML الخاصة بك هي عبر شبكة توصيل المحتوى (CDN). ثم نطبع النص “Hello World!”.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Hello, World! with Pyodide</title>
</head>
<body>
  <h1>Python Hello, World!</h1>
  <button id="runCode">Run Python Code</button>
  <pre id="result"></pre>

  <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
  <script>
    async function runHelloWorld() {
      const pyodide = await loadPyodide();
      const output = await pyodide.runPythonAsync(`
        print("Hello, World!")
      `);
      document.getElementById('result').textContent = output || "Check the console for output.";
    }
    document.getElementById('runCode').addEventListener('click', runHelloWorld);
  </script>
</body>
</html>

قمت بتشغيل الكود أعلاه في محرر W3Schools HTML TryIt وحصلت على هذه النتيجة:

عند النقر على الزر، يقوم Pyodide بتشغيل كود بايثون الذي يطبع عبارة “Hello, World!”. لا نرى أي شيء مطبوع على الشاشة، حيث تتم طباعته في وحدة التحكم افتراضيًا. سنقوم بتصحيح ذلك في المثال التالي.

مثال برمجي 2 – طباعة المخرجات في المتصفح

في مثالنا الثاني، سنستخدم مكتبة Pyodide لتشغيل كود بايثون في المتصفح، والذي سيُجري عملية حسابية بسيطة. في هذه الحالة، سنحسب الجذر التربيعي للعدد 16 ونعرض النتيجة في المتصفح.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Pyodide Example</title>
</head>
<body>
  <h1>Running Python in the Browser with Pyodide</h1>
  <button id="runPython">Run Python Code</button>
  <pre id="output"></pre>

  <!-- Load Pyodide from the CDN -->
  <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
  <script>
    async function main() {
      // Load Pyodide
      const pyodide = await loadPyodide();
      document.getElementById('runPython').addEventListener('click', async () => {
        // Run a simple Python command
        let result = await pyodide.runPythonAsync(`
          import math
          math.sqrt(16)
        `);
        document.getElementById('output').textContent = 'Square root of 16 is: ' + result;
      });
    }
    main();
  </script>
</body>
</html>

عند تشغيل الكود أعلاه في متصفح W3Schools TryIT، حصلت على هذه النتيجة:

مثال برمجي 3 – استدعاء دوال بايثون من جافا سكريبت

ومن الميزات القيّمة والقوية الأخرى لاستخدام Pyodide القدرة على استدعاء دوال Python من JavaScript والعكس صحيح.

في هذا المثال، نقوم بإنشاء دالة بايثون تقوم بعملية حسابية بسيطة – حساب مضروب العدد – ونقوم باستدعائها من كود جافا سكريبت.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Call Python from JavaScript</title>
</head>
<body>
  <h1>Calculate the Factorial of a Number</h1>
  <input type="number" id="numberInput" placeholder="Enter a number" />
  <button id="calcFactorial">Calculate Factorial</button>
  <pre id="result"></pre>

  <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
  <script>
    let pyodideReadyPromise = loadPyodide();

    async function calculateFactorial() {
      const pyodide = await pyodideReadyPromise;
      // Define a Python function for calculating factorial
      await pyodide.runPythonAsync(`
        def factorial(n):
            if n == 0:
                return 1
            else:
                return n * factorial(n - 1)
      `);
      // Get the input value from the HTML form
      const n = Number(document.getElementById('numberInput').value);
      // Call the Python factorial function
      let result = pyodide.globals.get("factorial")(n);
      document.getElementById('result').textContent = `Factorial of ${n} is ${result}`;
    }
    
    document.getElementById('calcFactorial').addEventListener('click', calculateFactorial);
  </script>
</body>
</html>

هذه عينة من المخرجات عند تشغيل البرنامج على موقع W3Schools. لن أُدرج قسم الكود هذه المرة، بل المخرجات فقط.

مثال برمجي 4 – استخدام مكتبات بايثون، على سبيل المثال NumPy

تكمن قوة لغة بايثون في نظامها البيئي الغني بالمكتبات. باستخدام Pyodide، يمكنك استيراد واستخدام مكتبات شائعة مثل NumPy لإجراء العمليات الحسابية.

يوضح المثال التالي كيفية إجراء عمليات على المصفوفات باستخدام NumPy في المتصفح. يتم تحميل مكتبة NumPy باستخدام الدالة pyodide.loadPackage.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>NumPy in the Browser</title>
</head>
<body>
  <h1>Matrix Multiplication with NumPy</h1>
  <button id="runNumPy">Run NumPy Code</button>
  <pre id="numpyResult"></pre>

  <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
  <script>
    async function runNumPyCode() {
      // Load the Pyodide interpreter
      const pyodide = await loadPyodide();
      
      // Load the NumPy package before using it
      await pyodide.loadPackage("numpy");
      
      // Run Python code to perform a matrix multiplication
      let result = await pyodide.runPythonAsync(`
        import numpy as np
        A = np.array([[1, 2], [3, 4]])
        B = np.array([[2, 0], [1, 2]])
        C = np.matmul(A, B)
        C.tolist()  # Convert the numpy array to a Python list for display
      `);
      
      // Convert the Python result (PyProxy) to a native JavaScript object
      // so it displays properly
      document.getElementById('numpyResult').textContent =
        'Matrix Multiplication Result: ' + JSON.stringify(result.toJs());
    }
    
    // Set up the event listener for the button
    document.getElementById('runNumPy').addEventListener('click', runNumPyCode);
  </script>
</body>
</html>

مثال برمجي 5 – استخدام مكتبات بايثون، على سبيل المثال matplotlib

من المزايا القوية الأخرى لتشغيل بايثون في المتصفح إمكانية إنشاء الرسوم البيانية. باستخدام Pyodide، يمكنك الاستعانة بمكتبات واجهة المستخدم الرسومية مثل Matplotlib لإنشاء الرسوم البيانية ديناميكيًا. إليك كيفية إنشاء وعرض رسم بياني بسيط على عنصر Canvas.

في هذا المثال، نقوم بإنشاء رسم بياني تربيعي (y = x²) باستخدام Matplotlib، وحفظ الصورة في مخزن مؤقت في الذاكرة كملف PNG، وتشفيرها كسلسلة base64 قبل عرضها.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Matplotlib in the Browser</title>
</head>
<body>
  <h1>Interactive Plot with Matplotlib</h1>
  <button id="plotGraph">Generate Plot</button>
  <img id="plotImage" alt="Plot will appear here" />

  <script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js"></script>
  <script>
    async function generatePlot() {
      // Load the Pyodide interpreter
      const pyodide = await loadPyodide();
      
      // Load the matplotlib package before using it
      await pyodide.loadPackage("matplotlib");
      
      // Run Python code that creates a plot and returns it as a base64 encoded PNG image
      let imageBase64 = await pyodide.runPythonAsync(`
        import matplotlib.pyplot as plt
        import io, base64
        
        # Create a simple plot
        plt.figure()
        plt.plot([0, 1, 2, 3], [0, 1, 4, 9], marker='o')
        plt.title("Quadratic Plot")
        plt.xlabel("X Axis")
        plt.ylabel("Y Axis")
        
        # Save the plot to a bytes buffer
        buf = io.BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        
        # Encode the image in base64 and return it
        base64.b64encode(buf.read()).decode('ascii')
      `);
      
      // Set the src attribute of the image element to display the plot
      document.getElementById('plotImage').src = "data:image/png;base64," + imageBase64;
    }
    
    // Add event listener to the button to generate the plot
    document.getElementById('plotGraph').addEventListener('click', generatePlot);
  </script>
</body>
</html>

مثال برمجي رقم 6: تشغيل بايثون في عامل ويب

بالنسبة للتطبيقات الأكثر تعقيدًا، أو عندما تحتاج إلى ضمان عدم إعاقة العمليات الحسابية المكثفة لواجهة المستخدم الرئيسية، يمكنك تشغيل Pyodide ضمن Web Worker. تتيح لك Web Workers تشغيل البرامج النصية في سلاسل عمليات خلفية، مما يحافظ على استجابة تطبيقك.

فيما يلي مثال على كيفية إعداد Pyodide في Web Worker. نقوم بإجراء عملية حسابية ومحاكاة تشغيلها لفترة من الوقت عن طريق إضافة تأخيرات باستخدام دالة ()sleep. كما نعرض عدادًا يتم تحديثه باستمرار يُظهر أن واجهة المستخدم الرئيسية تعمل وتستجيب بشكل طبيعي.

سنحتاج إلى ثلاثة ملفات لهذا الغرض: ملف index.html وملفان JavaScript.

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Pyodide Web Worker Example</title>
</head>
<body>
  <h1>Running Python in a Web Worker</h1>
  <button id="startWorker">Start Computation</button>
  <p id="status">Status: Idle</p>
  <pre id="workerOutput"></pre>
  <script src="main.js"></script>
</body>
</html>

worker.js

// Load Pyodide from the CDN inside the worker
self.importScripts("https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js");

async function initPyodide() {
  self.pyodide = await loadPyodide();
  // Inform the main thread that Pyodide has been loaded
  self.postMessage("Pyodide loaded in Worker");
}

initPyodide();

// Listen for messages from the main thread
self.onmessage = async (event) => {
  if (event.data === 'start') {
    // Execute a heavy computation in Python within the worker.
    // The compute function now pauses for 0.5 seconds every 1,000,000 iterations.
    let result = await self.pyodide.runPythonAsync(`
import time
def compute():
    total = 0
    for i in range(1, 10000001):  # Loop from 1 to 10,000,000
        total += i
        if i % 1000000 == 0:
            time.sleep(0.5)  # Pause for 0.5 seconds every 1,000,000 iterations
    return total
compute()
    `);
    // Send the computed result back to the main thread
    self.postMessage("Computed result: " + result);
  }
};

main.js

// Create a new worker from worker.js
const worker = new Worker('worker.js');

// DOM elements to update status and output
const statusElement = document.getElementById('status');
const outputElement = document.getElementById('workerOutput');
const startButton = document.getElementById('startWorker');

let timerInterval;
let secondsElapsed = 0;

// Listen for messages from the worker
worker.onmessage = (event) => {
  // Append any message from the worker to the output
  outputElement.textContent += event.data + "\n";

  if (event.data.startsWith("Computed result:")) {
    // When computation is complete, stop the timer and update status
    clearInterval(timerInterval);
    statusElement.textContent = `Status: Completed in ${secondsElapsed} seconds`;
  } else if (event.data === "Pyodide loaded in Worker") {
    // Update status when the worker is ready
    statusElement.textContent = "Status: Worker Ready";
  }
};

// When the start button is clicked, begin the computation
startButton.addEventListener('click', () => {
  // Reset the display and timer
  outputElement.textContent = "";
  secondsElapsed = 0;
  statusElement.textContent = "Status: Running...";
  
  // Start a timer that updates the main page every second
  timerInterval = setInterval(() => {
    secondsElapsed++;
    statusElement.textContent = `Status: Running... ${secondsElapsed} seconds elapsed`;
  }, 1000);
  
  // Tell the worker to start the heavy computation
  worker.postMessage('start');
});

لتشغيل هذا الكود، أنشئ الملفات الثلاثة المذكورة أعلاه وضعها في نفس المجلد على جهازك المحلي. ثم اكتب الأمر التالي في ذلك المجلد.

$ python -m http.server 8000

الآن، اكتب عنوان URL هذا في متصفحك.

http://localhost:8000/index.html

يجب أن ترى شاشة كهذه.

الآن، إذا ضغطت على زر <strong>Start Computation</strong>، فسترى عدادًا معروضًا على الشاشة، يبدأ من 1 ويتزايد بمقدار 1 كل ثانية حتى يكتمل الحساب ويتم عرض النتيجة النهائية – حوالي 5 ثوانٍ إجمالاً.

هذا يدل على أن منطق الواجهة الأمامية والحسابات لا تتقيد بالعمل الذي يتم تنفيذه بواسطة كود بايثون الموجود خلف الزر.

مثال برمجي رقم 7: تشغيل لوحة بيانات بسيطة

في مثالنا الأخير، سأوضح لكم كيفية تشغيل لوحة بيانات بسيطة مباشرةً في متصفحكم. ستكون بياناتنا المصدرية عبارة عن بيانات مبيعات اصطناعية في ملف CSV.

نحتاج إلى ثلاثة ملفات لهذا الغرض، ويجب أن تكون جميعها في نفس المجلد.

sales_data.csv

Date,Category,Region,Sales
2021-01-01,Books,West,610.57
2021-01-01,Beauty,West,2319.0
2021-01-01,Electronics,North,4196.76
2021-01-01,Electronics,West,1132.53
2021-01-01,Home,North,544.12
2021-01-01,Beauty,East,3243.56
2021-01-01,Sports,East,2023.08
2021-01-01,Fashion,East,2540.87
2021-01-01,Automotive,South,953.05
2021-01-01,Electronics,North,3142.8
2021-01-01,Books,East,2319.27
2021-01-01,Sports,East,4385.25
2021-01-01,Beauty,North,2179.01
2021-01-01,Fashion,North,2234.61
2021-01-01,Beauty,South,4338.5
2021-01-01,Beauty,East,783.36
2021-01-01,Sports,West,696.25
2021-01-01,Electronics,South,97.03
2021-01-01,Books,West,4889.65

index.html

هذه هي واجهة المستخدم الرسومية الرئيسية للوحة التحكم الخاصة بنا.

&lt;!DOCTYPE html>
&lt;html lang="en">
&lt;head>
    &lt;meta charset="UTF-8">
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0">
    &lt;title>Pyodide Sales Dashboard&lt;/title>
    &lt;style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 20px; }
        h1 { color: #333; }
        input { margin: 10px; }
        select, button { padding: 10px; font-size: 16px; margin: 5px; }
        img { max-width: 100%; display: block; margin: 20px auto; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }

        th { background-color: #f4f4f4; }
  .sortable th {
   cursor: pointer;
   user-select: none;
  }
  .sortable th:hover {
   background-color: #e0e0e0;
  }
    &lt;/style>
    &lt;script src="https://cdn.jsdelivr.net/pyodide/v0.23.4/full/pyodide.js">&lt;/script>
&lt;/head>
&lt;body>

    &lt;h1>📊 Pyodide Sales Dashboard&lt;/h1>

    &lt;input type="file" id="csvUpload" accept=".csv">
    
    &lt;label for="metricSelect">Select Sales Metric:&lt;/label>
    &lt;select id="metricSelect">
        &lt;option value="total_sales">Total Sales&lt;/option>
        &lt;option value="category_sales">Sales by Category&lt;/option>
        &lt;option value="region_sales">Sales by Region&lt;/option>
        &lt;option value="monthly_trends">Monthly Trends&lt;/option>
    &lt;/select>

    &lt;br>&lt;br>
    &lt;button id="analyzeData">Analyze Data&lt;/button>

    &lt;h2>📈 Sales Data Visualization&lt;/h2>
    &lt;img id="chartImage" alt="Generated Chart" style="display: none">
    &lt;h2>📊 Sales Data Table&lt;/h2>
    &lt;div id="tableOutput">&lt;/div>

    &lt;script src="main.js">&lt;/script>

&lt;/script>

&lt;/body>
&lt;/html>

main.js

يحتوي هذا على كود بايثون الرئيسي الخاص بنا pyodide.

async function loadPyodideAndRun() {
  const pyodide = await loadPyodide();
  await pyodide.loadPackage(["numpy", "pandas", "matplotlib"]);
  
  document.getElementById("analyzeData").addEventListener("click", async () => {
    const fileInput = document.getElementById("csvUpload");
    const selectedMetric = document.getElementById("metricSelect").value;
    const chartImage = document.getElementById("chartImage");
    const tableOutput = document.getElementById("tableOutput");
    
    if (fileInput.files.length === 0) {
      alert("Please upload a CSV file first.");
      return;
    }

    // Read the CSV file
    const file = fileInput.files[0];
    const reader = new FileReader();
    reader.readAsText(file);
    
    reader.onload = async function (event) {
      const csvData = event.target.result;
      
      await pyodide.globals.set('csv_data', csvData);
      await pyodide.globals.set('selected_metric', selectedMetric);
      
      const pythonCode = 
        'import sys\n' +
        'import io\n' +
        'import numpy as np\n' +
        'import pandas as pd\n' +
        'import matplotlib\n' +
        'matplotlib.use("Agg")\n' +
        'import matplotlib.pyplot as plt\n' +
        'import base64\n' +
        '\n' +
        '# Capture output\n' +
        'output_buffer = io.StringIO()\n' +
        'sys.stdout = output_buffer\n' +
        '\n' +
        '# Read CSV directly using csv_data from JavaScript\n' +
        'df = pd.read_csv(io.StringIO(csv_data))\n' +
        '\n' +
        '# Ensure required columns exist\n' +
        'expected_cols = {"Date", "Category", "Region", "Sales"}\n' +
        'if not expected_cols.issubset(set(df.columns)):\n' +
        '    print("❌ CSV must contain \'Date\', \'Category\', \'Region\', and \'Sales\' columns.")\n' +
        '    sys.stdout = sys.__stdout__\n' +
        '    exit()\n' +
        '\n' +
        '# Convert Date column to datetime\n' +
        'df["Date"] = pd.to_datetime(df["Date"])\n' +
        '\n' +
        'plt.figure(figsize=(12, 6))\n' +
        '\n' +
        'if selected_metric == "total_sales":\n' +
        '    total_sales = df["Sales"].sum()\n' +
        '    print(f"💰 Total Sales: ${total_sales:,.2f}")\n' +
        '    # Add daily sales trend for total sales view\n' +
        '    daily_sales = df.groupby("Date")["Sales"].sum().reset_index()\n' +
        '    plt.plot(daily_sales["Date"], daily_sales["Sales"], marker="o")\n' +
        '    plt.title("Daily Sales Trend")\n' +
        '    plt.ylabel("Sales ($)")\n' +
        '    plt.xlabel("Date")\n' +
        '    plt.xticks(rotation=45)\n' +
        '    plt.grid(True, linestyle="--", alpha=0.7)\n' +
        '    # Show top sales days in table\n' +
        '    table_data = daily_sales.sort_values("Sales", ascending=False).head(10)\n' +
        '    table_data["Sales"] = table_data["Sales"].apply(lambda x: f"${x:,.2f}")\n' +
        '    print("&lt;h3>Top 10 Sales Days&lt;/h3>")\n' +
        '    print(table_data.to_html(index=False))\n' +
        'elif selected_metric == "category_sales":\n' +
        '    category_sales = df.groupby("Category")["Sales"].agg([\n' +
        '        ("Total Sales", "sum"),\n' +
        '        ("Average Sale", "mean"),\n' +
        '        ("Number of Sales", "count")\n' +
        '    ]).sort_values("Total Sales", ascending=True)\n' +
        '    category_sales["Total Sales"].plot(kind="bar", title="Sales by Category")\n' +
        '    plt.ylabel("Sales ($)")\n' +
        '    plt.xlabel("Category")\n' +
        '    plt.grid(True, linestyle="--", alpha=0.7)\n' +
        '    # Format table data\n' +
        '    table_data = category_sales.copy()\n' +
        '    table_data["Total Sales"] = table_data["Total Sales"].apply(lambda x: f"${x:,.2f}")\n' +
        '    table_data["Average Sale"] = table_data["Average Sale"].apply(lambda x: f"${x:,.2f}")\n' +
        '    print("&lt;h3>Sales by Category&lt;/h3>")\n' +
        '    print(table_data.to_html())\n' +
        'elif selected_metric == "region_sales":\n' +
        '    region_sales = df.groupby("Region")["Sales"].agg([\n' +
        '        ("Total Sales", "sum"),\n' +
        '        ("Average Sale", "mean"),\n' +
        '        ("Number of Sales", "count")\n' +
        '    ]).sort_values("Total Sales", ascending=True)\n' +
        '    region_sales["Total Sales"].plot(kind="barh", title="Sales by Region")\n' +
        '    plt.xlabel("Sales ($)")\n' +
        '    plt.ylabel("Region")\n' +
        '    plt.grid(True, linestyle="--", alpha=0.7)\n' +
        '    # Format table data\n' +
        '    table_data = region_sales.copy()\n' +
        '    table_data["Total Sales"] = table_data["Total Sales"].apply(lambda x: f"${x:,.2f}")\n' +
        '    table_data["Average Sale"] = table_data["Average Sale"].apply(lambda x: f"${x:,.2f}")\n' +
        '    print("&lt;h3>Sales by Region&lt;/h3>")\n' +
        '    print(table_data.to_html())\n' +
        'elif selected_metric == "monthly_trends":\n' +
        '    df["Month"] = df["Date"].dt.to_period("M")\n' +
        '    monthly_sales = df.groupby("Month")["Sales"].agg([\n' +
        '        ("Total Sales", "sum"),\n' +
        '        ("Average Sale", "mean"),\n' +
        '        ("Number of Sales", "count")\n' +
        '    ])\n' +
        '    monthly_sales["Total Sales"].plot(kind="line", marker="o", title="Monthly Sales Trends")\n' +
        '    plt.ylabel("Sales ($)")\n' +
        '    plt.xlabel("Month")\n' +
        '    plt.xticks(rotation=45)\n' +
        '    plt.grid(True, linestyle="--", alpha=0.7)\n' +
        '    # Format table data\n' +
        '    table_data = monthly_sales.copy()\n' +
        '    table_data["Total Sales"] = table_data["Total Sales"].apply(lambda x: f"${x:,.2f}")\n' +
        '    table_data["Average Sale"] = table_data["Average Sale"].apply(lambda x: f"${x:,.2f}")\n' +
        '    print("&lt;h3>Monthly Sales Analysis&lt;/h3>")\n' +
        '    print(table_data.to_html())\n' +
        '\n' +
        'plt.tight_layout()\n' +
        '\n' +
        'buf = io.BytesIO()\n' +
        'plt.savefig(buf, format="png", dpi=100, bbox_inches="tight")\n' +
        'plt.close()\n' +
        'img_data = base64.b64encode(buf.getvalue()).decode("utf-8")\n' +
        'print(f"IMAGE_START{img_data}IMAGE_END")\n' +
        '\n' +
        'sys.stdout = sys.__stdout__\n' +
        'output_buffer.getvalue()';

      const result = await pyodide.runPythonAsync(pythonCode);
      
      // Extract and display output with markers
      const imageMatch = result.match(/IMAGE_START(.+?)IMAGE_END/);
      if (imageMatch) {
        const imageData = imageMatch[1];
        chartImage.src = 'data:image/png;base64,' + imageData;
        chartImage.style.display = 'block';
        // Remove the image data from the result before showing the table
        tableOutput.innerHTML = result.replace(/IMAGE_START(.+?)IMAGE_END/, '').trim();
      } else {
        chartImage.style.display = 'none';
        tableOutput.innerHTML = result.trim();
      }
    };
  });
}

loadPyodideAndRun();

كما في المثال السابق، يمكنك تشغيل هذا البرنامج بالطريقة التالية. أنشئ الملفات الثلاثة وضعها في نفس المجلد على جهازك المحلي. في ذلك المجلد، اكتب الأمر التالي في موجه الأوامر:

$ python -m http.server 8000

الآن، اكتب عنوان URL هذا في متصفحك.

http://localhost:8000/index.html

في البداية، يجب أن تبدو شاشتك هكذا،

انقر على زر “اختيار ملف” وحدد ملف البيانات الذي أنشأته لإدخاله في لوحة التحكم. بعد ذلك، اختر مقياسًا مناسبًا من القائمة المنسدلة “تحديد مقياس المبيعات” وانقر على زر “تحليل البيانات”. بناءً على الخيارات التي تختار عرضها، سترى شيئًا مشابهًا لما يلي على شاشتك.

في هذه المقالة، شرحتُ كيفية تشغيل برامج بايثون داخل متصفحاتنا باستخدام Pyodide وWebAssembly، وعرضتُ عدة أمثلة توضح ذلك. وتحدثتُ عن دور WebAssembly كهدف تجميعي محمول وعالي الأداء يُوسّع إمكانيات المتصفح، وكيف يتحقق ذلك في بيئة بايثون باستخدام مكتبة Pyodide الخارجية.

آمل أن تدرك، بعد قراءة هذه المقالة، مدى قوة الجمع بين لغة بايثون، وPyodide، ومتصفح الويب.


اكتشاف المزيد من بايثون العربي

اشترك للحصول على أحدث التدوينات المرسلة إلى بريدك الإلكتروني.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *

Scroll to Top

اكتشاف المزيد من بايثون العربي

اشترك الآن للاستمرار في القراءة والحصول على حق الوصول إلى الأرشيف الكامل.

Continue reading