عند إنشاء 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.
{alertInfo} الخلاصة السريعة: استخدم__str__لتمثيل مقروء للمستخدم، و__repr__لتمثيل واضح ومفيد للمطور. وإذا لم تعرّف__str__فقد تستخدم بايثون__repr__كحل بديل عند تحويل object إلى نص.
{getToc} $title={محتوى المقال}
لماذا 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__
| النقطة | __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، وهذا مفيد في تمثيلات المطور.
ما معنى !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)]
لماذا __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")"
)
خطأ شائع: نسيان 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})"
)
خطأ شائع: جعل __repr__ طويلة جدًا
إذا كان object يحتوي آلاف العناصر، لا تطبعها كلها. اعرض أهم المعلومات أو عدد العناصر:
def __repr__(self):
return (
f"Dataset("
f"name={self.name!r}, "
f"rows={len(self.rows)}"
f")"
)
خطأ شائع: تنفيذ عمليات ثقيلة داخل __str__ أو __repr__
هذه methods قد تستدعى أثناء الطباعة والتصحيح. لا تجعل __repr__() تنفذ طلب شبكة أو تعدل قاعدة بيانات فقط لبناء String.
مثال عملي كامل: 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 للتخزين الدائم؟
غالبًا لا. إذا كنت تريد تخزينًا منظمًا أو تبادل بيانات، استخدم JSON أو CSV أو Serializer واضحًا بدل اعتبار repr() صيغة تخزين رسمية.
هل أستخدم eval(repr(obj))؟
لا تستخدم eval() مع بيانات غير موثوقة. حتى لو كانت repr() قريبة من صيغة إنشاء object، هدفها الأساسي الوضوح للمطور وليس تشغيل النص ككود.
كيف أختار المعلومات داخل __repr__؟
- أظهر ID أو SKU عند الحاجة.
- أظهر الاسم أو Label الرئيسي.
- أظهر الحالة أو القيم المهمة.
- استخدم عدد العناصر بدل طباعة بيانات ضخمة.
- لا تعرض كلمات مرور أو Tokens أو Secrets.
مقارنة سريعة
| السيناريو | الاختيار الأنسب غالبًا |
|---|---|
| عرض اسم المنتج للمستخدم | __str__ |
| Debugging لكائن Product | __repr__ |
| طباعة List من objects | __repr__ للعناصر |
| f-string بتمثيل المطور | !r |
| f-string بتمثيل المستخدم | !s |
تمرين عملي
أنشئ 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])
ما الذي نتعلمه عن Magic Methods من هذا الدرس؟
__str__ و__repr__ مجرد مثالين على طريقة جعل Classes المخصصة تتفاعل مع بروتوكولات بايثون. لاحقًا تستطيع تعلم __len__ و__eq__ و__add__ و__contains__.
قائمة فحص لفهم الدرس
- هل تعرف لماذا يظهر الشكل الافتراضي للكائن؟
- هل تستطيع كتابة
__str__تعيد String؟ - هل تعرف دور
__repr__في Debugging؟ - هل تعرف لماذا List تستخدم repr لعناصرها؟
- هل تعرف معنى
!rو!sداخل f-string؟ - هل تتجنب وضع أسرار داخل
__repr__؟ - هل تتجنب العمليات الثقيلة والتغييرات الجانبية داخل التمثيل النصي؟
روابط داخلية مفيدة من بايثون العرب
- كورس أساسيات بايثون
- أساسيات بايثون 34: شرح Classes وObjects
- أساسيات بايثون 35: Class Attributes وInstance Attributes
- أساسيات بايثون 38: Encapsulation في بايثون
ملاحظة للمحرر: بعد نشر درس 39 عن classmethod وstaticmethod أضف رابطه هنا كقراءة سابقة مباشرة.
مصادر رسمية للتوسع
- توثيق __repr__ الرسمي في بايثون
- توثيق __str__ الرسمي في بايثون
- توثيق repr() الرسمي
- توثيق str في بايثون
الخلاصة
__str__ و__repr__ تمنحانك التحكم في الطريقة التي تمثل بها objects كنص. __str__ مناسبة عادةً للنص المقروء للمستخدم، بينما __repr__ مفيدة لتمثيل أدق للمطور أثناء Debugging.
عند طباعة object مباشرة يمكن أن تستخدم بايثون __str__، أما عند عرض object داخل List أو Dictionary فغالبًا يظهر تمثيل repr() للعناصر، ولهذا من المفيد تعريف الاثنين في Classes المهمة.
استخدم !r داخل f-string عندما تريد تمثيل repr() لقيمة، واحرص على أن تعيد __str__ و__repr__ String، وأن تكون __repr__ واضحة ولا تكشف بيانات حساسة.
{alertSuccess} القاعدة المهمة: فكّر في__str__كواجهة نصية للمستخدم، وفي__repr__كنافذة للمطور يرى من خلالها هوية object وحالته بسرعة.
أسئلة شائعة
ما الفرق بين __str__ و__repr__ في بايثون؟
__str__ تستخدم عادةً لإنشاء تمثيل نصي مقروء للمستخدم، بينما __repr__ تستخدم لتمثيل أدق وأكثر فائدة للمطور والتصحيح.
لماذا print(object) يظهر memory address؟
إذا لم تعرّف تمثيلًا مناسبًا للكائن، تستخدم بايثون تمثيلًا افتراضيًا قد يعرض اسم Class ومعلومات عن object. أضف __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 أو أسرارًا داخله.



