سلام دوستان...
در این قسمت از آموزش پایتون نگاه اولیه به کلاس ها داریم :
کلاس ها مقدار کمی نحو(syntax) جدید را معرفی می کنند، از جمله سه نوع شی جدید و برخی مفاهیم(semantics) جدید.
ساده ترین حالت تعریف کلاس به صورت زیر است:
class ClassName:
<statement-1>
.
.
.
<statement-n>
</statement-n></statement-1>
تعاریف کلاس، مانند تعاریف تابع(عبارت def) باید پیش از اینکه اثری داشته باشند، اجرا شوند. (می توانید تعریف یک کلاس را در شاخه ای از یک عبارت if یا درون یک تابع قرار دهید.)
در عمل، عبارات درون تعریف یک کلاس، معمولا تعاریف تابع خواهند بود، اما عبارات دیگر نیز مجار هستند و گاهی مفید—بعدا به این موضوع خواهیم پرداخت. تعاریف تابع درون یک کلاس معمولا یک شکل عجیبی از لیست آرگومان ها دارند، که توسط قراردادهای فراخوانی متدها نوشته شده اند—دوباره می گویم، این موضع بعدا توضیح داده خواهد شد.
وقتی تعریف یک کلاس وارد شود، یک فضای نام جدیدی ساخته می شود و به عنوان حوزه محلی استفاده می شود—بنابراین، تمامی تخصیص ها به متغیر های محلی، به این فضای نام جدید می روند. به خصوص، تعاریف تابع، نام تابع جدید را در اینجا اتصال می دهند.
زمانی که تعریف یک کلاس به صورت نرمال قرار گیرد(از طریق پایان)، یک شی کلاس ساخته می شود. این اساسا یک بسته بندی حول محتویات فضای نامی است که توسط تعریف کلاس ایجاد شده است. در بخش بعدی مباحث بیشتری درباره اشیای کلاس فرا خواهیم گرفت. حوزه محلی اصلی ( حوزه ای که دقیقا قبل از تعریف کلاس وارد شده است) باز گردانده شده است، و شی کلاس، به نام کلاس که در سربرگ تعریف کلاس است، محدود شده است. (ClassName در مثال).
اشیای کلاس از دو نوع عملیات پشتیبانی می کنند: ارجاع های ویژگی (attribute references) و نمونه گیری (instantiation).
ارجاع های ویژگی، از نحوه نگارش استاندارد مورد استفاده برای تمامی ارجاع های ویژگی در پایتون، استفاده می کنند: obj.name . تمامی نام های موجود در فضای نام کلاس، در زمان ساخت شی کلاس، نام های معتبر ویژگی هستند. بنابراین اگر تعریف کلاس به صورت زیر باشد:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
پس MyClass.i و MyClass.f ارجاع های ویژگی معتبر هستند، که به ترتیب یک عدد صحیح و یک شی تابع را باز می گردانند. همچنین می توان به ویژگی های کلاس مقداری را تخصیص داد، بنابراین می توانید مقدار MyClass.i را با تخصیص، تغییر دهید. __doc__ نیز یک ویژگی معتبر است، که docstring متعلق به کلاس "A simple example class" را باز می گرداند. نمونه گیری کلاس از نمادگذاری تابع استفاده می کند. فقط وانمود کنید که شی کلاس یک تابع بدون پارامتر است که یک نمونه جدید از کلاس را باز می گرداند. برای مثال(کلاس بالا را در نظر بگیرید):
x = MyClass()
عبارت بالا یک نمونه جدید از کلاس را ایجاد می کند و این شی را به متغیر محلی x تخصیص می دهد. عمل نمونه گیری (صدا زدن یک شی کلاس) یک شی خالی را ایجاد می کند. بسیاری از کلاس ها، تمایل به ایجاد اشیا با نمونه هایی که برای یک حالت اولیه خاص سفارشی شده باشند، دارند. بنابراین ممکن است یک کلاس یک متد خاص به نام __init__() را به صورت زیر تعریف کند:
def __init__(self):
self.data = []
زمانی که یک کلاس متد __init__() را تعریف می کند، نمونه گیری کلاس، به صورت خودکار __init__() را برای نمونه تازه ساخته شده کلاس فراخوانی می کند. بنابراین در این مثال، یک نمونه جدید و مقدار دهی اولیه شده توسط عبارت زیر قابل دستیابی است:
x = MyClass()
البته که متد __init__() میتواند برای انعطاف پذیری بیشتر دارای آرگومان باشد. در این صورت، آرگومان های داده شده به عملگر نمونه گیری کلاس، به __init__() پاس داده می شوند. برای مثال:
>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
حال با اشیای نمونه چه کار می توان کرد؟ تنها عملیات قابل فهم توسط اشیای نمونه، ارجاع های ویژگی هستند. دو نوع نام ویژگی معتبر وجود دارد: ویژگی های داده (data attributes) و متدها.
ویژگی های داده” متناظر است با “متغیر های نمونه” در Smalltalk و "اعضای داده” در C++ . ویژگی های داده نیازی به اعلان شدن ندارند؛ آنها همانند متغیر های محلی، در اولین جایی که مقداری به آنها تخصیص یابد، به وجود می آیند. برای مثال، اگر x نمونه MyClass که در بالا ایجاد شد، باشد، قطعه کد زیر مقدار 16 را بدون برجای گذاشتن ردی چاپ خواهد کرد.
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
نوع دیگری از ارجاع ویژگی نمونه (instance attribute reference)، متد است.
یک متد تابعی است که متعلق به یک شی است. ( در پایتون، واژه متد تنها مختص به نمونه های کلاس نیست: سایر انواع شی هم می توانند متد داشته باشند. برای مثال، اشیای لیست، متدهایی به نام های append ، insert ، remove ، sort و غیره دارند. اگر چه در مباحث بعدی منحصرا از واژه متد برای بیان متدهای اشیای نمونه کلاس استفاده می شود، مگر اینکه صریحا خلاف آن بیان شود.)
نام های معتبر متد مربوط به یک شی نمونه، به کلاس آن بستگی دارد. در تعریف، تمامی ویژگی های یک کلاس که اشیای تابع هستند، متدهای متناظر با نمونه های آن را تعریف می کنند. بنابراین در مثال ما، از آنجایی که MyClass.f یک تابع است، x.f یک ارجاع معتبر متد است. اما x.i (معتبر)نیست، زیرا MyClass.i (یک تابع) نیست. اما x.f چیزی مشابه MyClass.f نیست—این یک شی متد است، نه یک شی تابع.
معمولا یک متد دقیقا پس از بسته شدن (bound)، فراخوانی می شود:
x.f()
در مثال MyClass ، رشته 'hello world' باز گردانده می شود. اگر چه، فراخوانی بلافاصله یک متد، ضروری نیست: x.f یک شی متد است، و می تواند در زمانی دیگر ذخیره و صدا زده شود. برای مثال:
xf = x.f
while True:
print(xf())
تا آخر زمان به چاپ hello world ادامه می دهد. زمانی که یک متد فراخوانی می شود، دقیقا چه اتفاقی می افتد؟ ممکن است متوجه شده باشید که x.f() در بالا بدون آرگومان فراخوانی شده است، هر چند که تعریف تابع برای f() یک آرگومان مشخص کرده است. چه اتفاقی برای آرگومان افتاد؟ زمانی که تابعی نیاز به یک آرگومان دارد و بدون آرگومان فراخوانی می شود، پایتون حتما استثنایی را اعلام می کند—حتی اگر آن آرگومان واقعا استفاده نشده باشد.
در واقع ممکن است پاسخ را حدس زده باشید: نکته خاص درباره متد ها این است که شی نمونه به عنوان اولین آرگومان تابع پاس داده می شود. در مثال ما، فراخوانی x.f() دقیقا برابر با فراخوانی MyClass.f(x) است. به طور کلی، فراخوانی یک متد با لیستی از n آرگومان، برابر است با فراخوانی تابع متناظر آن، با لیستی از آرگومان ها که به واسطه درج شی نمونه متد، قبل از اولین آرگومان ساخته شده است.
اگر همچنان متوجه نحوه عملکرد متدها نمی شوید، نگاهی به پیاده سازی احتمالا میتواند قضیه را روشن کند. زمانی که یک ویژگی غیر داده از یک نمونه، ارجاع یافته باشد، کلاس نمونه جستجو می شود. اگر نام، یک ویژگی کلاس معتبر که یک شی تابع است را نشان دهد، یک شی متد به واسطه جمع کردن(اشاره گر ها به) شی نمونه و شی تابع که با هم در یک شی abstract پیدا شده اند، ساخته میشود: این شی متد است. زمانی که شی متد به همراه لیستی از آرگومان ها فراخوانی شده باشد، یک لیست آرگومان جدید از شی نمونه و لیست آرگومان ها ساخته می شود، و شی تابع با لیست آرگومان جدید فراخوانی می شود.
به طور کلی، متغیر های نمونه برای داده هستند و برای هر نمونه یکتا هستند. متغیر های کلاس برای ویژگی ها و متدها هستند و بین همه نمونه های کلاس به اشتراک گذاشته می شوند.
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
همانطور که در بخش سخنی درباره نام ها و اشیا بحث شد، داده های اشتراکی می توانند تاثیرات احتمالی شگفت انگیزی با اشیای تغییر پذیر درگیر مانند لیست ها و دیکشنری ها داشته باشند. برای مثال، لیست tricks در کد زیر نباید به عنوان متغیر کلاس استفاده شود زیرا تنها یک لیست توسط همه نمونه های Dog به اشتراک گذاشته می شود.
طراحی صحیح کلاس، باید به جای آن از یک متغیر نمونه استفاده کند.
class Dog:
def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog
def add_trick(self, trick):
self.tricks.append(trick)
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
اگر یک نام ویژگی(attribute name) مشابه هم در یک نمونه و هم در یک کلاس رخ دهد، سپس جستجوی ویژگی، نمونه را اولویت بندی می کند.
>>> class Warehouse:
purpose = 'storage'
region = 'west'
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east
ویژگی های داده همانطور که توسط کاربران معمولی (مشتری ها) یک شی ارجاع داده می شوند، ممکن است توسط متدها نیز ارجاع یابند. به بیان دیگر، کلاس ها برای پیاده سازی انواع داده انتزاعی خالص قابل استفاده نیستند. در حقیقت، هیچ چیزی در پایتون، اجبار به پنهان سازی داده را ممکن نمی کند—همه چیز بستگی به قرارداد دارد. ( از طرف دیگر، پیاده سازی پایتون، نوشته شده در C، می تواند به طور کامل جزییات پیاده سازی را پنهان کند و دسترسی به یک شی را در صورت نیاز کنترل کند. توسط ضمیمه های پایتون که به C نوشته شده است، می توان از این استفاده کرد. )
مشتری ها (Clients) باید با احتیاط از ویژگی های داده استفاده کنند—مشتری ها ممکن است ثابت های(invariants) نگهداری شده توسط متد ها را با مهر زدن روی ویژگی های داده آنها اشتباه بگیرند. توجه داشته باشید که ممکن است مشتری ها ویژگی های داده خود را بدون تاثیر گذاری روی اعتبار متدها، به یک شی نمونه اضافه کنند، البته تا زمانی که از ناسازگاری بین نام ها پرهیز شود—مجددا می گویم، قرارداد های نام گذاری می تواند مانع بسیاری از دردسرها در اینجا شود.
هیچ گونه اختصار نویسی برای ارجاع ویژگی های داده(یا سایر متدها) از درون متدها وجود ندارد. در واقع به نظر من این خوانایی متدها را افزایش می دهد: در نگاه کلی به یک متد، امکان اشتباه و سر در گمی بین متغیرهای محلی و متغیرهای نمونه به هیچ وجه وجود ندارد.
اغلب اولین آرگومان یک متد self نام دارد. این فقط یک قرارداد است. نام self مطلقا هیچ معنی خاصی برای پایتون ندارد. هر چند توجه داشته باشد که با رعایت نکردن قراردادها، ممکن است کد شما برای سایر برنامه نویسان پایتون از خوانایی کمتری برخوردار باشد. همچنین ممکن است یک برنامه مرورگر کلاس با تکیه بر چنین قراردادهای نوشته شود.
هر شی تابعی که یک ویژگی کلاس باشد، یک متدی را برای نمونه های آن کلاس تعریف می کند. ضروری نیست که تعریف تابع به صورت نوشتاری درون تعریف کلاس قرار گیرد (محصور باشد). تخصیص یک شی تابع به یک متغیر محلی درون کلاس نیز کفایت می کند. برای مثال:
# Function defined outside the class
def f1(self, x, y):
return min(x, x+y)
class C:
f = f1
def g(self):
return 'hello world'
h = g
حال f,g و h همه ویژگی های کلاس c هستند که به اشیای تابع رجوع می کنند، و متعاقبا همه آنها متدهای نمونه های c هستند – h دقیقا برابر است با g. توجه داشته باشید که معمولا این عمل تنها برای سر در گم کردن خواننده برنامه انجام می شود.
ممکن است متدها با استفاده از ویژگی های متد آرگومان self، سایر متدها را فراخوانی کنند.
class Bag:
def __init__(self):
self.data = []
def add(self, x):
self.data.append(x)
def addtwice(self, x):
self.add(x)
self.add(x)
متدها ممکن است به همان شیوه مشابه ارجاع توابع معمولی، نام های سراسری را ارجاع دهند. حوزه سراسری مربوط به یک متد، ماژولی است که در بردارنده تعریف آن است. (یک کلاس هرگز به عنوان یک حوزه سراسری استفاده نمی شود.) درحالی که به ندرت به دلیل خوبی برای استفاده از داده سراسری در یک متد بر میخوریم، کاربرد های درست و قانونی بسیاری از حوزه سراسری وجود دارد: یک مورد این است که، توابع و ماژول های وارد شده به حوزه سراسری می توانند مانند توابع و کلاس های تعریف شده در آن، توسط متدها استفاده شوند. معمولا کلاسی که در بردارنده متد است، خود نیز درون حوزه سراسری تعریف شده است. در بخش بعد دلایل خوبی برای اینکه چرا یک متد ممکن است بخواهد به کلاس خود ارجاع دهد، را بیان می کنیم.
هر مقدار، یک شی است و بنابراین یک کلاس دارد(نوع آن نیز نامیده می شود) و به عنوان object.__class__ ذخیره می شود.
آموزش پایتون ادامه دارد