خانه / آموزش‌ها / آموزش ماژول Struct و داده‌های باینری در پایتون

آموزش ماژول Struct و داده‌های باینری در پایتون

🐍 HomeOfPython
|
📅 1404/10/25

ساختار داده‌های باینری

سطح مقدماتی (Beginner Level)

در بسیاری از پروژه‌های پایتون، شما با داده‌های سطح بالا (High-level) مانند لیست‌ها، دیکشنری‌ها و رشته‌ها کار می‌کنید. اما زمانی که نیاز دارید با فایل‌های باینری، پروتکل‌های شبکه یا کتابخانه‌های زبان C ارتباط برقرار کنید، باید داده‌ها را به بایت (Bytes) تبدیل کنید. ماژول struct در پایتون دقیقاً برای همین کار ساخته شده است.

۱. مفهوم Pack و Unpack

دو عملیات اصلی در این ماژول وجود دارد:

  1. Packing: تبدیل متغیرهای پایتون (مانند اعداد صحیح و اعشاری) به رشته‌ای از بایت‌ها.
  2. Unpacking: تبدیل رشته‌ای از بایت‌ها به متغیرهای پایتون.

برای انجام این کارها از "رشته‌های فرمت" (Format Strings) استفاده می‌کنیم که به پایتون می‌گویند هر داده چه نوعی دارد (مثلاً i برای عدد صحیح، f برای اعشاری).

مثال اول: تبدیل عدد به بایت (Pack)

در این مثال ساده، سه عدد را به یک ساختار باینری تبدیل می‌کنیم.

Python

مثال دوم: بازیابی داده‌ها (Unpack)

حالا همان داده‌های باینری را به متغیرهای پایتون برمی‌گردانیم.

Python

مثال سوم: تعریف تابع کمکی (Static)

گاهی اوقات می‌خواهیم تابعی داشته باشیم که فقط عملیات تبدیل را تعریف کند اما فعلاً اجرا نشود.

python
import struct

def create_binary_packet(player_id, score):
    # 'I' به معنی unsigned int و 'f' به معنی float است
    return struct.pack('If', player_id, score)

۲. کاراکترهای فرمت (Format Characters)

برای اینکه به struct بگوییم چگونه داده‌ها را ذخیره کند، باید از کاراکترهای خاصی استفاده کنیم. جدول زیر پرکاربردترین‌ها را نشان می‌دهد:

  • x: بایت خالی (Padding)
  • c: کاراکتر (char) - طول ۱ بایت
  • b: عدد صحیح علامت‌دار (signed char) - طول ۱ بایت
  • h: عدد صحیح کوتاه (short) - طول ۲ بایت
  • i: عدد صحیح (int) - طول ۴ بایت
  • f: عدد اعشاری (float) - طول ۴ بایت
  • d: عدد اعشاری دقیق (double) - طول ۸ بایت
  • s: رشته (char[])

مثال اول: ترکیب انواع مختلف داده

ترکیبی از یک عدد صحیح، دو کاراکتر و یک عدد اعشاری.

Python

مثال دوم: محاسبه سایز فرمت

قبل از تبدیل، می‌توانیم بفهمیم یک فرمت خاص چند بایت فضا اشغال می‌کند.

Python

۳. ترتیب بایت‌ها (Byte Order / Endianness)

کامپیوترهای مختلف بایت‌ها را به ترتیب‌های متفاوتی در حافظه ذخیره می‌کنند (Little Endian vs Big Endian). برای تبادل داده بین سیستم‌های مختلف (مثلاً شبکه)، باید این ترتیب را مشخص کنید.

  • @: ترتیب بومی سیستم (پیش‌فرض)
  • =: ترتیب استاندارد (بدون Alignment خاص)
  • <: Little Endian (استاندارد PC های اینتل)
  • >: Big Endian (استاندارد شبکه)
  • !: Network (همان Big Endian)

مثال: تفاوت خروجی در Endianness مختلف

Python

سطح پیشرفته (Professional Level)

در سطح حرفه‌ای، بحث کارایی (Performance)، مدیریت بافرها و تراز حافظه (Memory Alignment) اهمیت پیدا می‌کند. استفاده صحیح از ماژول struct می‌تواند سرعت پردازش فایل‌های حجیم یا بسته‌های شبکه را به شدت افزایش دهد.

۱. استفاده از کلاس struct.Struct

استفاده از توابع ماژولار مثل struct.pack() برای هر بار صدا زدن، رشته فرمت را کامپایل می‌کند. اگر قرار است هزاران بار یک فرمت ثابت را تبدیل کنید، بهتر است یک آبجکت Struct بسازید تا فرمت فقط یک‌بار کامپایل شود. این کار سربار (Overhead) را کاهش می‌دهد.

مثال اول: مقایسه روش تابعی و کلاس‌محور

در اینجا نحوه تعریف کلاس Struct را می‌بینیم.

Python

مثال دوم: تعریف کلاس پکت (Static)

این الگو در برنامه‌نویسی شبکه بسیار رایج است.

python
import struct

class NetworkPacket:
    # هدر بسته: 2 بایت نسخه، 2 بایت نوع پیام، 4 بایت طول پیام
    _header_struct = struct.Struct('!HH I')

    def __init__(self, version, msg_type, payload):
        self.version = version
        self.msg_type = msg_type
        self.payload = payload  # Assume bytes

    def to_bytes(self):
        header = self._header_struct.pack(self.version, self.msg_type, len(self.payload))
        return header + self.payload

۲. کار با بافرها (pack_into و unpack_from)

توابع pack و unpack معمولی، رشته‌های بایت جدیدی می‌سازند (Memory allocation). در برنامه‌های High-Performance، ما می‌خواهیم روی یک بافر حافظه از پیش تخصیص داده شده بنویسیم تا از ساخت اشیاء اضافی جلوگیری کنیم.

مثال اول: نوشتن در یک بافر مشخص

استفاده از pack_into برای نوشتن داده در آفست خاصی از یک bytearray.

Python

مثال دوم: خواندن از آفست‌های مختلف

استفاده از unpack_from برای خواندن بدون برش (Slicing) بافر.

Python

۳. تراز حافظه (Memory Alignment) و Padding

در زبان C، کامپایلرها برای افزایش سرعت دسترسی به حافظه، متغیرها را در آدرس‌های زوج (مثلاً مضرب ۴ یا ۸) قرار می‌دهند. این باعث ایجاد فضاهای خالی یا Padding بین داده‌ها می‌شود.

  • اگر از @ (Native) استفاده کنید، پایتون Padding اضافه می‌کند.
  • اگر از = (Standard) استفاده کنید، پایتون داده‌ها را فشرده و بدون Padding می‌چیند.

مثال اول: مشاهده تفاوت سایز با و بدون Padding

Python

مثال دوم: مدیریت دستی Padding

گاهی نیاز دارید خودتان با کاراکتر x پدینگ را کنترل کنید.

Python

۴. مدیریت رشته‌های با طول متغیر (Pascal String vs C-String)

در باینری، رشته‌ها یا طول ثابت دارند (s) یا طولشان در ابتدای آن‌ها ذکر می‌شود (Pascal String - p).

مثال: کار با رشته‌های ثابت و Pascal

توجه کنید که 10s یعنی یک رشته که دقیقاً ۱۰ بایت فضا می‌گیرد. اگر رشته کوتاه‌تر باشد، با null پر می‌شود.

Python