مقالات مختارة

الفرق بين __str__ و__repr__ في Python | أساسيات بايثون 40

الفرق بين __str__ و__repr__ في بايثون وشرح Magic Methods

عند إنشاء Class في بايثون ثم طباعة object منها، قد تحصل على نتيجة غير مفهومة مثل:

<__main__.Product object at 0x000001F...>

هذا ليس خطأ. بايثون تخبرك أن لديك object من class باسم Product، لكنها لا تعرف كيف تريد تمثيله كنص. هنا يأتي دور اثنتين من أهم Magic Methods في البرمجة الكائنية: __str__() و__repr__().

__str__() تساعدك على تحديد تمثيل مقروء ومناسب للمستخدم، بينما __repr__() تهدف عادةً إلى إعطاء تمثيل أوضح للمطور والتصحيح Debugging. والفرق بينهما يظهر بوضوح عند استخدام print() وstr() وrepr() وعند عرض objects داخل Lists أو Dictionaries.

في الدرس 40 من سلسلة أساسيات بايثون سنفهم لماذا يظهر الشكل الافتراضي للكائن، وكيف نكتب __str__ و__repr__ بصورة صحيحة، وما معنى !r داخل f-string، وما علاقة هذه الدوال بمفهوم Magic Methods أو Dunder Methods.

الخلاصة السريعة: استخدم __str__ لتمثيل مقروء للمستخدم، و__repr__ لتمثيل واضح ومفيد للمطور. وإذا لم تعرّف __str__ فقد تستخدم بايثون __repr__ كحل بديل عند تحويل object إلى نص.

لماذا print(object) يظهر شكلًا غريبًا؟

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price


product = Product("Keyboard", 80)

print(product)

قد تظهر نتيجة قريبة من:

<__main__.Product object at 0x000001F...>

السبب أن Class لم تحدد تمثيلًا نصيًا مخصصًا لهذا object.

ما هي Magic Methods في بايثون؟

Magic Methods أو Special Methods هي methods بأسماء خاصة تبدأ وتنتهي بشرطتين سفليتين، مثل:

__init__
__str__
__repr__
__len__
__eq__
__add__

يسميها كثير من المبرمجين Dunder Methods، أي Double Underscore Methods. تستدعي بايثون كثيرًا منها تلقائيًا عندما تستخدم عمليات عادية مثل str() وrepr() وlen() و==.

ما هي __str__ في بايثون؟

__str__() تحدد تمثيلًا نصيًا مقروءًا للكائن، وغالبًا يكون موجهًا للمستخدم.

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):
        return f"{self.name} - ${self.price}"

ثم:

product = Product("Keyboard", 80)

print(product)

الناتج:

Keyboard - $80

ما العلاقة بين print و__str__؟

عندما تطبع object أو تحولها إلى نص، تحتاج بايثون إلى تمثيل نصي:

print(product)

text = str(product)
print(text)

وجود __str__() يسمح لك بالتحكم في هذا التمثيل.

يجب أن تعيد __str__ نصًا

هذا صحيح:

def __str__(self):
    return self.name

لكن هذا غير صحيح إذا كانت price عددًا:

def __str__(self):
    return self.price

الصحيح أن تعيد String:

def __str__(self):
    return str(self.price)

ما هي __repr__ في بايثون؟

__repr__() تعطي تمثيلًا للكائن موجهًا عادةً للمطور. من المفيد أن يتضمن اسم Class والقيم الأساسية التي تساعدك على فهم object أثناء Debugging.

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __repr__(self):
        return (
            f"Product("
            f"name={self.name!r}, "
            f"price={self.price!r}"
            f")"
        )

ثم:

product = Product("Keyboard", 80)

print(repr(product))

الناتج:

Product(name='Keyboard', price=80)
الفرق بين str وrepr عند طباعة object في بايثون

الفرق بين __str__ و__repr__

النقطة__str____repr__
الهدف المعتادتمثيل مقروء للمستخدم.تمثيل واضح للمطور والتصحيح.
تظهر معstr(obj) والطباعة.repr(obj) وعرض عناصر كثير من الحاويات.
الأسلوبيمكن أن يكون مختصرًا وجميلًا.يفضل أن يكون دقيقًا وغير غامض.
القيمة المعادةString.String.

تعريف __str__ و__repr__ معًا

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):
        return f"{self.name} - ${self.price}"

    def __repr__(self):
        return (
            f"Product("
            f"name={self.name!r}, "
            f"price={self.price!r}"
            f")"
        )
product = Product("Keyboard", 80)

print(str(product))
print(repr(product))

نتيجة ممكنة:

Keyboard - $80
Product(name='Keyboard', price=80)

ما معنى !r داخل f-string؟

!r يعني استخدام repr() على القيمة قبل إدخالها في النص.

name = "Ali"

print(f"{name}")
print(f"{name!r}")

الناتج:

Ali
'Ali'

الاقتباسات في النتيجة الثانية توضح أن القيمة String، وهذا مفيد في تمثيلات المطور.

شرح !r داخل f-string وعلاقته بدالة repr في بايثون

ما معنى !s داخل f-string؟

print(f"{product!s}")
print(f"{product!r}")

!s تطلب str()، بينما !r تطلب repr().

ماذا يحدث إذا عرّفت __repr__ فقط؟

إذا لم توجد __str__() مخصصة، يمكن أن تستخدم بايثون تمثيل __repr__() كبديل للتمثيل النصي.

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __repr__(self):
        return (
            f"Product("
            f"name={self.name!r}, "
            f"price={self.price!r}"
            f")"
        )


product = Product("Keyboard", 80)
print(product)

ماذا لو عرّفت __str__ فقط؟

ستحصل على طباعة جميلة للكائن مباشرة، لكن الفرق يظهر عند وضع objects داخل List.

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):
        return f"{self.name} - ${self.price}"


products = [
    Product("Keyboard", 80),
    Product("Mouse", 30)
]

print(products)

الحاويات مثل List تستخدم تمثيل repr() لعناصرها، لذلك قد ترى الشكل الافتراضي للكائنات إذا لم تعرّف __repr__().

الحل: أضف __repr__

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

    def __str__(self):
        return f"{self.name} - ${self.price}"

    def __repr__(self):
        return (
            f"Product("
            f"name={self.name!r}, "
            f"price={self.price!r}"
            f")"
        )
products = [
    Product("Keyboard", 80),
    Product("Mouse", 30)
]

print(products)

الآن يصبح العرض واضحًا:

[Product(name='Keyboard', price=80), Product(name='Mouse', price=30)]
لماذا تستخدم list repr للكائنات داخلها في بايثون

لماذا __repr__ مهمة في Debugging؟

عندما تطبع List أو Dictionary تحتوي عشرات objects، فإن __repr__() الجيدة تساعدك على رؤية القيم المهمة مباشرة بدل عناوين Objects غير المفهومة.

هل يجب أن يكون __repr__ كودًا يعيد إنشاء object؟

هناك قاعدة إرشادية شائعة: إذا كان ذلك ممكنًا وعمليًا، اجعل __repr__() قريبة من تعبير واضح يمكن أن يعيد إنشاء الكائن أو على الأقل يصفه بدقة.

Product(name='Keyboard', price=80)

لكن هذا ليس شرطًا يمكن تحقيقه دائمًا. الأهم أن يكون التمثيل واضحًا وغير غامض للمطور.

مثال __repr__ غير مفيد

def __repr__(self):
    return "Product object"

هذا لا يخبرك أي Product تقصد. تمثيل أفضل:

def __repr__(self):
    return (
        f"Product("
        f"name={self.name!r}, "
        f"price={self.price!r}"
        f")"
    )

أخطاء شائعة عند استخدام __str__ و__repr__

خطأ شائع: نسيان return داخل __str__

def __str__(self):
    print(self.name)

هذا يطبع القيمة لكنه لا يعيد String. الصحيح:

def __str__(self):
    return self.name

خطأ شائع: إرجاع قيمة غير نصية

def __repr__(self):
    return self.price

إذا كانت price رقمًا فهذا غير صحيح. أعد String:

def __repr__(self):
    return (
        f"Product(price={self.price!r})"
    )

خطأ شائع: وضع معلومات حساسة داخل __repr__

تمثيل repr() قد يظهر في Logs أو Debugging أو رسائل الخطأ، لذلك لا تضع كلمات مرور أو Tokens أو Secrets داخله.

# تصميم سيئ

def __repr__(self):
    return (
        f"User("
        f"email={self.email!r}, "
        f"password={self.password!r}"
        f")"
    )

الأفضل حذف السر:

def __repr__(self):
    return (
        f"User(email={self.email!r})"
    )
أخطاء شائعة عند استخدام __str__ و__repr__ في بايثون

خطأ شائع: جعل __repr__ طويلة جدًا

إذا كان object يحتوي آلاف العناصر، لا تطبعها كلها. اعرض أهم المعلومات أو عدد العناصر:

def __repr__(self):
    return (
        f"Dataset("
        f"name={self.name!r}, "
        f"rows={len(self.rows)}"
        f")"
    )

خطأ شائع: تنفيذ عمليات ثقيلة داخل __str__ أو __repr__

هذه methods قد تستدعى أثناء الطباعة والتصحيح. لا تجعل __repr__() تنفذ طلب شبكة أو تعدل قاعدة بيانات فقط لبناء String.

أمثلة عملية على __str__ و__repr__

مثال عملي كامل: Product

class Product:
    def __init__(self, sku, name, price):
        self.sku = sku
        self.name = name
        self.price = float(price)

    def __str__(self):
        return (
            f"{self.name} "
            f"(${self.price:.2f})"
        )

    def __repr__(self):
        return (
            f"Product("
            f"sku={self.sku!r}, "
            f"name={self.name!r}, "
            f"price={self.price!r}"
            f")"
        )
product = Product(
    "KB100",
    "Keyboard",
    80
)

print(product)
print(repr(product))

نتيجة ممكنة:

Keyboard ($80.00)
Product(sku='KB100', name='Keyboard', price=80.0)

استخدام !s و!r مع نفس object

print(
    f"User view: {product!s}"
)

print(
    f"Debug view: {product!r}"
)

مثال مع Employees

class Employee:
    def __init__(self, employee_id, name, department):
        self.employee_id = employee_id
        self.name = name
        self.department = department

    def __str__(self):
        return (
            f"{self.name} - "
            f"{self.department}"
        )

    def __repr__(self):
        return (
            f"Employee("
            f"employee_id={self.employee_id!r}, "
            f"name={self.name!r}, "
            f"department={self.department!r}"
            f")"
        )

هنا __str__ مناسبة للعرض، بينما __repr__ تضيف ID ليساعد المطور.

Object يحتوي Object آخر

class Customer:
    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return (
            f"Customer(name={self.name!r})"
        )


class Order:
    def __init__(self, order_id, customer):
        self.order_id = order_id
        self.customer = customer

    def __repr__(self):
        return (
            f"Order("
            f"order_id={self.order_id!r}, "
            f"customer={self.customer!r}"
            f")"
        )

استخدام !r مع customer يستفيد من Customer.__repr__().

اختبار __str__ و__repr__

product = Product(
    "KB100",
    "Keyboard",
    80
)

assert (
    str(product)
    == "Keyboard ($80.00)"
)

ويمكن اختبار repr() أيضًا إذا كان شكلها جزءًا مهمًا من سلوك Class.

أفضل ممارسات __repr__

هل أعتمد على repr للتخزين الدائم؟

غالبًا لا. إذا كنت تريد تخزينًا منظمًا أو تبادل بيانات، استخدم JSON أو CSV أو Serializer واضحًا بدل اعتبار repr() صيغة تخزين رسمية.

هل أستخدم eval(repr(obj))؟

لا تستخدم eval() مع بيانات غير موثوقة. حتى لو كانت repr() قريبة من صيغة إنشاء object، هدفها الأساسي الوضوح للمطور وليس تشغيل النص ككود.

كيف أختار المعلومات داخل __repr__؟

  • أظهر ID أو SKU عند الحاجة.
  • أظهر الاسم أو Label الرئيسي.
  • أظهر الحالة أو القيم المهمة.
  • استخدم عدد العناصر بدل طباعة بيانات ضخمة.
  • لا تعرض كلمات مرور أو Tokens أو Secrets.

تمرين عملي

أنشئ Class باسم Book تحتوي على title وauthor وprice، واجعل __str__ مناسبة للمستخدم و__repr__ مناسبة للمطور.

حل مقترح

class Book:
    def __init__(self, title, author, price):
        self.title = title
        self.author = author
        self.price = price

    def __str__(self):
        return (
            f"{self.title} "
            f"by {self.author}"
        )

    def __repr__(self):
        return (
            f"Book("
            f"title={self.title!r}, "
            f"author={self.author!r}, "
            f"price={self.price!r}"
            f")"
        )
book = Book(
    "Python Guide",
    "Ali",
    25
)

print(book)
print(repr(book))
print([book])

روابط داخلية مفيدة من بايثون العرب

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

الخلاصة

__str__ و__repr__ تمنحانك التحكم في الطريقة التي تمثل بها objects كنص. __str__ مناسبة عادةً للنص المقروء للمستخدم، بينما __repr__ مفيدة لتمثيل أدق للمطور أثناء Debugging.

عند طباعة object مباشرة يمكن أن تستخدم بايثون __str__، أما عند عرض object داخل List أو Dictionary فغالبًا يظهر تمثيل repr() للعناصر، ولهذا من المفيد تعريف الاثنين في Classes المهمة.

استخدم !r داخل f-string عندما تريد تمثيل repr() لقيمة، واحرص على أن تعيد __str__ و__repr__ String، وأن تكون __repr__ واضحة ولا تكشف بيانات حساسة.

القاعدة المهمة: فكّر في __str__ كواجهة نصية للمستخدم، وفي __repr__ كنافذة للمطور يرى من خلالها هوية object وحالته بسرعة.

أسئلة شائعة

ما الفرق بين __str__ و__repr__ في بايثون؟

__str__ تستخدم عادةً لإنشاء تمثيل نصي مقروء للمستخدم، بينما __repr__ تستخدم لتمثيل أدق وأكثر فائدة للمطور والتصحيح.

لماذا print(object) يظهر تمثيلًا مثل <... object at 0x...>؟

إذا لم تعرّف تمثيلًا نصيًا مخصصًا للكائن، تستخدم بايثون التمثيل الافتراضي الموروث من object، وقد يظهر بشكل قريب من <__main__.Product object at 0x...>. عرّف __str__ أو __repr__ عندما تحتاج تمثيلًا أوضح.

كيف أغير طريقة طباعة object في بايثون؟

عرّف __str__(self) داخل Class وأعد منها String تريد إظهارها عند تحويل object إلى نص.

لماذا object داخل list لا يستخدم __str__؟

تمثيل Lists يستخدم repr() لعناصرها، لذلك تحتاج إلى __repr__ إذا أردت تمثيلًا واضحًا للobjects داخل List.

ما معنى !r في f-string؟

!r تطلب استخدام repr() على القيمة قبل إدخالها في النص.

ما معنى !s في f-string؟

!s تطلب استخدام str() على القيمة.

هل يجب أن تعيد __str__ String؟

نعم. القيمة المعادة من __str__ يجب أن تكون نصًا.

هل يجب أن تعيد __repr__ String؟

نعم. __repr__ أيضًا يجب أن تعيد String.

ماذا يحدث إذا عرّفت __repr__ ولم أعرّف __str__؟

يمكن أن تستخدم بايثون تمثيل __repr__ كبديل عند الحاجة إلى تمثيل نصي للكائن.

هل __repr__ يجب أن تعيد كودًا يعيد إنشاء object؟

هذا هدف إرشادي مفيد عندما يكون ممكنًا، لكنه ليس شرطًا في كل Class. الأهم أن يكون التمثيل واضحًا وغير غامض.

ما هي Magic Methods في بايثون؟

هي Special Methods بأسماء مثل __init__ و__str__ و__repr__ تسمح للكائنات المخصصة بالتفاعل مع عمليات وبروتوكولات بايثون.

ما معنى Dunder Methods؟

Dunder اختصار لـDouble Underscore، وهو اسم شائع للدوال الخاصة التي تبدأ وتنتهي بشرطتين سفليتين.

هل أستدعي product.__str__() مباشرة؟

يفضل في الاستخدام العادي استخدام str(product) بدل استدعاء Special Method مباشرة.

هل أستدعي product.__repr__() مباشرة؟

يفضل استخدام repr(product) في الكود العادي.

هل من الآمن وضع Password داخل __repr__؟

لا. قد يظهر repr() في Logs أو أثناء Debugging، لذلك لا تضع كلمات مرور أو Tokens أو أسرارًا داخله.

Admin
Admin
تعليقات