البيانات والسلوك معًا
حتى الآن كان المركز يمكن أن يكون صفًا أو قاموسًا مع دوال منفصلة تعمل عليه. تجمع الفئة (class) البيانات والدوال التي تخصها في نوع جديد. يعرّف class Position: المخطط؛ واستدعاؤها Position("AAPL", 100, 180.0) يبني كائنًا (يسمى أيضًا نسخة instance) من ذلك المخطط. يحمل كل كائن بياناته الخاصة، وتعمل على ذلك الكائن الدوال المعرّفة في الفئة وتسمى الدوال الأعضاء (methods).
class Position:
def __init__(self, symbol, quantity=0):
self.symbol = symbol # attributes belong to this object
self.quantity = quantity
def describe(self):
return f"{self.quantity} x {self.symbol}"
apple = Position("AAPL", 100)
tesla = Position("TSLA")
print(apple.describe()) # 100 x AAPL
print(tesla.describe()) # 0 x TSLA
print(apple.quantity, tesla.quantity) # 100 0__init__ و self
__init__ هو الدالة البانية (constructor): تشغّلها بايثون تلقائيًا عند إنشاء كائن وتحفظ القيم الابتدائية. تتلقى كل دالة عضو الكائن نفسه معاملًا أول يسمى عُرفًا self؛ ولا تمرّره أنت بل بايثون (apple.describe() تستدعي Position.describe(apple)). وكل ما يُحفظ بـself.name = value هو خاصية (attribute) لذلك الكائن وتبقى بين استدعاءات الدوال. و__init__ تُهيّئ الأمور ولا تُرجع شيئًا.
class Account:
def __init__(self, cash):
self.cash = cash
def deposit(self, amount):
self.cash += amount # changes this object's attribute
return self.cash
first = Account(1000)
second = Account(50)
first.deposit(500)
print(first.cash, second.cash) # 1500 50 (separate objects, separate data)
same = first # a second name for the same object
same.deposit(1)
print(first.cash) # 1501دوال تغيّر الحالة أو تجيب عن أسئلة
بعض الدوال الأعضاء تجيب عن سؤال حول الكائن دون تغييره (market_value(price))؛ وينبغي أن تُرجع نتيجة فقط وتترك الخصائص كما هي. وبعضها يغيّر الكائن (apply_fill) فيحدّث الخصائص وكثيرًا ما يُرجع شيئًا مفيدًا كالربح المحقَّق. ويمكن لدالة أن تستدعي أخرى بـself.other_method(...) مما يبقي القواعد في مكان واحد: فإذا احتاجت unrealized_pnl إلى القيمة السوقية سألت self.market_value(price) بدل تكرار الصيغة.
class Position:
def __init__(self, symbol, quantity=0, avg_price=0.0):
self.symbol = symbol
self.quantity = quantity
self.avg_price = avg_price
def market_value(self, price):
return self.quantity * price
def unrealized_pnl(self, price):
return self.market_value(price) - self.quantity * self.avg_price
pos = Position("AAPL", 100, 180.0)
print(pos.market_value(185.0)) # 18500.0
print(pos.unrealized_pnl(185.0)) # 500.0مزالق ومتى تستخدم فئة
نسيان self. هو الزلة الكلاسيكية: فـquantity = 5 داخل دالة تنشئ متغيرًا محليًا زائلًا لا خاصية. استخدم CapWords لأسماء الفئات (Position) والحروف الصغيرة مع الشرطات السفلية للخصائص والدوال؛ والشرطة السفلية في البداية (_cache) تعني أن الشيء داخلي. ولا تستخدم أبدًا قيمة افتراضية قابلة للتعديل مثل def __init__(self, fills=[]): ستتشارك كل الكائنات تلك القائمة الوحيدة؛ استخدم fills=None وأنشئ [] في الداخل. أنشئ فئة حين تسير البيانات وقواعدها معًا؛ والدالة العادية أو القاموس أبسط في الغالب.
class Wrong:
def __init__(self, quantity):
quantity = quantity # forgot self.: nothing is stored
class Shared:
def __init__(self, fills=[]): # one list shared by every object
self.fills = fills
class Right:
def __init__(self, fills=None):
self.fills = [] if fills is None else list(fills)
a, b = Shared(), Shared()
a.fills.append(100)
print(b.fills) # [100] (surprise!)
c, d = Right(), Right()
c.fills.append(100)
print(d.fills) # []
print(hasattr(Wrong(5), "quantity")) # Falseclass Name:تعرّف نوعًا؛ وName(args)تبني كائنًا وتشغّل__init__.self.attr = valueيخزّن بيانات في الكائن؛ وكل دالة عضو تأخذselfأولًا.- دوال الاستعلام تُرجع إجابات؛ ودوال التحديث تغيّر الخصائص؛ وتجنّب القيم الافتراضية القابلة للتعديل.
التالي: الدوال الخاصة مثل __repr__ و__eq__، التي تجعل كائناتك تُطبع بشكل جيد وتُقارن كما تتوقع.
المختبر: ما الذي يُقيَّم
- ابنِ مركزًا باستخدام
__init__25 نقطة - قيّم المركز باستخدام
market_value25 نقطة - قِس الربح غير المحقق باستخدام
unrealized_pnl25 نقطة - حدّث المركز بعد صفقة باستخدام
apply_fill25 نقطة