لماذا الشرطة السفلية قبل import في Go؟ شرح بالأمثلة

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

ما الذي يخفيه الشرطة السفلية في import؟ دليل لمن يريد فهم Go بشكل أعمق

\n\n

هل سبق لك أن فتحت كودًا لشخص آخر بلغة Go ورأيت سطرًا مثل import _ "github.com/lib/pq"؟ أول رد فعل — «هل هذا قانوني أصلاً؟ ألن يشتكي المترجم؟». مفاجأة: لن يشتكي. بل أكثر من ذلك، هذه واحدة من أكثر الميزات أناقة في اللغة، والتي غالبًا ما تثير أسئلة لدى المبتدئين. دعنا نفهم ما يفعله هذا الرمز الغامض للشرطة السفلية قبل الاستيراد في Go، وكيفية استخدامه بذكاء.

\n\n

على Stack Overflow، جمع هذا السؤال آلاف المشاهدات — وهذا ليس مفاجئًا. يبدو التركيب النحوي كخطأ مطبعي، لكن خلفه يكمن مفهوم عميق لـ تهيئة الحزم والآثار الجانبية. في هذه المقالة، لن نعطي تعريفًا جافًا فقط، بل سنعرض سيناريوهات حقيقية حيث لا يمكن الاستغناء عن _. هيا بنا!

\n\n

المشكلة: «استوردت ولكن لا أستخدم»

\n\n

في Go، هناك قاعدة صارمة: يجب استخدام كل حزمة مستوردة. وإلا لن يتم تجميع الكود. هذا من أجل النظافة والأداء — لا حمولة ميتة. لكن ماذا لو أردنا فقط تنفيذ كود الحزمة عند بدء البرنامج، دون استدعاء دوالها مباشرة؟ هنا يأتي دور blank identifier (المعرف الفارغ) — الرمز _.

\n\n

عندما تكتب:

\n
import _ "some/package"
\n

— فأنت تقول للمترجم: «أعلم أنني لن أستدعي دوال من هذه الحزمة، لكن من فضلك، نفذ دالة init() الخاصة بها». سينفذ Go التهيئة، لكنه لن يعطي خطأ «unused import».

\n\n

كيف تعمل init() ولماذا هذا مهم

\n\n

يمكن أن تحتوي كل حزمة في Go على دالة أو أكثر init(). يتم تنفيذها تلقائيًا عند استيراد الحزمة، قبل دالة main(). ترتيب التنفيذ محدد بدقة:

\n
    \n
  • أولاً، تتم تهيئة متغيرات الحزمة
  • \n
  • ثم يتم استدعاء init()
  • \n
  • إذا كانت الحزمة تستورد حزمًا أخرى — يتم تهيئتها أولاً
  • \n
\n\n

مثال للتوضيح. لننشئ حزمة config:

\n
// config/config.go\npackage config\n\nvar AppName string\n\nfunc init() {\n    AppName = "MyApp v1.0"\n    println("Config initialized:", AppName)\n}
\n\n

الآن في main.go يمكننا استيراد هذه الحزمة «بصمت»:

\n
// main.go\npackage main\n\nimport (\n    _ "example.com/project/config"\n    "fmt"\n)\n\nfunc main() {\n    fmt.Println("Main function started")\n}
\n\n

نتيجة التنفيذ:

\n
Config initialized: MyApp v1.0\nMain function started
\n\n

لاحظ: لم نستدعِ config.AppName — لقد استوردنا الحزمة بشرطة سفلية فقط، وتم تشغيل init() الخاص بها تلقائيًا. هذه هي الآلية الأساسية التي تُبنى عليها العديد من الحلول الواقعية.

\n\n

السيناريو الرئيسي: تسجيل برامج تشغيل قواعد البيانات

\n\n

المثال الأكثر شيوعًا لاستخدام import _ في كود الإنتاج هو تسجيل برامج تشغيل قواعد البيانات. تستخدم الحزمة القياسية database/sql نمط «المصنع»: يتم تسجيل جميع برامج التشغيل عبر sql.Register() في دوال init() الخاصة بها.

\n\n

انظر كيف يتم توصيل PostgreSQL (برنامج التشغيل lib/pq):

\n
import (\n    "database/sql"\n    _ "github.com/lib/pq" // يسجل برنامج التشغيل "postgres"\n)\n\nfunc main() {\n    db, err := sql.Open("postgres", "user=... dbname=...")\n    // ...\n}
\n\n

داخل lib/pq في init() مكتوب شيء مثل:

\n
func init() {\n    sql.Register("postgres", &Driver{})\n}
\n\n

بدون الاستيراد الفارغ، لن يتم تسجيل برنامج التشغيل، وسيعيد sql.Open("postgres", ...) خطأ «unknown driver». نفس الشيء ينطبق على MySQL و SQLite و Oracle وقواعد بيانات أخرى.

المدونات

توصيات الكتب