بايثون في التقنية المالية

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

· · تحديث · 27 دقائق قراءة

شعار بايثون فوق دفتر أستاذ متوازن ومخطط لتدفق المدفوعات
في هذا المقال

كل منتج تقنية مالية رأيته يُبنى في منطقتنا ينتهي به الحال وفيه بايثون في مكان ما. أحيانًا تكون هي الخلفية كلها. وأحيانًا تكون سكربت تسوية كتبه محلل مالي ذات مساء، ثم صار بعد عامين هو ما يسوّي مدفوعات التجار كل ليلة. وأحيانًا تكون نموذج كشف الاحتيال، أو مهام مستودع البيانات، أو الدفتر التفاعلي (notebook) الذي يجيب عن سؤال الجهة الرقابية قبل الموعد النهائي بساعة.

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

لماذا تهم بايثون في التقنية المالية

الطبقة المحيطة بالمال هي موطن بايثون

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

تهيمن بايثون على تلك الطبقة المحيطة. فهي لغة علم البيانات، والسكربتات، والربط بين الأنظمة، وأطر العمل (Django وFastAPI وFlask) التي تتيح لفريق صغير إطلاق منتج خاضع للرقابة في شهور لا سنوات. وحتى في الشركات التي كُتبت نواتها بلغة Java أو Go أو Rust، تكون نماذج المخاطر وأدوات التسوية ومنظومة التحليلات مكتوبة ببايثون في الغالب. لكنها ليست لغة محرك مطابقة الأوامر. والحجة الصادقة لبايثون في التقنية المالية هي حجة لبايثون في معظم النظام، لا كله.

السؤال عمليًا نادرًا ما يكون "بايثون أم لا". السؤال هو "أي جزء من النظام ينبغي أن يكون ببايثون، وأين بالضبط نرسم الخط".

لماذا السؤال أكثر حدة في منطقتنا

ثلاثة أمور تجعل هذه المنطقة مختلفة عن الأسواق التي كُتبت لها معظم النصائح عن بايثون.

أولًا، سرعة التغير الرقابي. أصدر البنك المركزي السعودي (SAMA) والمصرف المركزي الإماراتي (CBUAE) والبنك المركزي المصري (CBE) أطر ترخيص وشبكات مدفوعات فورية وأطر خدمات مصرفية مفتوحة خلال سنوات قليلة متقاربة. والمنظومة التقنية التي تتيح لك تغيير حد أو جدول رسوم أو صيغة تقرير في يوم واحد، مع اختبارات، تملك ميزة حقيقية.

ثانيًا، سطح التكامل. المحفظة في مصر تتحدث مع ميزة، ومع إنستاباي عبر بنك شريك، ومع فوري للإيداع النقدي، ومع مُحصّل بطاقات، ومع شركات الاتصالات؛ والمنتج السعودي يتحدث مع مدى وسداد وسريع ومجمّع للخدمات المصرفية المفتوحة. لا شيء من هذا برّاق، وكله مقيّد بالإدخال والإخراج (I/O) ومثقل بالمخططات ويتغير كثيرًا، وهذا بالضبط ما تجيده بايثون.

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

أين تُستخدم

كل ما في هذا القسم استخدام موثّق علنًا؛ لا شيء مختلق.

المدفوعات وتكاملات التجار

تحتفظ Stripe بمكتبة بايثون رسمية، stripe-python، وكثير من وثائقها مكتوب ببايثون؛ وتنشر Adyen وCheckout.com ومعظم مزودي خدمات الدفع العالميين حزم SDK أو أمثلة ببايثون. إقليميًا، توفر فوري وPaymob وTap وHyperPay وMoyasar وبوابات البنوك واجهات REST تغلّفها الفرق ببضع مئات من أسطر httpx: محوّل (adapter) رفيع لكل مزود خلف واجهة داخلية موحدة.

الوساطة والتداول وإدارة الثروات

تحدث مهندسو Robinhood علنًا عن تشغيل خلفيتهم ببايثون وDjango لسنوات قبل فصل بعض الأجزاء. واستخدم التمويل الكمي بايثون قبل أن تظهر كلمة "fintech" بزمن: وُلدت pandas داخل صندوق تحوّط، وتقوم NumPy تحت كل مكتبة تسعير واختبار رجعي تقريبًا، وما زالت مكاتب التداول الكمي تبني نماذجها الأولية في الدفاتر التفاعلية ولا تنقل إلى C++ أو Rust إلا المسارات الساخنة. وتطبيقات المستشار الآلي والتداول المرخصة في الخليج تميل إلى الشكل نفسه: بايثون لكل شيء عدا توجيه الأوامر.

المخاطر والاحتيال ومنصات البيانات

هنا لا منافس جديًا لبايثون: scikit-learn لنموذج الاحتيال الأول، وأشجار التعزيز المتدرج للثاني، وPyTorch لنماذج التسلسل فوق تاريخ المعاملات. ويقوم Apache Airflow، المكتوب ببايثون والمبني أصلًا في Airbnb، بجدولة المهام الليلية في نسبة كبيرة من منصات بيانات التقنية المالية؛ وdbt وPrefect وDagster مكتوبة ببايثون أيضًا. وكتبت Netflix مطولًا عن مدى تغلغل بايثون في أدوات بياناتها، والشكل نفسه، تنسيق ببايثون حول مستودع بيانات عمودي، هو ما تنتهي إليه معظم شركات الإقراض والمدفوعات.

خلفيات الويب والمكاتب الخلفية

يعمل Instagram على Django منذ يومه الأول، ووصف علنًا كيف وسّع ذلك إلى مئات الملايين من المستخدمين؛ وبُني Dropbox على بايثون ثم نشر قصة فحص الأنواع لملايين الأسطر منها باستخدام mypy. لا أحد منهما شركة تقنية مالية، لكنهما دليل الوجود على أن "بايثون لا تتوسع" اعتراض على المعمارية لا على اللغة. وعلى جانب المحاسبة، كُتب Odoo وERPNext، وهما من أكثر حزم تخطيط موارد المؤسسات مفتوحة المصدر انتشارًا، ببايثون، وتشغّل كثير من المكاتب الخلفية في المنطقة دفتر الأستاذ العام والرواتب على أحدهما.

نقاط القوة

سرعة التكرار حيث تتغير القواعد أسبوعيًا

يتغير جدول رسوم. تطلب الجهة الرقابية حقلًا جديدًا في التقرير الشهري. يغيّر بنك شريك رمز حالة. في التقنية المالية المنتج قواعد في معظمه، والقواعد تتبدل باستمرار. قوة بايثون في قصر المسافة بين "اتفقنا على التغيير في الاجتماع" و"صار مباشرًا ومختبَرًا"؛ فالمهندس المتمرس يستطيع عادة إنجاز تغيير جيد الاختبار في قاعدة عمل خلال ساعات.

منظومة البيانات هي الخندق الدفاعي

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

المقروئية ميزة امتثال

سيقرأ المدققون ولجان المخاطر وأحيانًا الفريق التقني لدى الجهة الرقابية شفرتك، أو على الأقل الأجزاء التي تحسب الفوائد والرسوم والحدود. وبايثون تُقرأ أقرب إلى المواصفة من معظم اللغات؛ فحساب رسوم مكتوب بـ Decimal وأسماء واضحة يمكن أن يراجعه شخص مالي لم يكتب شفرة قط. وهذا مهم حين يسأل بنك شريك لماذا يختلف تقرير تسويته بقرش واحد.

المكتبة القياسية تؤدي دورها

ثلاثة أجزاء من المكتبة القياسية تهم في التقنية المالية أكثر من أي مجال آخر. تمنحك decimal حسابًا دقيقًا بتقريب صريح. وتمنحك dataclasses مع frozen=True كائنات قيمة غير قابلة للتغيير شبه مجانًا. وتفحص وحدة typing، مع mypy أو pyright في التكامل المستمر، شكل طلب الدفع قبل الإنتاج. أضف uuid وhmac وsecrets وlogging وستتمكن من بناء جزء مفاجئ من خدمة مدفوعات دون حزمة خارجية واحدة.

المخاطر والمزالق

الفاصلة العائمة وثمن خطأ التقريب

أول خطأ في كل قاعدة شفرة للتقنية المالية هو نفسه: شخص ما خزّن المال أو حسبه بنوع float. يجتاز كل الاختبارات بأرقام مستديرة، ثم في الإنتاج تعطي رسوم 2.75% على 10.01 صافيًا لا يعود ليساوي الإجمالي. اضرب ذلك في بضع مئات الآلاف من المعاملات، وسيجد الفريق المالي نفسه يسوّي يدويًا في عطلة نهاية الأسبوع.

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

حساب المال بـ Decimal وتقريب صريح

from decimal import Decimal, ROUND_HALF_EVEN

CENTS = Decimal("0.01")


def to_money(value: str | int | Decimal, places: Decimal = CENTS) -> Decimal:
    """Turn an exact string, int or Decimal into a rounded money amount.

    Floats are rejected on purpose: Decimal(0.1) carries binary noise.

    >>> to_money("19.995")
    Decimal('20.00')
    >>> to_money("19.985")
    Decimal('19.98')
    >>> to_money(0.1)
    Traceback (most recent call last):
        ...
    TypeError: pass money as str, int or Decimal, never float
    """
    if isinstance(value, float):
        raise TypeError("pass money as str, int or Decimal, never float")
    return Decimal(value).quantize(places, rounding=ROUND_HALF_EVEN)


def split_fee(amount: Decimal, fee_rate: Decimal) -> tuple[Decimal, Decimal]:
    """Return (fee, net) such that fee + net == amount, always.

    >>> split_fee(to_money("100.00"), Decimal("0.0275"))
    (Decimal('2.75'), Decimal('97.25'))
    >>> fee, net = split_fee(to_money("10.01"), Decimal("0.0275"))
    >>> fee + net == Decimal("10.01")
    True
    """
    fee = to_money(amount * fee_rate)
    net = amount - fee  # derive the remainder; never round twice
    return fee, net


if __name__ == "__main__":
    import doctest

    doctest.testmod()
    print(to_money("0.1") + to_money("0.2"))  # 0.30, not 0.30000000000000004

تم الاختبار مع Python 3.12.

تُرفض قيم float عند الحدود بدل تحويلها بصمت، وتقرّب split_fee الرسوم ثم تشتق الصافي بالطرح؛ فتقريب الجانبين كلٍّ على حدة هو الطريقة التي تحصل بها على ثغرة بقرش واحد لا يجدها أحد.

الأنواع الديناميكية عند الحدود

داخل الوحدة البرمجية، الأنواع الديناميكية أداة إنتاجية. وعند الحدود، حيث يتحول JSON قادم من بنك شريك إلى كائن في نظامك، تصبح عبئًا: حقل حالة كان عددًا صحيحًا الشهر الماضي وصار نصًا هذا الشهر سيفشل بعد ثلاث خدمات بطريقة تبدو كخطأ في منطق الأعمال. تحقق من كل حمولة واردة مقابل مخطط صريح (pydantic أو attrs أو dataclasses مع فحوصات) وعامل الحقول المجهولة كأخطاء لا كضجيج.

الدفتر التفاعلي الذي صار إنتاجًا

لكل فريق بيانات واحد منها: دفتر تفاعلي حسب درجة مخاطر، ورُبط بمهمة cron "مؤقتًا"، وصار الآن يقرر من يحصل على قرض، بلا اختبار ولا إصدار ولا مراجعة، وكاتبه غادر الشركة. تجعل بايثون هذا الفشل سهلًا على نحو غير معتاد لأن اللغة نفسها تعمل في الدفتر التفاعلي وعلى الخادم. ارسم الخط صراحة: الدفاتر التفاعلية للاستكشاف؛ وكل ما يلمس مال العميل حزمة، مع اختبارات، داخل خط النشر.

المعمارية وأنماط التكامل

الكتلة الواحدة المعيارية أولًا

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

التكرار الآمن (idempotency) أينما تحرك المال

الشبكات تفشل، والعملاء يعيدون المحاولة، وتطبيقات الجوال تعيد الإرسال. الافتراض الآمن الوحيد أن كل طلب لتحريك المال سيصل مرتين على الأقل. ومفتاح التكرار الآمن، الذي يقدمه الطالب ويُخزَّن مع النتيجة، يحوّل التكرار إلى إعادة غير مؤذية. المعالج أدناه خالٍ من أطر العمل؛ وفي الإنتاج يكون المخزن فهرسًا فريدًا في PostgreSQL أو مفتاح Redis بمدة صلاحية، ويُمرَّر المفتاح إلى مزود الدفع ليزيل التكرار من جانبه أيضًا.

معالج دفع بتكرار آمن

from dataclasses import dataclass
from typing import Callable


@dataclass(frozen=True)
class PaymentResult:
    key: str
    status: str  # "captured" | "failed"
    provider_ref: str | None = None


class InFlight(Exception):
    """The same key is being processed right now; retry after a short delay."""


class DedupeStore:  # production: a unique index in PostgreSQL, or Redis SET NX with a TTL
    def __init__(self) -> None:
        self._rows: dict[str, PaymentResult | None] = {}  # None marks "in flight"

    def claim(self, key: str) -> bool:
        if key in self._rows:
            return False
        self._rows[key] = None
        return True

    def get(self, key: str) -> PaymentResult | None:
        return self._rows.get(key)

    def put(self, key: str, result: PaymentResult) -> None:
        self._rows[key] = result

    def release(self, key: str) -> None:
        self._rows.pop(key, None)


def handle_payment(key: str, charge: Callable[[str], str], store: DedupeStore) -> PaymentResult:
    if (done := store.get(key)) is not None:
        return done  # replay: same answer, money moved once
    if not store.claim(key):
        raise InFlight(key)  # concurrent duplicate: answer 409, never charge twice
    try:
        provider_ref = charge(key)  # forward the key so the provider dedupes as well
    except TimeoutError:
        store.release(key)  # outcome unknown: the client may retry with the same key
        raise
    except Exception:
        result = PaymentResult(key, "failed")
    else:
        result = PaymentResult(key, "captured", provider_ref)
    store.put(key, result)  # failures are final too; a new attempt needs a new key
    return result

تم الاختبار مع Python 3.12.

الفروع المهمة هي غير المعتادة. انتهاء المهلة يعني أن النتيجة مجهولة، فيُحرَّر الحجز ويجوز للعميل إعادة المحاولة بالمفتاح نفسه؛ ولأن المفتاح وصل إلى المزود أيضًا، لا يمكن لإعادة المحاولة أن تخصم مرتين. ويُخزَّن الفشل الحتمي نهائيًا، ويحصل التكرار المتزامن على خطأ "قيد المعالجة"، وتعيد الإعادة بعد النجاح النتيجة الأصلية دون لمس المزود.

دفتر الأستاذ مزدوج القيد مصدرًا للحقيقة

الأرصدة لا تُخزَّن؛ بل تُشتق. هذه الجملة الواحدة تنقذ شركات تقنية مالية أكثر من أي قاعدة معمارية أخرى. يسجل دفتر الأستاذ مزدوج القيد كل حركة قيدًا يجب أن تتوازن فيه سطور المدين والدائن، والقيود غير قابلة للتغيير، والتصحيحات قيود جديدة. وتُحسب الأرصدة من السطور وتُخزَّن مؤقتًا بحذر، مع إمكانية إعادة بناء الذاكرة المؤقتة دائمًا. والمثال أدناه مقصود أن يكون أدنى ما يمكن: لا قاعدة بيانات ولا جدول حسابات، فقط الثابت.

ترحيل أدنى في دفتر أستاذ مزدوج القيد

from dataclasses import dataclass, field
from datetime import datetime, timezone
from decimal import Decimal
from uuid import UUID, uuid4


class UnbalancedEntry(ValueError): pass


@dataclass(frozen=True, slots=True)
class Line:
    account: str  # e.g. "assets:psp_receivable", "liabilities:wallet:u123"
    debit: Decimal = Decimal("0")
    credit: Decimal = Decimal("0")

    def __post_init__(self) -> None:
        if self.debit < 0 or self.credit < 0:
            raise ValueError("amounts are non-negative; flip the side instead")
        if (self.debit > 0) == (self.credit > 0):
            raise ValueError("a line is a debit or a credit, never both or neither")


@dataclass(frozen=True, slots=True)
class Entry:
    """A balanced, immutable journal entry. Corrections are new entries, never edits."""
    description: str
    lines: tuple[Line, ...]
    currency: str = "EGP"
    id: UUID = field(default_factory=uuid4)
    posted_at: datetime = field(default_factory=lambda: datetime.now(timezone.utc))

    def __post_init__(self) -> None:
        if len(self.lines) < 2:
            raise UnbalancedEntry("an entry needs at least two lines")
        debits = sum((line.debit for line in self.lines), Decimal("0"))
        credits = sum((line.credit for line in self.lines), Decimal("0"))
        if debits != credits:
            raise UnbalancedEntry(f"debits {debits} != credits {credits}")


if __name__ == "__main__":
    topup = Entry(
        "Card top-up: 100.00 gross, 2.75 PSP fee",
        (
            Line("assets:psp_receivable", debit=Decimal("97.25")),
            Line("expenses:psp_fees", debit=Decimal("2.75")),
            Line("liabilities:wallet:u123", credit=Decimal("100.00")),
        ),
    )
    print(topup.id, "posted", len(topup.lines), "lines")
    try:
        Entry("broken", (Line("assets:cash", debit=Decimal("10")), Line("income:fees", credit=Decimal("9"))))
    except UnbalancedEntry as exc:
        print("rejected:", exc)

تم الاختبار مع Python 3.12.

في نظام حقيقي يقابل هذا جدولين في PostgreSQL، entries وlines، مع قيد يفرض ثابت التوازن نفسه، وأعمدة NUMERIC تُقرأ بوصفها Decimal، ودون صلاحية UPDATE أو DELETE على أي من الجدولين لدور التطبيق. تفرض بايثون القاعدة أولًا؛ وتفرضها قاعدة البيانات أخيرًا.

صندوق الصادر والطوابير والتسوية

ثلاثة أنماط أخرى تظهر في كل خلفية تقنية مالية ببايثون عملت عليها. صندوق الصادر المعاملاتي (transactional outbox): اكتب تغيير الأعمال وصف "حدث للإرسال" في المعاملة نفسها ودع عاملًا ينشره، حتى لا يقول دفتر الأستاذ "مدفوع" أبدًا بينما لم يخرج الـ webhook بصمت. الطوابير لكل ما هو بطيء: الإشعارات ونداءات اعرف عميلك وتوليد كشوف الحساب تمر عبر Celery أو Dramatiq أو RQ أو arq مع إعادة محاولات وطوابير للرسائل الميتة، وليس داخل معالج الطلب أبدًا. والتسوية اليومية: مهمة تسحب ملف تسوية كل مزود وتقارنه سطرًا بسطر مع دفتر الأستاذ؛ والفروق تذاكر لا تعديلات صامتة.

التكامل مع مزودي الدفع والبنوك الإقليمية

ما زال كثير من التكاملات الإقليمية يتضمن إسقاط ملفات CSV عبر SFTP، ونقاط نهاية SOAP، ونداءات رجعية تصل خارج الترتيب، ومخططات توقيع موثقة في ملف PDF. وبايثون تتعامل مع كل ذلك: paramiko لـ SFTP، وzeep لـ SOAP، وhmac للتوقيعات، وcsv وdecimal للملفات. غلّف كل مزود في محوّل خلف واجهة واحدة، وسجّل كل تبادل خام مع إخفاء الأسرار، وافترض أن كل نداء رجعي قد يتكرر.

لا يكتمل تكامل المدفوعات حين يعمل المسار السعيد. يكتمل حين يترك النداء الرجعي المكرر والمتأخر والمفقود دفتر الأستاذ صحيحًا جميعًا.

الأمان والامتثال

الأطر التي ستُقاس عليها

إذا لمست بيانات البطاقات فإن PCI DSS ينطبق، وأنفع قرار معماري هو إبقاء خدمات بايثون خارج النطاق بألا يلمسها رقم بطاقة أساسي أبدًا: رمّز عند الحافة بالحقول المستضافة أو حزمة SDK من مزود الدفع. وتحدد أطر الأمن السيبراني والإسناد الخارجي لدى SAMA في السعودية، ولوائح CBUAE في الإمارات (مع نظامين منفصلين في DIFC وADGM)، وقواعد الترخيص لدى CBE في مصر أين تستضيف وكيف تسجل وكيف تدير الأطراف الثالثة. وتضيف الخدمات المصرفية المفتوحة، على نموذج PSD2 في أوروبا والأطر المحلية في الخليج، إدارة الموافقات ومعايير أمان API فوق ذلك.

لا يفرض أي منها لغة. وكلها تنتج طلبات أدلة، وقاعدة شفرة بايثون مقروءة ومزودة بالأنواع ومختبَرة ومسجَّلة تجعل الإجابة عن تلك الطلبات أرخص.

إدارة الأسرار

لا تعيش الأسرار في settings.py، ولا في ملفات .env أُودعت "مؤقتًا"، ولا في متغيرات بيئة تستطيع أي عملية على الخادم قراءتها. تعيش في مدير أسرار (الخاص بمزود السحابة أو HashiCorp Vault)، وتُجلب عند بدء التشغيل بقطعة شفرة قصيرة ومدققة، وتُدوَّر وفق دليل تشغيل مكتوب قبل الحادث الأول. ومن فخاخ بايثون تحديدًا: تنقيح بـ print() يسرّب مفتاحًا إلى السجلات، وrepr() لكائن إعدادات في تقرير استثناء، و__str__ يتضمن الرمز.

سلسلة توريد الاعتماديات

هنا تكون فرق بايثون أضعف مما تظن في أغلب الأحيان.

  • ثبّت كل شيء مع البصمات (hashes). استخدم ملف قفل من Poetry أو uv أو pip-tools أو pylock.toml الموحد في PEP 751، وثبّت في الإنتاج بـ --require-hashes أو ما يعادله.
  • دقق باستمرار. يلتقط pip-audit والفاحصات المدمجة في GitHub وGitLab وسجلات السحابة الإصدارات ذات الثغرات المعروفة؛ شغّلها في التكامل المستمر ووفق جدول، لأن التنبيهات الجديدة تصدر ضد ملفات قفل قديمة.
  • أنتج قائمة مكونات البرمجيات (SBOM). تطلبها الجهات الرقابية والبنوك الشريكة بشكل متزايد، وأدوات CycloneDX تجعلها رخيصة متى كان ملف القفل صادقًا.
  • استخدم فهرسًا خاصًا أو وكيلًا لا يقدم إلا الحزم المسموح بها؛ فانتحال أسماء الحزم على PyPI حقيقي.
  • أبقِ المسار الحرج نحيلًا: الشفرة التي توقّع طلبات مزود الدفع وتحسب المال ينبغي أن تعتمد على أقل ما يمكن خارج المكتبة القياسية.

التعامل مع البيانات الشخصية وإقامة البيانات

بيانات التقنية المالية كلها تقريبًا بيانات شخصية: أسماء وأرقام هوية وطنية وأرقام هواتف وتاريخ معاملات. صنّفها، وشفّرها في حالة السكون بمفاتيح تتحكم فيها وفي أثناء النقل في كل مكان، وقلّلها؛ فمهمة التسوية نادرًا ما تحتاج إلى اسم العميل. وأخفِها في السجلات وبيئات ما قبل الإنتاج؛ ومرشح التسجيل الذي يحجب كل ما يشبه رقم بطاقة أو رقم هوية ينتمي إلى إيداعات الأسبوع الأول.

إقامة البيانات (data residency) قرار معماري من الدرجة الأولى هنا. فلدى الجهات الرقابية في السعودية والإمارات توقعات بتوطين البيانات المالية في كثير من الحالات، ويقيّد نظام حماية البيانات في مصر النقل عبر الحدود؛ تأكد من التفاصيل مع المستشار القانوني. تحتاج خدماتك وطوابيرك وقواعد بياناتك ونسخك الاحتياطية جميعًا إلى منطقة تستطيع الإشارة إليها، وقد تكون خدمة SaaS عالمية لتتبع الأخطاء ملاحظة تدقيق إذا كانت آثار المكدس تحتوي بيانات شخصية.

سجلات التدقيق

يجيب سجل التدقيق عن "من غيّر ماذا، ومتى، ومن أين، وكيف كان يبدو قبل ذلك". وهو للإلحاق فقط، ومنفصل عن سجلات التطبيق، ومهيكل بصيغة JSON، ويسجل الفاعل والموضوع معًا. نفّذه دالة واحدة تُستدعى من طبقة الخدمة، لا أسطر logger.info متناثرة، وأرسله إلى مخزن لا يستطيع دور التطبيق تعديله. وثبات دفتر الأستاذ وثبات سجل التدقيق هما الخاصيتان اللتان يختبرهما الفاحص أولًا.

الأداء والتوسع

قفل المفسر العام (GIL) بصراحة

يعني قفل المفسر العام في CPython أن خيطًا واحدًا ينفذ شفرة بايثون في كل لحظة داخل العملية الواحدة. وبالنسبة إلى حمل التقنية المالية المعتاد يهم هذا أقل مما يخشاه الناس، لأن ذلك الحمل ينتظر PostgreSQL أو مزود الدفع أو مزود اعرف عميلك أو Redis، والخيوط تحرر القفل أثناء الانتظار. لكنه يهم في العمل المقيد بالمعالج داخل بايثون الخالصة: تقييم نموذج صفًا صفًا، أو تحليل ملف كبير، أو تسلسل استجابات ضخمة.

والمشهد يتغير. جعل PEP 703 بناء CPython بلا قفل (free-threaded) ممكنًا، وقد شُحن بناءً اختياريًا منذ Python 3.13. ليس هو الافتراضي بعد، وكثير من امتدادات C ما زالت تلحق به، لذا لن أشغّل خدمة مدفوعات عليه اليوم؛ لكن حجة "بايثون لا تستطيع استخدام أنويتي" صار لها تاريخ انتهاء.

asyncio ونماذج العمال

ثمة ثلاثة نماذج نشر عاقلة، وتستخدم معظم الفرق اثنين منها على الأقل. العمال المتزامنون (Gunicorn بعدة عمليات) بسطاء ومتوقعون وما زالوا الصواب لتطبيقات Django التي تهيمن عليها استعلامات قاعدة البيانات. والعمال غير المتزامنين (Uvicorn مع FastAPI أو Django غير المتزامن) المبنيون على asyncio يتألقون حين يتفرع الطلب إلى عدة نداءات خارجية؛ والفخ نداء واحد حاجب يوقف حلقة الأحداث. وعمال الخلفية (Celery وDramatiq وRQ وarq) يأخذون كل ما لا يلزم حدوثه داخل الطلب ويعملون أيضًا ضغطًا عكسيًا: حين يبطئ مزود اعرف عميلك ينمو الطابور وتبقى واجهة API قائمة. راقب عمق الطابور وعمره، واجعل كل مهمة آمنة التكرار، لأن الوسيط سيسلّم بعضها مرتين.

العمليات المتعددة، لا الخيوط، هي الطريقة لاستخدام الأنوية المتعددة في CPython؛ شغّل عدة عمال لكل حاوية أو عدة حاويات لكل عقدة.

تجميع اتصالات PostgreSQL

تحتفظ كل عملية بايثون باتصالاتها الخاصة بقاعدة البيانات، ومع العمال غير المتزامنين تستطيع العملية الواحدة فتح كثير منها؛ ودون مجمّع ستستنفد حد اتصالات PostgreSQL قبل معالجها بكثير. شغّل PgBouncer (أو ما يعادله لدى مزود السحابة) في وضع المعاملة أمام قاعدة البيانات، واضبط حجم مجمّعات التطبيق بتحفظ، وأبقِ المعاملات قصيرة: لا نداء لمزود دفع داخل معاملة مفتوحة أبدًا. يوفر psycopg 3 وasyncpg مجمّعات؛ والاتصالات الدائمة في Django مع PgBouncer هي التركيبة المعتادة.

مخارج الطوارئ: PyPy وCython وRust

حين يكون المسار الساخن مقيدًا بالمعالج فعلًا، لديك خيارات قبل إعادة كتابة الخدمة. اتجه إلى العمليات المتجهة بـ NumPy أو pandas، أو انقل الحساب إلى SQL؛ وهذا يصلح معظم مهام التسوية والتقارير. استخدم Cython أو امتداد C لحلقة ضيقة. اكتب امتداد Rust عبر PyO3 وmaturin: نواة pydantic وpolars وruff وuv كلها Rust تحت سطح بايثون، وكتابة وحدة Rust صغيرة لفحص توقيع أو محلل صارت أمرًا روتينيًا. أما PyPy، المفسر ذو الترجمة الفورية، فيستطيع تسريع أحمال بايثون الخالصة، لكن توافق امتدادات C وقلة الألفة التشغيلية تجعله خيارًا هامشيًا للخدمات الخاضعة للرقابة.

أين بايثون خيار ضعيف

لن أختار بايثون لمحرك مطابقة في بورصة، أو معالج تغذية بيانات السوق، أو بوابة مخاطر يجب أن تجيب في ميكروثوانٍ، أو طبقة HSM أو أجهزة نقاط البيع ذات ميزانيات كمون صارمة، أو أي شيء يكون فيه الكمون الذيلي المتوقع تحت حمل المعالج هو المنتج نفسه. تلك مكانها Rust أو Go أو C++ أو Java، وعلى فريق بايثون أن يكون مرتاحًا لبناء خدمة صغيرة بلغة أخرى حين يتطلب الحمل ذلك.

الحمل ملاءمة بايثون ملاحظات
واجهات API للتطبيقات والتجار ممتازة مقيد بالإدخال والإخراج؛ غير متزامن أو متعدد العمليات
تنسيق المدفوعات ومحوّلات مزودي الدفع ممتازة التكرار الآمن أهم من السرعة
خدمة دفتر الأستاذ على PostgreSQL جيدة قاعدة البيانات تقوم بالعمل الثقيل
تقييم الاحتيال (دفعات) ممتازة NumPy وpandas ومكتبات النماذج
تقييم الاحتيال (مباشر دون 10 مللي ثانية) مقبولة قدّم النماذج من بيئة تشغيل مترجمة
التسوية والتقارير ممتازة pandas أو SQL
محرك المطابقة والتداول عالي التردد ضعيفة Rust أو C++ أو Java
تكامل الأجهزة في الزمن الحقيقي الصارم ضعيفة غير مناسبة

الفريق والتوظيف في المنطقة

توافر المواهب

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

الفرق البعيدة والموزعة

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

إشارات المقابلة المهمة في التقنية المالية

الإشارات التي أمنحها أكبر وزن، بالترتيب:

  1. هل يستطيع أن يشرح، دون تلميح، لماذا المال ليس float وماذا تعني أوضاع تقريب Decimal؟ هذا السؤال الواحد يفصل من عملوا قرب المال عمن لم يعملوا.
  2. هل يلجأ إلى التكرار الآمن والمعاملات حين يُطلب منه تصميم نقطة نهاية "إرسال أموال"، أم يبدأ بمخطط الروابط؟
  3. هل عاش مع فاحص أنواع وملف قفل وخط تكامل مستمر؟
  4. هل يستطيع قراءة خطة EXPLAIN في PostgreSQL والتفكير في مجمّع اتصالات؟
  5. هل يفهم أن السجلات قد تحتوي بيانات شخصية وأن print() في الإنتاج خطأ أمني؟

معلومات أطر العمل (Django مقابل FastAPI مقابل Flask) إشارة ضعيفة؛ فمن يملك الخمس أعلاه سيتعلم إطار عمل في أسبوعين.

رفع مهارات فريق قائم

تأتي فرق كثيرة في المنطقة من خلفيات PHP أو Java أو .NET، أو من علم البيانات لا الهندسة، والانتقال إلى بايثون الإنتاجية أسرع من العكس. وأعلى الاستثمارات أثرًا، في خبرتي، فاحص أنواع صارم في التكامل المستمر، وثقافة مراجعة تعامل شفرة التعامل مع المال كفئة تغيير مختلفة، ودورة داخلية قصيرة عن Decimal والتكرار الآمن والقيد المزدوج.

إطار اتخاذ القرار

اختر بايثون حين

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

تجنب بايثون، أو اعزلها، حين

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

مصفوفة القرار

قيّم كل صف من 1 (ضعيف) إلى 5 (ممتاز) لوضعك أنت؛ والدرجات أدناه تقدير توضيحي لمنتج مدفوعات إقليمي معتاد، لا معيار قياس.

المعيار Python Go Java أو Kotlin PHP (Laravel) Node.js (TypeScript)
الوقت حتى منتج أولي مرخص 5 3 3 5 4
تكامل البيانات والمخاطر وتعلم الآلة 5 2 3 1 2
الإنتاجية الخام لكل نواة 2 5 4 2 3
الكمون الذيلي المتوقع 2 5 4 2 3
عمق التوظيف في مصر 5 2 4 5 4
عمق التوظيف في السعودية والإمارات 4 2 4 3 4
المقروئية للمدققين 5 4 3 3 3
انضباط سلسلة التوريد افتراضيًا 3 4 4 3 2
نشر ممل ومفهوم جيدًا 4 5 5 4 4

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

الهجين الذي يفوز عادة

لمعظم شركات التقنية المالية في المنطقة: بايثون لطبقة التطبيق ومنصة البيانات والأدوات؛ وPostgreSQL مصدرًا للحقيقة؛ وبضع خدمات بـ Go أو Rust حيث يتطلب الكمون ذلك. الخطأ نادرًا ما يكون اختيار بايثون؛ الخطأ اختيار بايثون دون الانضباط الذي يجعلها آمنة للمال.

الأسئلة الشائعة

هل بايثون سريعة بما يكفي لبوابة دفع؟

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

هل نستخدم Django أم FastAPI لخلفية تقنية مالية؟

Django حين يكون للمنتج سطح إدارة ومكتب خلفي كبير ويكون الحمل مقيدًا بقاعدة البيانات في معظمه. وFastAPI حين تكون الخدمة طبقة تكامل تبدأ بواجهة API وتتفرع إلى نداءات خارجية كثيرة. وتشغّل فرق كثيرة الاثنين: Django للمكتب الخلفي ودفتر الأستاذ، وFastAPI عند الحافة. لن يصنع أي من الخيارين المنتج أو يكسره؛ أما Decimal والتكرار الآمن وتصميم دفتر الأستاذ فستفعل.

كيف نتعامل مع المال بأمان في بايثون؟

لا تستخدم float أبدًا. مثّل المبالغ بـ Decimal أو وحدات صغرى صحيحة، واحمل العملة مع المبلغ، وقرّب مرة واحدة بوضع مسمّى في مكان معلوم، واجعل دفتر الأستاذ مزدوج القيد وغير قابل للتغيير. تحقق من كل مبلغ عند الحدود، وخزّن المبالغ في أعمدة NUMERIC في PostgreSQL، واكتب اختبارات تؤكد أن الرسوم زائد الصافي تساوي الإجمالي دائمًا.

هل تستطيع بايثون اجتياز تدقيق PCI DSS أو تدقيق بنك مركزي؟

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

هل توظيف مهندسي بايثون صعب في مصر والسعودية والإمارات؟

توظيف مهندسي بايثون سهل نسبيًا، خصوصًا في مصر. أما توظيف مهندسي بايثون عملوا مع المال ويفهمون التكرار الآمن ويستطيعون الدفاع عن تصميم دفتر أستاذ فصعب في كل مكان، بما فيه هنا. وظّف على الأساسيات، وضع المهندسين الجدد مع فريقي التسوية والمخاطر، وتوقع أن تنمّي خبرة التقنية المالية داخليًا لا أن تشتريها.

الخلاصات الرئيسية

  • الموطن الطبيعي لبايثون في التقنية المالية هو الطبقة المحيطة بالمال: التكاملات والمخاطر والبيانات والتقارير والمكتب الخلفي؛ ويمكن أن تكون النواة بايثون أيضًا، مع الانضباط.
  • المال ليس float أبدًا. استخدم Decimal أو وحدات صغرى صحيحة، وقرّب مرة واحدة بوضع صريح، واشتق البواقي بدل التقريب مرتين.
  • كل نقطة نهاية تحرك المال آمنة التكرار، بمفتاح من الطالب، يُمرَّر إلى المزود.
  • تُشتق الأرصدة من دفتر أستاذ مزدوج القيد غير قابل للتغيير؛ ويُفرض الثابت في بايثون ثم مرة أخرى في PostgreSQL.
  • نادرًا ما يقيّد GIL أحمال التقنية المالية المقيدة بالإدخال والإخراج؛ استخدم العمليات، وasyncio حيث يكثر التفرع، والطوابير لكل ما هو بطيء، ومجمّعًا أمام PostgreSQL.
  • نظافة سلسلة التوريد (ملفات قفل بالبصمات، وتدقيق مستمر، وSBOM، وفهرس خاص) هي حيث تخفق فرق بايثون أمام الفاحص في أغلب الأحيان.
  • إقامة البيانات وتقليل البيانات الشخصية قراران معماريان في السعودية والإمارات ومصر، لا أفكار لاحقة.
  • وظّف على الأساسيات لا على معلومات أطر العمل، واختر بايثون حين يكون المنتج قواعد وتكاملات؛ واعزلها حيث يكون الكمون بالميكروثانية هو المنتج.