شنبه ۲۹ دي ۱۴۰۳
Tut24 آموزش برنامه نویسی و مجله تخصصی فناوری ورود/عضویت

آموزش پایتون - شی گرایی

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

اگر تجربه‌ای قبلی‌ در برنامه نویسی شی گرا (OO) ندارید، ممکن است لازم باشد با مطالعه و تحقیق بیشتر وارد این درس شوید

با این حال، در ادامه یک مقدمه کوتاه از برنامه نویسی شی گرا (OOP) آورده شده است تا به شما کمک کند ، سریع‌تر به مباحث اصلی برسید −

بررسی اصطلاحات شی گرایی

  • کلاس(Class) − یک نمونه تعریف شده توسط کاربر برای یک شی است که مجموعه‌ای از ویژگی‌های آن شیء را مشخص می‌کند. این ویژگی‌ها عضوهای داده (متغیرهای کلاس و متغیرهای نمونه) و متدها را تعریف می‌کنند و با استفاده از نماد نقطه قابل دسترسی هستند.

  • متغیر کلاس(Class variable) − متغیری است که توسط تمام نمونه‌های یک کلاس به اشتراک گذاشته می‌شود. متغیرهای کلاس در داخل کلاس تعریف می‌شوند اما خارج از هر یک از متدهای کلاس هستند. متغیرهای کلاس به اندازه متغیرهای نمونه استفاده نمی‌شوند.

  • عضو داده(Data member) − یک متغیر کلاس یا متغیر نمونه است که داده‌ای مرتبط با یک کلاس و اشیاي آن را نگهداری می‌کند.

  • بارگذاری تابع(Function overloading) − تخصیص بیش از یک عملکرد به یک تابع خاص و انجام عملیات وابسته به نوع اشیا یا آرگومان‌هایی دارد که در آنها استفاده می‌شود.

  • متغیر نمونه(Instance variable) − یک متغیری است که درون یک متد تعریف می‌شود و تنها متعلق به نمونه فعلی یک کلاس است.

  • ارث‌بری(Inheritance) − انتقال ویژگی‌های یک کلاس به کلاس‌های دیگری که از آن ارث می‌برند.

  • نمونه(Instance) − یک شیء منفرد از یک کلاس مشخص است. به عنوان مثال، یک شیء به نام obj که متعلق به کلاس دایره است، نمونه‌ای از کلاس دایره است.

  • نمونه‌سازی(Instantiation) − ایجاد یک نمونه از یک کلاس.

  • متد(Method) − یک نوع خاصی از تابع است که در تعریف کلاس تعریف می‌شود.

  • شیء(Object) − یک نمونه منحصر به فرد از یک ساختار داده است که توسط کلاس آن تعریف شده است. یک شیء شامل عضوهای داده (متغیرهای کلاس و متغیرهای نمونه) و متدها است.

  • بارگذاری عملگر(Operator overloading) − تخصیص بیش از یک تابع به یک عملگر خاص.

ایجاد کلاس‌ها

برای ایجاد یک تعریف کلاس جدید از عبارت class  استفاده می‌کنیم و سپس نام کلاس بلافاصله پس از کلمه کلیدی class و پس از آن دو نقطه قرار می‌گیرد، به شرح زیر −

class ClassName:
   'Optional class documentation string'
   class_suite
  • یک کلاس دارای رشته مستندسازی است که می‌توان به وسیله ClassName.__doc__ به آن دسترسی پیدا کرد.

  • class_suite شامل تمام دستورهای مربوط به تعریف اعضای کلاس، ویژگی‌های داده و توابع است.

مثال

مثالی از یک کلاس ساده در پایتون به شرح زیر است −

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary
  • متغیر empCount یک متغیر کلاس است که مقدار آن بین تمام نمونه‌های این کلاس به اشتراک گذاشته می‌شود. می‌توان به آن به عنوان Employee.empCount از داخل کلاس یا خارج از کلاس دسترسی داشت.

  • اولین متد __init__() یک متد ویژه است که به عنوان سازنده کلاس یا متد مقدماتی نامیده می‌شود و  هنگامی که شما یک نمونه جدید از این کلاس ایجاد می‌کنید پایتون آن را فراخوانی می‌کند.

  • شما متدهای دیگر کلاس را مانند توابع عادی تعریف می‌کنید با این تفاوت که آرگومان اول هر متد self است. پایتون آرگومان self را به لیست شما اضافه می‌کند؛ شما نیازی به اضافه کردن آن هنگام فراخوانی متدها ندارید.

ایجاد اشیاء نمونه

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

"This would create first object of Employee class"
emp1 = Employee("Sara", 2000)
"This would create second object of Employee class"
emp2 = Employee("Manni", 5000)

دسترسی به ویژگی‌ها

شما با استفاده از اپراتور نقطه همراه با نام شیء به ویژگی‌های شیء دسترسی پیدا می‌کنید. به متغیر کلاس به وسیله نام کلاس به صورت زیر می‌توانید دسترسی پیدا کنید−

emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount

حالا، تمام مفاهیم را با هم ترکیب می‌کنیم −

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

"This would create first object of Employee class"
emp1 = Employee("Sara", 2000)
"This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCount

وقتی کد بالا اجرا می‌شود، نتیجه زیر را تولید می‌کند −

Name :  Sara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

شما می‌توانید در هر زمانی ویژگی‌های کلاس‌ها و اشیاء را اضافه، حذف یا تغییر دهید −

emp1.age = 7  # Add an 'age' attribute.
emp1.age = 8  # Modify 'age' attribute.
del emp1.age  # Delete 'age' attribute.

به جای استفاده از دستورهای عادی برای دسترسی به ویژگی‌ها، شما می‌توانید از توابع زیر استفاده کنید −

  • تابع getattr(obj, name[, default]) − برای دسترسی به ویژگی شیء.

  • تابع hasattr(obj,name) − برای بررسی وجود یا عدم وجود یک ویژگی.

  • تابع setattr(obj,name,value) − برای تنظیم یک ویژگی. اگر ویژگی وجود نداشته باشد، آن ایجاد خواهد شد.

  • تابع delattr(obj, name) − برای حذف یک ویژگی.

hasattr(emp1, 'age')    # Returns true if 'age' attribute exists
getattr(emp1, 'age')    # Returns value of 'age' attribute
setattr(emp1, 'age', 8) # Set attribute 'age' at 8
delattr(empl, 'age')    # Delete attribute 'age'

ویژگی‌های کلاس تعبیه شده

هر کلاس پایتون دارای ویژگی‌های تعبیه شده زیر است که می‌توان به آن‌ها با استفاده از اپراتور نقطه مانند هر ویژگی دیگری دسترسی پیدا کرد −

  • __dict__ − دیکشنری شامل فضای نام کلاس.

  • __doc__ − رشته مستندات کلاس یا None، اگر تعریف نشده باشد.

  • __name__ − نام کلاس.

  • __module__ − نام ماژولی که کلاس در آن تعریف شده است. این ویژگی در حالت تعاملی "__main__" است.

  • __bases__ − تاپلی شامل کلاس‌های پایه، با ترتیب حضور آن‌ها در لیست کلاس پایه، ممکن است خالی باشد.

برای کلاس بالا، بیایید سعی کنیم به همه این ویژگی‌ها دسترسی پیدا کنیم −

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

print "Employee.__doc__:", Employee.__doc__
print "Employee.__name__:", Employee.__name__
print "Employee.__module__:", Employee.__module__
print "Employee.__bases__:", Employee.__bases__
print "Employee.__dict__:", Employee.__dict__

هنگام اجرای کد بالا، نتیجه زیر تولید می‌شود −

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: ()
Employee.__dict__: {'__module__': '__main__', 'displayCount':
<function displayCount at 0xb7c84994>, 'empCount': 2, 
'displayEmployee': <function displayEmployee at 0xb7c8441c>, 
'__doc__': 'Common base class for all employees', 
'__init__': <function __init__ at 0xb7c846bc>}

تخریب اشیاء (گردآوری زباله)

پایتون به طور خودکار اشیاء غیرضروری (نوع‌های داخلی یا نمونه‌های کلاس) را حذف می‌کند تا فضای حافظه را آزاد کند. فرایندی که پایتون به طور دوره‌ای بلاک‌های حافظه را که دیگر در حال استفاده نیستند، بازیابی می‌کند ، گردآوری زباله گفته می‌شود.

گردآوری زباله پایتون در طول اجرای برنامه انجام می‌شود و زمانی فعال می‌شود که تعداد ارجاع به یک شیء به صفر برسد. تعداد ارجاع به یک شیء با تغییر تعداد نام‌های متعددی که به آن اشاره می‌کنند، تغییر می‌کند.

تعداد ارجاع به یک شیء افزایش می‌یابد هنگامی که به آن نام جدیدی اختصاص داده می‌شود یا در یک ظرف (لیست، تاپل یا دیکشنری) قرار می‌گیرد. تعداد ارجاع به یک شیء کاهش می‌یابد هنگامی که با استفاده از دستور del حذف می‌شود، مرجع آن تغییر می‌کند یا مرجع آن از دامنه خارج می‌شود. هنگامی که تعداد ارجاع یک شیء به صفر برسد، پایتون به طور خودکار آن را جمع‌آوری می‌کند.

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40> 

یک کلاس می‌تواند متد ویژه __del__() که به آن مخرب (destructor) نیز گفته می‌شود را پیاده‌سازی کند که در هنگام تخریب نمونه فراخوانی می‌شود. این متد ممکن است برای پاک کردن منابع غیر حافظه‌ای استفاده شود که توسط یک نمونه استفاده می‌شوند.

مثال

این کد مخرب __del__() نام کلاس یک نمونه را که در حال تخریب است، چاپ می‌کند −

class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print class_name, "destroyed"

pt1 = Point()
pt2 = pt1
pt3 = pt1
print id(pt1), id(pt2), id(pt3) # prints the ids of the obejcts
del pt1
del pt2
del pt3

هنگام اجرای کد بالا، نتیجه زیر تولید می‌شود −

3083401324 3083401324 3083401324
Point destroyed

توجهبهتر است کلاس‌های خود را در فایل‌های جداگانه تعریف کنید، سپس آن‌ها را در فایل اصلی برنامه خود با استفاده از دستور import وارد کنید.

ارث‌بری کلاس (Class Inheritance)

به جای این که از ابتدا شروع به نوشتن یک کلاس کنید ، شما می‌توانید با قرار دادن نام کلاس والد در پرانتز پس از نام کلاس جدید یک کلاس را از یک کلاس موجود ایجاد کنید. کلاس فرزند ویژگی‌های کلاس والد را به ارث می‌برد و می‌توانید از آن‌ها در کلاس فرزند استفاده کنید، به طوری که انگار در کلاس فرزند تعریف شده‌اند. کلاس فرزند همچنین می‌تواند اعضا و متدهای کلاس والد را بازنویسی کند.

نحوه نوشتن

کلاس‌های مشتق شده مشابه با کلاس والد خود تعریف می‌شوند؛ با این تفاوت که پس از نام کلاس، یک لیست از کلاس‌ها برای ارث‌بری مشخص می‌شود −

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

مثال:

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print "Calling parent constructor"

   def parentMethod(self):
      print 'Calling parent method'

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print "Parent attribute :", Parent.parentAttr

class Child(Parent): # define child class
   def __init__(self):
      print "Calling child constructor"

   def childMethod(self):
      print 'Calling child method'

c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

هنگام اجرای کد بالا، نتیجه زیر را تولید می‌کند −

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

به همین شکل، شما می‌توانید یک کلاس را از چندین کلاس والد مشتق کنید به شرح زیر −

class A:        # define your class A
.....

class B:         # define your class B
.....

class C(A, B):   # subclass of A and B
.....

.

می‌توانید از توابع issubclass() و isinstance() برای بررسی ارتباط دو کلاس و نمونه‌ها استفاده کنید.

  • تابع بولی issubclass(sub, sup) مقدار True برمی‌گرداند اگر کلاسsub یک زیرکلاس از کلاس اصلی sup باشد.

  • تابع بولی isinstance(obj, Class) مقدار True برمی‌گرداند اگر obj یک نمونه از کلاس Class باشد یا یک نمونه از یک زیرکلاس از کلاس Class باشد.

Override کردن متدها

همیشه می‌توانید متدهای کلاس والد خود را override کنید. یکی از دلایل برای override کردن متدهای کلاس والد، این است که شاید نیاز به عملکرد ویژه یا متفاوت در زیرکلاس خود داشته باشید.

مثال

class Parent:        # define parent class
   def myMethod(self):
      print 'Calling parent method'

class Child(Parent): # define child class
   def myMethod(self):
      print 'Calling child method'

c = Child()          # instance of child
c.myMethod()         # child calls overridden method

زمانی که کد بالا اجرا می‌شود، نتیجه زیر را تولید می‌کند:

Calling child method

روش‌های بارگذاری پایه

در جدول زیر، برخی از قابلیت‌های عمومی که می‌توانید در کلاس‌های خودتان بارگذاری کنید لیست شده‌اند:

ردیف روش، توضیحات و فراخوانی نمونه
۱

__init__(self [, args...])

سازنده (با هر آرگومان اختیاری)

فراخوانی نمونه: obj = className(args)

۲

__del__(self)

مخرب، حذف یک شیء

فراخوانی نمونه: del obj

۳

__repr__(self)

نمایش رشته قابل ارزیابی

فراخوانی نمونه: repr(obj)

۴

__str__(self)

نمایش رشته قابل چاپ

فراخوانی نمونه: str(obj)

۵

__cmp__(self, x)

مقایسه شیء

فراخوانی نمونه: cmp(obj, x)

بارگذاری عملگرها

فرض کنید یک کلاس بردار ایجاد کرده‌اید برای نمایش بردارهای دوبعدی، چه اتفاقی می‌افتد وقتی از عملگر جمع برای جمع آنها استفاده می‌کنید؟ احتمالاً پایتون خطا می‌دهد.

اما می‌توانید متد __add__ را در کلاس خود تعریف کنید تا جمع بردارها را انجام دهد و در این صورت عملگر جمع به دستورات شما عمل می‌کند:

نمونه

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print v1 + v2

وقتی کد بالا اجرا می‌شود، نتیجه زیر را تولید می‌کند:

Vector(7,8)

مخفی‌کردن داده‌ها (Data Hiding)

ویژگی‌های یک شیء ممکن است در خارج از تعریف کلاس قابل مشاهده باشند یا نباشند. برای مخفی‌کردن ویژگی‌ها، نیاز است نام آن‌ها را با پیشوند __ قرار دهید و در این صورت این ویژگی‌ها برای خارج از کلاس مستقیماً قابل مشاهده نخواهند بود.

مثال

#!/usr/bin/python

class JustCounter:
   __secretCount = 0
  
   def count(self):
      self.__secretCount += 1
      print self.__secretCount

counter = JustCounter()
counter.count()
counter.count()
print counter.__secretCount

زمانی که کد بالا اجرا می‌شود، نتیجه زیر را تولید می‌کند −

1
2
Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

می‌توانید به اینگونه ویژگی‌ها به صورت object._نام_کلاس__نام_ویژگی دسترسی پیدا کنید. اگر خط آخر خود را با خط زیر جایگزین کنید، آنگاه کار خواهد کرد −

.........................
print counter._JustCounter__secretCount

وقتی کد بالا اجرا شود، نتیجه زیر را تولید می‌کند −

1
2
2