شی گرایی در پایتون

پایتون به طور کامل از object oriented پشتیبانی میکند. همه چیز در پایتون کلاس و یا شی ای از یک کلاس است. string, list, dictionary, tuple و .. همه و همه از جمله object هایی هستند که از کلاس های built-in پایتون نمونه سازی شده اند.

کلاس

کلاس در زبان های برنامه نویسی به منزله الگو یا pattern برای شی است. در حقیقت ساختار شی را چگونگی تعریف کلاس مشخص می کند. هر کلاس شامل مجموعه ای از رفتار ها ست که متد ها آن ها را پیاده سازی می کنند و مجموعه ای از خصوصیات.

کلاس با استفاده از کلمه کلیدی class، نام کلاس و علامت : و سپس با indent در ادامه اعضای کلاس تعریف می شود:

class <ClassName>:
    <statement1>
    <statement2>
    .
    .
    <statementN>

اعضای تشکیل دهنده کلاس:

  • ویژگی های کلاس (Class Attributes)

متغیر هایی هستند که به طور مستقیم در کلاس تعریف می شوند و با اعضای کلاس به اشتراک گذاشته می شوند. به صورت instance_ name.variable_name یا class_name.variable_name می توان به آن ها دسترسی پیدا کرد.

class Student:
    school_name = 'XYZ School'

مقدار school_name برای همه نمونه ها یکسان خواهد ماند مگر اینکه صریحاً مقدار آن را در خود کلاس تغییر دهیم.

>>> Student.school_name 
'XYZ School' 
>>> std = Student()
>>> std.school_name 
'XYZ School'

همانطور که مشخص است متغیر کلاس و متغیر های نمونه های ساخته شده از کلاس دقیقا یکی هستند.

student1 = Student()
student2 = Student()
id(student1.school_name) == id(Student.school_name) == id (student2.school_name)

برای تغییر مقدار متغیر های کلاس به صورت زیر عمل می کنیم (تغییر در نمونه تاثیری در مقدار متغیر کلاس ندارد).

>>> Student.school_name = 'ABC School'
>>> student1 = Student()
>>> student1.school_name
'ABC School'
>>> student1.school_name = 'My School'
>>> student1.school_name
'My School' 
>>> Student.school_name
'ABC School' 
>>> student2 = Student()
>>> student2.school_name
'ABC School'
  • سازنده (Constructor)

هرگاه یک شی از کلاس ساخته می شود، متد سازنده به طور خودکار صدا زده می شود(پرانتز جلوی نام کلاس constructor را فراخوانی می کند). در پایتون متدهای ()__init__ و ()__new__ به عنوان constructor شناخته می شود. ()__new__ هنگام ایجاد یک شی فراخوانی می شود و ()__init__ برای مقداردهی اولیه شی فراخوانی می شود، این متد با یک ورودی خاص به نام self است (self یک نام متعارف است ولی نامگذاری اولین پارامتر دلخواه است). متد سازنده برای تعریف attribute های یک نمونه و مقداردهی اولیه آنها استفاده می شود.

class Student:
    def __init__(self): # constructor method
        print('Constructor invoked')

با هر بار نمونه سازی از کلاس فوق تابع سازنده صدا زده می شود:

>>>s1 = Student()
Constructor invoked
>>>s2 = Student()
Constructor invoked
  • ویژگی های وهله ای (Instance Attributes)

شامل attribute ها و property هایی هستند که به یک instance اضافه می شوند. این اعضا در متد سازنده کلاس اضافه می شوند. در مثال زیر name و age ویژگی هایی هستند که به کلاس اضافه شده اند.

class Student:

    def __init__(self): # constructor
        self.name = "'' # instance attribute
        self.age = 0 # instance attribute

برای دسترسی به instance attribute ها به صورت زیر عمل میکنیم:

>>> std = Student()
>>> std.name
""
>>> std.age
0

و برای مقدار دهی به آنها به صورت زیر عمل میکنیم:

>>> std = Student()
>>> std.name = "Bill"  # assign value to instance attribute
>>> std.age=25          # assign value to instance attribute
>>> std.name             # access instance attribute value
Bill
>>> std.age                # access value to instance attribute
25

در صورتی که instance attribute ها را در متد سازنده مقدار دهی کنیم می توان نمونه سازی را به صورت زیر انجام داد:

class Student:
    def __init__(self, name, age): 
        self.name = name
        self.age = age


>>> std = Student('Bill',25)
>>> std.name
'Bill'
>>> std.age
25
  • پراپرتی ها (Properties)

در پایتون property رابطی برای instance attribute ها فراهم می کند و آن ها را کپسوله می کند. property ها به دو صورت پیاده سازی می شوند: با استفاده از متد ()property و با استفاده از دکوراتور [email protected]

هدف از تعریف یک property تغییر رفتار یک متد است، به نحوی که بتوان از آن مانند یک attribute استفاده کرد. encapsulate و اعتبار سنجی روی attribute ها از مهمترین دلایل تعریف یک property است.

تعریف property با استفاده از متد ()proprty:

class Student:
    def __init__(self):
        self.__name=''
    def setname(self, name):
        print('setname() called')
        self.__name=name
    def getname(self):
        print('getname() called')
        return self.__name
    name = property(getname, setname)

در مثال بالا با استفاده از دو متد setname و getname یک property ایجاد کرده و آن را در متغیر name قرار دادیم. متد ()property متغیر name__ را به صورت private در آورده و برای دسترسی به آن باید از nameاستفاده کنیم که در واقع متد های getnam و setname را فراخوانی می کند.

>>> std = Student()
>>> std.name="Steve"
setname() called
>>> std.name
getname() called
'Steve'

برای تعریف یک property بهتر است از دکوراتور [email protected] استفاده کنیم که یک دکوراتور برای متد ()property است و یک property را تعریف می کند.

مثال زیر را در نظر بگیرید:

class User:
    def __init__(self, age):
        self.age = age
 
    @property
    def age(self):
        return self._age

در مثال بالا [email protected] تابع ()age را تعریف می کند که متغیر private به نام age__ را برمی گرداند. در این مرحله می توان از ()age به عنوان یک property استفاده کرد.

class User:
    def __init__(self, age):
        self.age = age
 
    @property
    def age(self):
        return self.__age
    
    @age.setter
    def age(self, value):
        if value <= 0 or value > 99:
            self.__age = 0
            print('Wrong age in input')
            return
    self.__age = value
        print(f'Age days is {self.__age * 365}')

در این مرحله متد ()age را به گونه ای overload کردیم که یکی از آنها عملیات set کردن و دیگری عملیات get را انجام می دهد.

این property سن کاربر را دریافت کرده و در صورتی که بین ۰ و ۹۹ سال باشد تعداد روزهای آن را باز می گرداند.

  • متد ها (Methods)

در هر کلاس می توان با استفاده از کلمه کلیدی def یک تابع ایجاد کرد. معمولا (به جز کلاس متد ها) متدها self را به عنوان اولین پارامتر در ورودی می پذیرند که این نام قراردادی است و استفاده از نام دلخواه نیز مجاز است، هرچند استاندارد نیست. self به نمونه ساخته شده از کلاس اشاره می کند.

class Student:
    def __init__(self, name, age): 
        self.name = name 
        self.age = age 
    def displayInfo(self): # class method
        print('Student Name: ', self.name,', Age: ', self.age)

برای دسترسی به متد ها (instance method) ابتدا از کلاس instance گرفته و سپس به صورت instance_name.method_name به آن دسترسی خواهیم داشت.

>>> std = Student('Steve', 25)
>>> std.displayInfo()
Student Name: Steve , Age: 25

با استفاده از ()type می توان به نام کلاس از instance یک دسترسی پیدا کرد:

>>> title = 'python'
>>> print(type(title))
<class 'str'>

کلاس ها با استفاده از pass می توانند هیچ عضوی نداشته باشد، مانند کلاس زیر:

class Student:
    pass

کلمه کلیدی pass یک null operation است که وقتی که اجرا شود هیچ اتفاقی نمی افتد.
در مواقعی کاربرد دارد که برای پرهیز از دریافت خطا نیاز باشد یک بلاک هیچ کاری نکند، مثلا هنوز پیاده سازی تکمیل نشده است .

نمونه سازی (instantiation)

برای نمونه سازی از کلاس تنها کافی است نام کلاس را شبیه به اجرای یک کلاس بنویسیم:

student = Student()

اگر در متد سازنده instance variable ست شده باشد آنگاه بایددر زمان نمونه سازی مقادیر مربوط به آن ها را در instance قرار داد:

student = Student(instancevariable1, ... instance_variablen)

برنامه نویسی شی گرایی (Object Oriented Programming)

شی گرایی یکی از روش های برنامه نویسی است که امکان استفاده مجدد (Code reusability) از کد ها را فراهم می کند و ساختار منسجم برنامه های ایجاد شده با این روش باعث محبوبیت در بین برنامه نویسان است.

تکنیک های شی گرایی عبارت است از: inheritance, polymorphism, abstraction, ... .

ارث بری

با فرض اطلاع از مفهوم ارث بری به سراغ پیاده سازی inheritance در پایتون می رویم:

کلاس child از کلاس parent ارث بری می کند (inherit می شود) اگر داخل پرانتز بعد از نام کلاس child نام کلاس parent نوشته شود. به کلاس parent کلاس base یا پدر می گویند و به کلاسی را که ارث بری می کند کلاس فرزند گفته می شود.

class Parent:
    statements
                    
class Child(Parent):
    statements

>>> q1 = QuadriLateral(7,5,6,4)
>>> q1.perimeter()
perimeter=22ر دسترس است و ارث بری این گونه موجب استفاده مجدد از کد ها می شود.

class QuadriLateral:
    def __init__(self, a, b, c, d):
        self.side1=a
        self.side2=b
        self.side3=c
        self.side4=d

    def perimeter(self):
        p=self.side1 + self.side2 + self.side3 + self.side4
        print("perimeter=",p)

در کلاس QuadriLatral (چهار ضلعی) چهار ضلع را به عنوان instance variable تعریف کرده ایم و تابع perimeter مجموع ضلع های نمونه ساخته شده را برمی گرداند.

>>> q1 = QuadriLateral(7, 5, 6, 4)
>>> q1.perimeter()
perimeter=22

حال ارث بری را در کلاس جدید اعمال می کنیم:

class Rectangle(QuadriLateral):
    def __init__(self, a, b):
        super().__init__(a, b, a, b)

از آنجا که کلاس QuadriLateral در سازنده خود instance variable هایی را دریافت می کند، کلاس فرزند هم باید آن ها را به کلاس پدر ارسال کند، کلمه کلیدی super به کلاس پدر اشاره می کند. در مثال بالا کلاس فرزند instance variable ها را به سازنده کلاس پدر ارسال کرده است.

>>> r1 = Rectangle(10, 20)
>>> r1.perimeter()
perimeter = 60

همانطور که مشاهده می کنید کلاس فرزند متد perimeter را از کلاس پدر به ارث برده است.

ارث بری چندگانه

پایتون مانند ++C و برخلاف java و #C از ارث بری چندگانه (Multi Inheritance) پشتیبانی می کند.

class Base1(object): 
    def __init__(self):
        self.str1 = "Geek1"
	print("Base1") 

class Base2(object): 
    def __init__(self): 
        self.str2 = "Geek2"		
	print("Base2") 

class Derived(Base1, Base2): 
    def __init__(self): 
        Base1.__init__(self) 
	Base2.__init__(self) 
	print("Derived") 
    def printStrs(self): 
        print(self.str1, self.str2) 

ob = Derived() 
ob.printStrs() 

خروجی:

Base1
Base2
Derived
Geek1 Geek2

ارث بری چند سطحی (Multilevel inheritance)

زمانی که ارث بری در چند مرحله و از کلاسی به کلاس دیگر انجام شود به آن Multilevel inheritance گفته می شود.

class Base(object): 
    def __init__(self, name): 
        self.name = name
    def getName(self): 
        return self.name 

class Child(Base): 
	def __init__(self, name, age): 
		Base.__init__(self, name) 
		self.age = age 
	def getAge(self): 
		return self.age
 
class GrandChild(Child):
    def __init__(self, name, age, address): 
        Child.__init__(self, name, age) 
	self.address = address 

    def getAddress(self): 
         return self.address	

g = GrandChild("Geek1", 23, "Noida") 
print(g.getName(), g.getAge(), g.getAddress()) 

خروجی:

Geek1 23 Noida()

بازنویسی متد (Overriding in Python)

در صورت لزوم می توان متد های کلاس پدر را در کلاس فرزند بازنویسی (override) کرد. instance به هر دو متد دسترسی خواهد داشت اما در زمان initialize متد override شده را فراخوانی می کند.

class Rectangle(QuadriLateral):
    def __init__(self, a,b):
        super().__init__(a, b, a, b)

    def area(self):
        a = self.side1 * self.side2
        print("area of rectangle=", a)


class Square(Rectangle):
    def __init__(self, a):
        super().__init__(a, a)
    def area(self):
        a = pow(self.side1, 2)
        print('Area of Square: ', a)

آنجا همانطور که مشاهده می کنید متد override شده در کلاس Square (کلاس فرزند) اجرا خواهد شد.

>>>s = Square(10)
>>>s.area()
Area of Square: 100

چند ریختی (Polymorphism)

چند ریختی یا polymorphism امکان تعریف متدهای همنام کلاس پدر در کلاس فرزند است. در حقیقت چند ریختی به معنی استفاده از توابع با نام های یکسان و فرم های متفاوت است.

  • چند ریختی با استفاده از تابع

در این روش با استفاده از تابعی که در ورودی object دریافت می کند و متد مربوط به آن object را فراخوانی می کند polymorphism را پیاده سازی می کنند.

class Bear(object):
    def sound(self):
        print("Groarrr")
    
class Dog(object):
    def sound(self):
        print("Woof woof!")
    
    def makeSound(animalType):
        animalType.sound()
    

bearObj = Bear()
dogObj = Dog()
    
makeSound(bearObj)
makeSound(dogObj)

در این مثال تابع makeSound در ورودی object کلاس را دریافت کرده و متد مربوط به آن را اجرا می کند.

  • چند ریختی با استفاده از متد های کلاس
class Bird:
    def intro(self):
        print("There are different types of birds")
    def flight(self):
        print("Most of the birds can fly but some cannot")

class Parrot(Bird):
    def flight(self):
        print("Parrots can fly")

class Penguin(Bird):
    def flight(self):
        print("Penguins do not fly")


obj_bird = Bird()
obj_parr = Parrot()
obj_peng = Penguin() 

obj_bird.intro()
obj_bird.flight() 

obj_parr.intro()
obj_parr.flight() 
obj_peng.intro()
obj_peng.flight()

همانگونه که در خروجی مشاهده می کنیم کلاس های Penguin و parrot متد flight را override کرده اند.

There are different types of birds
Most of the birds can fly but some cannot
There are different types of bird
Parrots can fly
There are many types of birds
Penguins do not fly
  • چند ریختی با استفاده از abstract

در این روش تمام متدهایی که کلاس فرزند باید پیاده سازی کند را در یک کلاس abstract قرار داده و بدنه متد ها در کلاس abstract شامل NotImplementedErro باشد. در این صورت کلاس فرزند موظف است متدهای کلاس پدر را پیاده سازی کند و هر کلاس آن گونه که لازم است پیاده سازی را انجام می دهد.

class Document:
    def __init__(self, name):
        self.name = name
    
    def show(self):
        raise NotImplementedError("Subclass must implement abstract method")
    
class Pdf(Document):
    def show(self):
        return 'Show pdf contents!'

class Word(Document):
    def show(self):
        return 'Show word contents!'

documents = [Pdf('Document1'),
Pdf('Document2'),
Word('Document3')]

for document in documents:
    print document.name + ': ' + document.show()

خروجی

Document1: Show pdf contents!
Document2: Show pdf contents!
Document3: Show word contents!

رابط کاربری (Interface)

استفاده مهم interface در زبان هایی که single inheritance هستند به این دلیل است که امکان ارث بری یک کلاس از چندین کلاس وجود ندارد ولی ارث بری از چند interface ممکن است. در پایتون به دلیل پشتیبانی از multi inheritance نیاز به interface ضروری به نظر نمی رسد.

اانتزاعی (Abstraction)

کلاسی که شامل یک یا چند متد abstract باشد را کلاس abstract می گویند. یک متد abstract متدی است که اعلان (declaration) می شود ولی شامل بدنه پیاده سازی شده نیست. کلاس abstract معمولا به عنوان base کلاس مورد استفاده قرار می گیرند. abstract ها زمانی استفاده می شوند که بخواهیم برای یک متد چندین پیاده سازی مختلف انجام دهیم یا کلاس هایی وجود دارند که متد خاصی را به شیوه های مختلف پیاده سازی می کنند.

برای ایجاد کلاس abstract در پایتون از کتابخانه abc استفاده می کنیم:

from abc import ABC,abstractmethod 

class Animal(ABC): 
	@abstractmethod
	def move(self): 
		pass

class Human(Animal): 
	def move(self): 
		print("I can walk and run") 

class Snake(Animal): 
	def move(self): 
		print("I can crawl") 

class Dog(Animal): 
	def move(self): 
		print("I can bark") 

class Lion(Animal): 
	def move(self): 
		print("I can roar") 
dog = Dog() 
dog.move()

خروجی

I can bark

در صورتی که از یک کلاس abstract نمونه سازی کنیم با خطای زیر مواجه می شویم:

>>> animal = Animal()
Traceback (most recent call last):
  File "/home/ffe4267d930f204512b7f501bb1bc489.py", line 19, in 
    c=Animal()
TypeError: Can't instantiate abstract class Animal with abstract methods move

با استفاده از دکوراتور[email protected]می توان property های abstract تعریف کرد

import abc 
from abc import ABC, abstractmethod 

class parent(ABC): 
	@abc.abstractproperty 
	def geeks(self): 
		return "parent class"

class child(parent): 
	@property
	def geeks(self): 
		return "child class"

try: 
	r =parent() 
	print( r.geeks) 

except Exception as err: 
	print (err) 
r = child() 
print (r.geeks) 

کلاس abstract را با استفاده از NotImplemetError می توان به صورت ضمنی پیاده سازی کرد به این صورت که متدهایی که در باید کلاس فرزند پیاده سازی شوند را در کلاس پدر اعلان کرده و کلاس اکسپشن NotImplementedError را raise می کنیم. در این صورت کلاس هایی که از کلاس پدر ارث بری می کنند چنانچه متدها را پیاده سازی نکنن با خطا مواجه خواهند شد.

class Document:
    def __init__(self, name):
        self.name = name
    def show(self):
        raise NotImplementedError ()

class PDF(Document):
    def show(self):
        print("PDF Document has been shown")