اگر اسم «شیءگرایی» (OOP) در زبان پایتون را شنیدهاید و برایتان تازگی دارد، جای نگرانی نیست. مفهوم شی گرایی در پایتون این است که بهجای پخشکردن دادهها و توابع در همه جای کد، قسمت های مرتبط را در قالب «شیء»هایی منسجم جمع کنیم تا کد تمیزتر و قابلفهمتری داشته باشیم.
شی گرایی در پایتون چیست؟
برای درک بهتر OOP در پایتون تصور کنید برنامهای دارید که با «دانشجو» و «ایمیل» سروکار دارد. در روش قدیمی تابعی برای ذخیرهٔ نام دانشجو، تابعی برای ثبتنام او در درس و کلی کد پراکنده برای اطلاعاتش داشتید. اما در شیءگرایی همهی اینها در قالب دو شئ «دانشجو» و «ایمیل» جمع میشوند؛ یعنی یک شیء که هم ویژگیهایی مثل نام و سن را دارد و هم میتواند کارهایی مثل ثبتنام در درس انجام دهد. شیء «ایمیل» هم ویژگیهایی مثل گیرنده و موضوع دارد و میتواند کاری مانند ارسال ایمیل را انجام دهد.
چهار مفهوم اساسی در شیءگرایی وجود دارد:
کپسولهسازی(Capsulation): دادهها و توابع مرتبط را در یک واحد (شیء) نگه میداریم تا همهچیز در مورد آن موضوع، مشخص و منظم باشد.
وراثت(Inheritance): میتوانیم کلاس یا شیء جدیدی بسازیم که بعضی ویژگیها(attributes) و رفتارهای کلاس دیگری را به ارث ببرد و در صورت نیاز چیزهای تازهای به آن اضافه کند(مانند فرزندی که برخی ويژگی های ژنتیکی را از پدرش به ارث میبرد اما رفتار های جدیدی هم دارد)
چندریختی(Polymorphism): به این معنی که اشیاء گوناگون میتوانند یک تابع یا متد یکسان (مثلاً
draw) داشته باشند، اما هر کدام آن را به روش خاص خودشان پیاده کنند. برای نمونه، اگر دو کلاسِ «دایره» و «مربع» هر دو متدdrawداشته باشند، با صدا زدنdrawروی شئ «دایره»، یک دایره کشیده میشود و با صدا زدن همان متدdrawروی شئ «مربع»، یک مربع کشیده میشود.انتزاع(Abstraction): گاهی جزئیات اضافه را پنهان میکنیم تا فقط کار اصلی نمایان باشد و استفاده از شیء آسانتر شود برای مثال لازم نیست هنگام روشن کردن لامپ کاربرد بداند سیمکشی چگونه انجام شده و همه این جزییات را پشت دیوار پنهان میکنیم تا کاربر صرفا با زدن یک کلید از روشن شدن لامپ لذت میبرد.
در پایتون، با تعریف class، اشیاء را میسازید. برای هر کلاس، میتوانید خاصیتها (مانند نام و سن) و تابعهایی (مثلاً ثبتنام در درس) تعریف کنید. این کار باعث میشود کدتان بهتر سازماندهی شود و راحتتر بتوانید آن را بخوانید، تغییر دهید یا گسترش دهید.
با یادگیری شی گرایی، نهتنها کدهایتان شستهورفتهتر میشوند بلکه با شیءگرایی در پایتون، گام بزرگی برای ساخت نرمافزارهای پیچیده و حرفهای برداشتهاید.
کلاس چیست؟
کلاس (Class) در برنامهنویسی شیءگرا مانند یک چهارچوب یا قالب برای ایجاد اشیاء است. کلاس تعیین میکند که اشیائی که بر اساس آن ساخته میشوند چه ویژگیها (اطلاعات/متغیرها) و چه رفتارهایی (توابع/متدها) خواهند داشت . وقتی یک کلاس جدید تعریف میکنیم، در واقع یک نوع دادهای جدید در برنامه ایجاد کردهایم که میتوان از آن نمونهسازی (instantiate) کرد و شیء ساخت. به بیان دیگر، ایجاد یک کلاس به معنی تعریف یک نوع شیء جدید است که میتوان بعداً اشیائی از آن نوع را در حافظه ایجاد نمود
در پایتون برای تعریف یک کلاس از کلمهی کلیدی class استفاده میکنیم. به عنوان مثال، در کد زیر یک کلاس ساده به نام Car تعریف شده است که دو ویژگی(attribute) (مدل و سال) و یک رفتار (متد info برای نمایش اطلاعات خودرو) دارد:
class Car:
def __init__(self, model, year):
# متد سازنده (constructor)
#که هنگام ساخت شیء جدید صدا زده میشود
self.model = model # ویژگی مدل خودرو
self.year = year # ویژگی سال ساخت خودرو
def info(self):
# یک رفتار (متد) که اطلاعات خودرو را چاپ میکند
print(f"این خودرو یک {self.model} مدل سال {self.year} است.")
در مثال بالا، متد __init__ که به آن constructor میگوییم، هنگام ساختهشدن هر شیء جدید یکبار صدا زده میشود و وظیفهی مقداردهی اولیهی ویژگیهای شیء را بر عهده دارد . در اینجا هر خودرو با یک مدل (model) و سال ساخت (year) مشخص میشود و این مقادیر هنگام ایجاد شیء تعیین میگردند. متد info نیز یک رفتار نمونهای از کلاس است که خصوصیات خودرو را در قالب یک جمله چاپ میکند.
به طور خلاصه، کلاسها ساختار و نقشهی شیءها را تعریف میکنند؛ با تعریف یک کلاس جدید، نوع دادهای جدیدی ایجاد میکنیم که ترکیبی از دادهها و توابع مرتبط را در خود جای میدهد
شیء در پایتون چیست؟
شیء (Object) به هر نمونهی(instance) ساختهشده از روی یک کلاس گفته میشود. اگر کلاس را یک قالب در نظر بگیریم، شیء محصول نهاییای است که از روی آن قالب ایجاد شده است. هر شیء نسخهای مستقل از کلاس خود است که مقادیر مشخص خود را برای ویژگیها دارد (در حالی که ساختار و رفتار کلی را از کلاس به ارث برده است) برای ساختن یک شیء جدید از یک کلاس در پایتون، کافی است همانند یک تابع، نام کلاس را به همراه پرانتز فراخوانی کنیم). مثلاً با استفاده از کلاس Car که در بخش قبل تعریف کردیم، میتوانیم شیء جدیدی به صورت زیر ایجاد کنیم:
ساخت یک نمونه (شیء) از روی کلاس Car
my_car = Car("Toyota Corolla", 2020)
# دسترسی به ویژگیها و متدهای شیء
print(my_car.model) # خروجی: Toyota Corolla
print(my_car.year) # خروجی: 2020
my_car.info() # خروجی: این خودرو یک Toyota Corolla مدل سال 2020 است.
در این مثال، my_car یک شیء از نوع Car است که ویژگیهای خود (model و year) را مستقل از سایر اشیاء نگهداری میکند. میتوانیم چندین شیء دیگر نیز از کلاس Car بسازیم؛ به عنوان مثال car2 = Car(“Honda Civic”, 2018) یک خودروی دیگر خواهد بود با مقادیر متفاوت برای ویژگیها. تمام این اشیاء، خصوصیات و رفتارهای تعریفشده در کلاس را دارند اما مقادیر خصوصیات در هر شیء میتواند متفاوت باشد. به عنوان نمونه، my_car.model برابر “Toyota Corolla” است در حالی که car2.model میتواند “Honda Civic” باشد. به همین ترتیب، فراخوانی متد ()info برای هر شیء، اطلاعات همان شیء را نمایش خواهد داد.
از نظر فنی، در پایتون هر شیء دارای یک نوع(type) یا کلاس مشخص است. مثلا نوع my_car همان Car است و میتوان این را با تابع داخلی type یا isinstance بررسی کرد:
type(my_car) #
isinstance(my_car, Car) # True (تأیید میکند که my_car نمونهای از کلاس Car است)
همانطور که دیده میشود، شیء my_car از دید مفسر پایتون یک نمونه از کلاس Car محسوب میشود. بنابراین هر شیء، وضعیت (state) مختص به خود را دارد که توسط ویژگیها(attributes) نگهداری میشود و مجموعه رفتارهایی را از کلاسش به ارث میبرد که توسط متدها تعریف شدهاند.
کپسولهسازی (Encapsulation) در پایتون
یکی از اصول کلیدی در OOP، کپسولهسازی است. کپسولهسازی به این معناست که دادهها و متدهای مرتبط با هم در دل یک کلاس به عنوان یک واحد منسجم بستهبندی شوند و جزئیات پیادهسازی داخلی آنها از سایر بخشهای برنامه مخفی بماند. این کار باعث میشود پیچیدگی داخلی یک کلاس از دید استفادهکنندگانش پنهان شود و فقط عملکردها و ویژگیهای ضروری و تعریفشده نمایش داده شود. در نتیجه، سایر قسمتهای برنامه بدون نگرانی از جزئیات درونی هر شیء، تنها از رابط (interface) عمومی آن استفاده میکنند. این جداسازی، علاوه بر سادهسازی درک برنامه، به حفظ یکپارچگی دادهها کمک میکند زیرا تغییر یا دستکاری مستقیم دادههای حساس از بیرون کلاس محدود میشود.
به طور معمول، در زبانهای شیءگرای کلاسیک (مثل Java/C++) برای اعمال کپسولهسازی از سطوح دسترسی (private, public, protected) استفاده میشود تا برخی از ویژگیها/متدهای کلاس «خصوصی» علامتگذاری شوند و از دسترس خارج از کلاس مخفی بمانند. اما در پایتون چنین مکانیزم سفت و سختی وجود ندارد (. پایتون به جای اجبار از طریق قواعد زبان، بیشتر بر توافق بین برنامهنویسان تکیه میکند: مرسوم است که برای نشاندادن خصوصی بودن یک ویژگی(attribute) یا متد، نام آن را با یک ـ (underscore) شروع کنیم . این یک قرارداد نامگذاری است (که به آن weak encapsulation هم میگویند)؛ مثلا اگر در یک کلاس attributeای به نام balanceـ تعریف شود، استفادهکنندگان کلاس متوجه میشوند که این یک عضو داخلی است و نباید مستقیماً آن را تغییر دهند. البته خود پایتون مانع دسترسی به چنین attributeای نمیشود، بلکه این صرفاً یک علامت هشداردهنده برای برنامهنویسان است .
برای ایجاد اعضای کاملاً مخفی، پایتون مکانیزمی به نام Name Mangling (تغییر نام خودکار) دارد: اگر نام یک ویژگی(attribute) را با دو خط زیر شروع کنیم (__مثل__این)، مفسر به طور خودکار نام آن را در داخل کلاس تغییر میدهد تا از بیرون کلاس مستقیم قابل دسترس نباشد. به عنوان مثال:
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self.__balance = balance # ویژگی با نام خصوصی (name mangling)
account = BankAccount("Ali", 1000)
print(account.owner) # خروجی: Ali
print(account.__balance) # AttributeError: ویژگی پیدا نشد
در اینجا تلاش برای دسترسی مستقیم به balanceــ با خطا مواجه میشود، چرا که پایتون نام واقعی آن را به صورت _BankAccount__balance تغییر داده است تا از دسترس خارج شود. البته همچنان اگر واقعاً قصد داشته باشیم، میتوانیم با همین نام تغییریافته به ویژگی دسترسی پیدا کنیم (account._BankAccount__balance)، اما ایده این است که برنامهنویس چنین کاری را انجام ندهد.
راه حل صحیحتر برای دسترسی کنترلشده به ویژگیهای داخلی، استفاده از متدهای getter و setter یا در پایتون ترجیحاً استفاده از پراپرتیها (Properties) است . پراپرتی در پایتون با دکوریتور property@ پیاده میشود و اجازه میدهد برای یک ویژگی، متدهای خاصی جهت گرفتن مقدار (getter) و تنظیم مقدار (setter) تعریف کنیم، بدون اینکه نحو استفاده از آن برای بیرون کلاس تفاوتی کند. این یعنی از دید مصرفکننده، ویژگی همچنان به شکل یک attribute معمولی استفاده میشود، اما در پشت صحنه کدهای دلخواه ما برای اعتبارسنجی یا اعمال محدودیت اجرا میشوند. به کمک این قابلیت میتوان منطقهای کنترلی (مثلاً جلوگیری از قرار دادن مقدار نامعتبر) را روی خصوصیات پیاده کرد، بدون اینکه رابط عمومی کلاس (نحوه استفاده از آن) تغییری کند . برای مثال:
class Person:
def __init__(self, name, age):
self.name = name
self._age = None
self.age = age # استفاده از setter برای مقداردهی
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("سن نمیتواند منفی باشد!")
self._age = value
person = Person("Reza", 30)
person.age = 35 # OK
person.age = -5 # خطا: ValueError
print(person.age) # 35
در این قطعه کد، ویژگی age به صورت کپسولهشده پیادهسازی شده است. هنگام مقداردهی سن فرد، متد setter بررسی میکند که مقدار منفی نباشد. بدین ترتیب اعتبارسنجی درون کلاس و فراهم کردن یک رابط(interface) ساده و امن نمونهای از کپسولهسازی عملی در پایتون است. در مجموع، کپسولهسازی با پنهانسازی دادههای داخلی و فراهم کردن متدهای کنترلشده برای دسترسی به آنها، امنیت و پایداری دادهها را تضمین میکند و از تغییرات ناخواسته و باگهای ناشی از آن جلوگیری مینماید.
وراثت (Inheritance) در پایتون
وراثت مکانیزمی در شیءگرایی است که اجازه میدهد هنگام تعریف یک کلاس جدید آن کلاس کلیهی ویژگیها و رفتارهای یک کلاس دیگر را به ارث ببرد و صرفاً در صورت لزوم، در آنها تغییراتی ایجاد یا موارد جدیدی اضافه کند . به کلاس اصلی اصطلاحاً پدر یا ابرکلاس (Superclass/Parent) و به کلاس فرزند اصطلاحاً زیرکلاس (Subclass/Child) گفته میشود. با استفاده از وراثت میتوان یک ساختار سلسلهمراتبی (کلاسهای عمومیتر در بالا و کلاسهای تخصصیتر در پایین) ایجاد کرد و بخشهای مشترک را در کلاس والد تعریف نمود. این کار باعث استفادهی مجدد از کد و جلوگیری از تکرار میشود، چرا که زیرکلاسها لازم نیست ویژگیها/متدهای مشترک را دوباره تعریف کنند تعریف یک کلاس فرزند در پایتون بسیار ساده است. کافی است هنگام تعریف کلاس، نام کلاس والد را درون پرانتز بعد از نام کلاس ذکر کنیم. برای مثال، کلاس Dog را به عنوان زیرکلاس Animal تعریف میکنیم:
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name} صدایی از خود تولید میکند...")
#کلاس سگ که ویژگیهای کلاس حیوان را به ارث میبرد
class Dog(Animal):
def __init__(self, name):
super().__init__(name) # فراخوانی سازندهی کلاس پدر
def speak(self):
print(f"{self.name} واق واق میکند!") # سگها صدای واق واق دارند
# یک زیرکلاس دیگر
class Cat(Animal):
def __init__(self, name):
super().__init__(name) # فراخوانی سازندهی کلاس پدر
def speak(self):
print(f"{self.name} میو میکند!") # گربهها صدای میو دارند
در این مثال، کلاس Animal یک کلاس General یا عمومی است که تنها خصوصیت مشترک بین حیوانات (نام) و یک متد speak پیشفرض را تعریف کرده است. سپس Dog به عنوان sub class از Animal تعریف شده . در سازندهی Dog از تابع ()super استفاده شده است تا بخش مشترک سازندهی کلاس والد (Animal.__init__) فراخوانی شده و مقداردهی مربوط به name در کلاس والد انجام شود. با این کار، نیاز به نوشتن مجدد کد مقداردهی name در کلاس Dog نیست و این کار به کلاس والد یا parent محول شده است. همچنین، کلاس Dog متد speak را بازنویسی (override) کرده تا پیام مخصوص به سگ (واق واق) را چاپ کند. کلاس Cat نیز که از Animal ارثبری میکند، متد speak را بازنویسی کرده است.
اکنون تمام ویژگیهای تعریفشده در Animal (مثلاً ویژگی name و متد speak) به طور ضمنی در Dog و Cat وجود دارند، بدون اینکه مستقیماً در آن کلاسها تعریف شده باشند. میتوان اشیائی از کلاسهای Dog و Cat ساخت و از آنها استفاده کرد:
dog = Dog("Rex")
cat = Cat("Maloos")
dog.speak() # خروجی: Rex واق واق میکند
cat.speak() # خروجی: Maloos میو میکند
print(dog.name, cat.name) # خروجی: Rex Maloos
در اینجا dog و cat هر دو خصوصیت name را (که در Animal تعریف شده بود) دارند و متد speak مخصوص خود را اجرا میکنند. override متدها در زیرکلاسها این امکان را میدهد که رفتار کلاس والد برای موارد خاص تغییر کند یا حتی توسعه پیدا کند، بدون اینکه ساختار کلی کلاس والد تغییر داده شود . اگر لازم باشد میتوان در متد override شده از تابع ()super کمک گرفت تا علاوه بر رفتار جدید، رفتار والد نیز اجرا شود. برای نمونه، اگر میخواستیم متد speak در Dog هم متن والد («صدایی از خود تولید میکند…») را چاپ کند و هم متن جدید («واق واق میکند!») را، میتوانستیم در Dog.speak ابتدا ()super().speak را صدا بزنیم و سپس پیام جدید را چاپ کنیم.
مزیت وراثت این است که با تعریف خصوصیات و اعمال مشترک در کلاس پایه، تمام زیرکلاسها به آنها دسترسی دارند و در عین حال امکان تخصصیسازی رفتار در زیرکلاسها وجود دارد. این قابلیت به سازماندهی بهتر کد در سلسله مراتبهای مفهومی کمک میکند. برای مثال، در یک بازی رایانهای ممکن است کلاس پایهای به نام GameObject داشته باشیم و از آن کلاسهای فرزند مانند Player, Enemy, Item و … مشتق کنیم که هر کدام ویژگیها و رفتارهای خاص خود را علاوه بر ویژگیهای مشترک به ارثبردهشده دارند. به این ترتیب، ساختار پروژه منظمتر و تغییرات آتی آسانتر خواهد بود (چرا که تغییر در کلاس پایه بهصورت خودکار در همه زیرکلاسها اثر میگذارد).
چندریختی (Polymorphism) در پایتون
چندریختی (Polymorphism) یعنی ما میتوانیم با اشیای مختلفی که رفتاری مشابه دارند، به یک شکل برخورد کنیم، حتی اگر این اشیا از کلاسهای متفاوتی باشند.
به زبان خیلی ساده: تصور کن چند نوع حیوان داریم، مثل سگ و گربه. هر کدام از آنها میتوانند «حرف بزنند» (یعنی متد speak دارند)، اما هر کدام به روش خودشان. حالا اگر یک لیست از این حیوانها داشته باشیم، میتوانیم روی همهشان ()speak را اجرا کنیم، بدون اینکه لازم باشد بدانیم کدامشان سگ است و کدام گربه. هر کدام خودش میداند باید چه کاری انجام دهد. این یعنی: یک دستور واحد واکنش متفاوت با توجه به نوع شیء.
این ترتیب، با استفاده از یک روش ثابت، میتوانیم با اشیای مختلف کار کنیم؛ روشی که کد را سادهتر، منعطفتر و قابلگسترشتر میکند.
animals = [Dog("Rex"), Cat("Maloos"), Dog("Baloot")]
for animal in animals:
animal.speak()
خروجی این کد به ترتیب میتواند چیزی شبیه این باشد:
Rex واق واق میکند
Maloos میو میکند
Baloot واق واق میکند
همانطور که میبینید، ما بدون توجه به نوع دقیق هر شیء (Dog یا Cat بودن) صرفاً متد مشترک ()speak را فراخوانی کردیم و پایتون خود تصمیم گرفت که وابسته به نوع شیء، کدام پیادهسازی (Dog.speak یا Cat.speak) اجرا شود.
duck typing و چندریختی(polymorphism)
در پایتون، چندریختی فقط محدود به وراثت و کلاسهای مرتبط نیست؛ راه سادهتر و انعطافپذیرتری هم وجود دارد که به آن duck typing میگویند.
ایدهاش ساده است: “اگر یک شیء میتواند مثل اردک راه برود و مثل اردک صدا کند، ما با آن طوری رفتار میکنیم که انگار واقعاً اردک است — حتی اگر اسمش «اردک» نباشد :)”
یعنی در پایتون، مهم نیست شیء از چه کلاسی ساخته شده؛ اگر متدی که ما میخواهیم (مثلاً speak) را دارد، میتوانیم بدون هیچ نگرانی از آن استفاده کنیم. پایتون بررسی نمیکند که این شیء دقیقاً از کجا آمده؛ فقط کافی است رفتاری را که نیاز داریم داشته باشد.
این ویژگی باعث میشود کدها سادهتر و انعطافپذیرتر شوند و بدون وابستگی شدید به ساختار کلاسها بتوانیم از اشیای مختلف استفاده کنیم.
class Circle:
def draw(self):
print("در حال رسم دایره هستم.")
class Rectangle:
def draw(self):
print("در حال رسم مستطیل هستم.")
def render_shape(shape):
shape.draw()
# ایجاد نمونههایی از اشکال مختلف
circle = Circle()
rectangle = Rectangle()
# فراخوانی تابع برای رسم اشکال
render_shape(circle) # خروجی: در حال رسم دایره هستم.
render_shape(rectangle) # خروجی: در حال رسم مستطیل هستم.
کاربرد Duck Typing
در Duck Typing نیازی به ارثبری یا تعریف یک کلاس پایه و مشترک نیست؛ تنها کافی است شیء رفتار مورد انتظار (در اینجا متد draw) را داشته باشد. این امر برنامه را بسیار انعطافپذیر میکند؛ زیرا میتوانید کلاسهای جدیدی با متد draw اضافه کنید و تابع render_shape بدون هیچ تغییری کار خواهد کرد.
باید توجه داشته باشید که Duck Typing انعطافپذیرتر است و به شما این امکان را میدهد که هر شیء با متدهای مشابه را بدون نیاز به ارثبری از یک کلاس پایه استفاده کنید. Polymorphism به شما این امکان را میدهد که از اینترفیسها یا کلاسهای پایه استفاده کنید تا رفتار مشابه از کلاسهای مختلف بگیرید و کد شما قابل مدیریتتر باشد.بصورت معمول Polymorphism ساختار منظم تری ایجاد میکند که برای بخش های پیچیده مناسب تر است.
Abstraction در پایتون
«انتزاع» یا «Abstraction» در برنامهنویسی یعنی نمایش قسمتهای لازم و پنهان کردن جزئیات فنی. این تکنیک باعث سادهتر شدن کدنویسی و استفاده راحتتر از ابزارها و کتابخانهها میشود. برای مثال، هنگام استفاده از کتابخانههایی مثل SQLAlchemy، شما فقط متدهای مشخص (مثل ()create یا ()update) را فراخوانی میکنید و جزئیات درونی پنهان میماند. این باعث کاهش پیچیدگی و تکرار کمتر کد میشود، زیرا نیازی به درگیرشدن با جزئیات پشت صحنه نیست.
با abstraction میتوانید برنامه را به بخشهای جداگانه با واسط (interface) مشخص تقسیم کنید. این روش، کد را انعطافپذیر کرده و امکان تغییر آسان پیادهسازیها را فراهم میکند؛ اگر بخواهید الگوریتم یا منطق بخشی از برنامه را تغییر دهید، بخشهای دیگر بدون تغییر باقی میمانند.
همچنین، این روش به تیمهای برنامهنویسی کمک میکند تا سریعتر کار کنند و کد یکدیگر را بهتر درک کنند؛ زیرا نیازی نیست همه اعضای تیم از تمام جزئیات درونی سیستم اطلاع داشته باشند. در نهایت، استفاده از abstraction باعث افزایش کیفیت، کاهش خطا و سادهتر شدن نگهداری کد میشود.
Abstraction در کلاسها و متدها (کلاس انتزاعی)
در اینجا، شما یک کلاس انتزاعی (abstract class) میسازید که شامل متدهایی است که فقط تعریف شدهاند ولی پیادهسازی نشدهاند. کلاسهای دیگری که از این کلاس انتزاعی ارثبری میکنند، مجبورند که این متدها را پیادهسازی کنند.
اینگونه کلاسها به ما این امکان را میدهند که طراحی و معماری نرمافزار را سازمانیافتهتر انجام دهیم. وقتی یک متد در یک کلاس انتزاعی تعریف میشود، میخواهیم اطمینان حاصل کنیم که تمام کلاسهای فرزند که از آن ارث میبرند، آن متد را پیادهسازی کنند. اینگونه از پراکندگی و عدم یکپارچگی مخصوصا زمانهایی که چند برنامه نویس هم زمان روی یک پروژه در حال توسعه هستند.
from abc import ABC, abstractmethod
# یک کلاس انتزاعی برای شکلها
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * (self.radius ** 2)
# نمونهسازی مستقیم از کلاس انتزاعی (امکان پذیر شده چون موجب خطا میشود)
# shape = Shape() # خطا: نمیتوان یک کلاس انتزاعی را نمونهسازی کرد.
# استفاده از کلاسهای فرزند
shapes = [Rectangle(3, 4), Circle(5)]
for shape in shapes:
print(shape.area()) # خروجی: 12 و 78.5







