یه چالش باحال پایتون

سلام؛ چند وقت پیش با یه چالش در وب سایت codewars در زمینه برنامه نویسی پایتون مواجه شدم که نظرم رو جلب کرد و تصمیم گرفتم توی ویرگول با شما هم درمیون بزارم.

شرح چالش

تابعی بنویسید که اینطور عمل کنه

add(1)(2)  #result: 3
add(4)(4) #result: 8
add(5)(5)(5) #result: 15

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

خوب بزارید جمع بندی کنیم:

  • مقدار برگشتی تابع باید یک تابع دیگه باشه
  • مقدار برگشتی تابع باید یک عدد باشه

خوب، این بزرگ ترین چالشی هستش که ما در این مسئله داریم. چطور ممکنه یه تابع، هم یک عدد رو به عنوان مقدار برگشتی برگردونه و هم یک تابع دیگه رو؟

نکاتی که قبل از حل چالش باید بدونید

در پایتون همه انواع داده، خودشون از نوع object هستند. یعنی اینکه شما می تونید از انواع داده در پایتون مثل str و int و dict و ... ارث بری کنید. اگه این قابلیت نبود خیلی این چالش سخت میشد.

آشنایی با متد __call__

در پایتون توابع مخصوصی وجود دارند که برای کار های خاصی در نظر گرفته شده اند. اگه با شیء گرایی در پایتون آشنا باشید؛ باید با برخی از این توابع رو بشناسید. مثل __init__ که برای سربار گذاری اولیه (initialization) استفاده میشه (اولین متد مخصوصی که باهاش آشنا میشید معمولا همینه).

از متد __call__ برای این استفاده میشه که instance های یک class قابل فراخوانی (callable) بشوند. برای اینکه مطلب جا بیفته به کد زیر توجه کنید.

class A:
     def __init__(self): print (&quotinitialized&quot)
     def __call__(self): print (&quotthe instance is invoked&quot)

instance = A() # initializing the class
instance() # calling the instance of type `A`

#result:
# initialized 
# the instance is invoked

خوب همونطور که میبینید، در کد بالا instance رو دقیقا مثل یک تابع صدا زدیم. این به لطف متد __call__ امکان پذیره. اگر یک object، متد __call__ رو داشته باشه، می تونیم فراخوانیش کنیم اما اگر نداشته باشه، موقع فراخوانی اروری مبنی بر اینکه این شیء قابل فراخوانی نیست خواهیم گرفت.

وراثت در پایتون

ما در پایتون می تونیم ویژگی هایی که کلاس های دیگه دارند رو به ارث ببریم.اگر کلاسی از کلاس دیگر ارث ببره، تمام متد ها و property های اون کلاس رو هم به ارث میبره .در پایین یه مثال ساده رو برای نشون دادن این مطلب آورده ام

class A:
     def hello(self): return &quotHello &quot

class B(A): # B inherits from A, so it has a method called hello as well
     def greet(self, name):
          return self.hello() + name

obj = B()
obj.greet(&quotAshkan&quot) # result: 'Hello Ashkan'

ما در این کد، دو کلاس به نام A و B داریم، که B از A ارث بری کرده. پس تمام توابع کلاس A در کلاس B هم وجود دارند. اگر به کد های نوشته شده در تابع greet نگاهی بندازید، می فهمید که در اون از تابع hello استفاده شده. تابع hello از کلاس A به B به ارث رسیده.

حل چالش

خوب تا به اینجا تموم چیز های مورد نیاز رو یاد گرفتید. کاری که قراره انجام بدیم اینه:

  • از روی نوع داده int ارث بری می کنیم و نوع داده جدیدی به نام CustomNumber می سازیم. با این ارث بری که انجام دادیم، دیگه لازم نیست اصلا بدونیم که int چطور کار میکنه. ما تغییرات خودمون رو طوری اعمال می کنیم که رفتار فعلی int رو تغییر نده و در عین حال ویژگی هایی که ما می خواهیم رو اضافه کنه.
  • یک متد __call__ براش تعریف میکنیم که آرگومانی به نام number_to_add داره. number_to_add در اصل همون عددی هستش که قراره با عدد قبلیش جمع بشه. عدد قبلیش چیه؟ خوب همون self میشه.
  • همین دیگه. توضیحات بسه بریم سراغ کد ...
class CustomNumber(int): #inherits from int data type
     def __call__(self, number_to_add):
          return CustomNumber(self + number_to_add) 

فقط اینکه یه نکته ای در این کد وجود داره. مقدار برگشتی متد __call__، یک instance از نوع CustomNumber است. دلیل این کارم اینه که self (برخلاف چیزی که انتظار میره) از نوع int هستش. برای همین هربار یک instance جدید از روی CustomNumber میسازیم و به عنوان مقدار برگشتی، برمیگردونیم.

البته؛ خوب واقعا دلیلش رو نمیدونم که چرا تو این مورد self از نوع int هستش. اگه شما می دونید؛ خوشحال میشم که در کامنت هاتون بهم بگید.

نحوه استفاده

add = CustomNumber(10) # initialize `self` with the value of 10
value = add(20)(50)(30)
print (value) # 110