Object Oriented Python - คุณสมบัติขั้นสูง

ในสิ่งนี้เราจะดูคุณสมบัติขั้นสูงบางอย่างที่ Python มีให้

Core Syntax ในการออกแบบคลาสของเรา

เราจะมาดูกันว่า Python ช่วยให้เราใช้ประโยชน์จากตัวดำเนินการในคลาสของเราได้อย่างไร Python ส่วนใหญ่เป็นอ็อบเจ็กต์และวิธีการเรียกใช้อ็อบเจ็กต์และสิ่งนี้ยังคงดำเนินต่อไปแม้ว่ามันจะถูกซ่อนไว้ด้วยไวยากรณ์ที่สะดวกก็ตาม

>>> var1 = 'Hello'
>>> var2 = ' World!'
>>> var1 + var2
'Hello World!'
>>>
>>> var1.__add__(var2)
'Hello World!'
>>> num1 = 45
>>> num2 = 60
>>> num1.__add__(num2)
105
>>> var3 = ['a', 'b']
>>> var4 = ['hello', ' John']
>>> var3.__add__(var4)
['a', 'b', 'hello', ' John']

ดังนั้นถ้าเราต้องเพิ่ม magic method __add__ ให้กับคลาสของเราเองเราก็ทำได้เช่นกัน มาลองทำกันดู

เรามีคลาสที่เรียกว่า Sumlist ซึ่งมี contructor __init__ ซึ่งรับรายการเป็นอาร์กิวเมนต์ที่เรียกว่า my_list

class SumList(object):
   def __init__(self, my_list):
      self.mylist = my_list
   def __add__(self, other):
     new_list = [ x + y for x, y in zip(self.mylist, other.mylist)]

     return SumList(new_list)
   
   def __repr__(self):
      return str(self.mylist)

aa = SumList([3,6, 9, 12, 15])

bb = SumList([100, 200, 300, 400, 500])
cc = aa + bb # aa.__add__(bb)
print(cc) # should gives us a list ([103, 206, 309, 412, 515])

เอาต์พุต

[103, 206, 309, 412, 515]

แต่มีหลายวิธีที่จัดการภายในโดยวิธีมายากลอื่น ๆ ด้านล่างนี้คือบางส่วนของพวกเขา

'abc' in var # var.__contains__('abc')
var == 'abc' # var.__eq__('abc')
var[1] # var.__getitem__(1)
var[1:3] # var.__getslice__(1, 3)
len(var) # var.__len__()
print(var) # var.__repr__()

การรับช่วงจากชนิดในตัว

คลาสยังสามารถสืบทอดจากประเภทในตัวซึ่งหมายถึงการสืบทอดจากบิวท์อินและใช้ประโยชน์จากฟังก์ชันทั้งหมดที่พบ

ในตัวอย่างด้านล่างนี้เรากำลังสืบทอดมาจากพจนานุกรม แต่จากนั้นเรากำลังใช้หนึ่งในวิธีการ __setitem__ (setitem) นี้ถูกเรียกใช้เมื่อเราตั้งค่าคีย์และค่าในพจนานุกรม เนื่องจากเป็นวิธีมายากลจึงเรียกโดยปริยาย

class MyDict(dict):

   def __setitem__(self, key, val):
      print('setting a key and value!')
      dict.__setitem__(self, key, val)

dd = MyDict()
dd['a'] = 10
dd['b'] = 20

for key in dd.keys():
   print('{0} = {1}'.format(key, dd[key]))

เอาต์พุต

setting a key and value!
setting a key and value!
a = 10
b = 20

ลองขยายตัวอย่างก่อนหน้าของเราด้านล่างเราได้เรียกวิธีมายากลสองวิธีที่เรียกว่า __getitem__ และ __setitem__ ดีกว่าเมื่อเราจัดการกับดัชนีรายการ

# Mylist inherits from 'list' object but indexes from 1 instead for 0!
class Mylist(list): # inherits from list
   def __getitem__(self, index):
      if index == 0:
         raise IndexError
      if index > 0:
         index = index - 1
         return list.__getitem__(self, index) # this method is called when

# we access a value with subscript like x[1]
   def __setitem__(self, index, value):
      if index == 0:
         raise IndexError
      if index > 0:
      index = index - 1
      list.__setitem__(self, index, value)

x = Mylist(['a', 'b', 'c']) # __init__() inherited from builtin list

print(x) # __repr__() inherited from builtin list

x.append('HELLO'); # append() inherited from builtin list

print(x[1]) # 'a' (Mylist.__getitem__ cutomizes list superclass
               # method. index is 1, but reflects 0!

print (x[4]) # 'HELLO' (index is 4 but reflects 3!

เอาต์พุต

['a', 'b', 'c']
a
HELLO

ในตัวอย่างข้างต้นเราตั้งค่ารายการไอเท็มสามรายการใน Mylist และโดยปริยายเรียกเมธอด __init__ และเมื่อเราพิมพ์องค์ประกอบ x เราจะได้รับรายการไอเท็มสามรายการ (['a', 'b', 'c']) จากนั้นต่อท้ายองค์ประกอบอื่นในรายการนี้ ต่อมาเราขอดัชนี 1 และดัชนี 4 แต่ถ้าคุณเห็นผลลัพธ์เราจะได้รับองค์ประกอบจาก (ดัชนี -1) สิ่งที่เราขอ อย่างที่เราทราบกันดีว่าการจัดทำดัชนีรายการเริ่มต้นจาก 0 แต่ที่นี่การจัดทำดัชนีเริ่มต้นจาก 1 (นั่นคือเหตุผลที่เราได้รับรายการแรกของรายการ)

หลักการตั้งชื่อ

ในนี้เราจะพิจารณาชื่อที่เราจะใช้สำหรับตัวแปรโดยเฉพาะตัวแปรส่วนตัวและอนุสัญญาที่ใช้โดยโปรแกรมเมอร์ Python ทั่วโลก แม้ว่าตัวแปรจะถูกกำหนดให้เป็นส่วนตัว แต่ไม่มีความเป็นส่วนตัวใน Python และสิ่งนี้เกิดจากการออกแบบ เช่นเดียวกับภาษาเอกสารอื่น ๆ Python มีรูปแบบการตั้งชื่อและรูปแบบที่ส่งเสริมแม้ว่าจะไม่ได้บังคับใช้ก็ตาม มีคู่มือสไตล์ที่เขียนโดย“Guido van Rossum” the originator of Python, that describe the best practices and use of name and is called PEP8. Here is the link for this, https://www.python.org/dev/peps/pep-0008/

PEP ย่อมาจากข้อเสนอการปรับปรุง Python และเป็นชุดของเอกสารที่แจกจ่ายในชุมชน Python เพื่อหารือเกี่ยวกับการเปลี่ยนแปลงที่เสนอ ตัวอย่างเช่นแนะนำทั้งหมด

  • ชื่อโมดูล - all_lower_case
  • ชื่อคลาสและชื่อข้อยกเว้น - CamelCase
  • ชื่อสากลและชื่อท้องถิ่น - all_lower_case
  • ชื่อฟังก์ชันและเมธอด - all_lower_case
  • ค่าคงที่ - ALL_UPPER_CASE

นี่เป็นเพียงคำแนะนำคุณสามารถเปลี่ยนแปลงได้หากต้องการ แต่เนื่องจากนักพัฒนาส่วนใหญ่ปฏิบัติตามคำแนะนำเหล่านี้ดังนั้นรหัสของคุณอาจอ่านได้น้อยลง

ทำไมต้องเป็นไปตามแบบแผน?

เราสามารถทำตามคำแนะนำของ PEP ที่เราช่วยให้เราได้รับ

  • นักพัฒนาซอฟต์แวร์ส่วนใหญ่คุ้นเคยมากขึ้น
  • ชัดเจนยิ่งขึ้นสำหรับผู้อ่านโค้ดส่วนใหญ่ของคุณ
  • จะตรงกับสไตล์ของผู้ร่วมให้ข้อมูลคนอื่น ๆ ที่ทำงานบนฐานรหัสเดียวกัน
  • เครื่องหมายของนักพัฒนาซอฟต์แวร์มืออาชีพ
  • ทุกคนจะยอมรับคุณ

การตั้งชื่อตัวแปร - 'สาธารณะ' และ 'ส่วนตัว'

ใน Python เมื่อเราจัดการกับโมดูลและคลาสเรากำหนดตัวแปรหรือแอตทริบิวต์บางอย่างเป็นส่วนตัว ใน Python ไม่มีตัวแปรอินสแตนซ์ "ส่วนตัว" ที่ไม่สามารถเข้าถึงได้ยกเว้นภายในออบเจ็กต์ ส่วนตัวหมายความว่าพวกเขาไม่ได้ตั้งใจให้ผู้ใช้โค้ดใช้แทนพวกเขามีจุดประสงค์เพื่อใช้ภายใน โดยทั่วไปแล้วนักพัฒนา Python ส่วนใหญ่จะปฏิบัติตามแบบแผนเช่นชื่อที่นำหน้าด้วยเครื่องหมายขีดล่างเป็นต้น _attrval (ตัวอย่างด้านล่าง) ควรถือเป็นส่วนที่ไม่ใช่สาธารณะของ API หรือโค้ด Python ไม่ว่าจะเป็นฟังก์ชันวิธีการหรือสมาชิกข้อมูล ด้านล่างนี้คือหลักการตั้งชื่อที่เราปฏิบัติตาม

  • แอตทริบิวต์หรือตัวแปรสาธารณะ (มีวัตถุประสงค์เพื่อใช้โดยผู้นำเข้าของโมดูลนี้หรือผู้ใช้คลาสนี้) -regular_lower_case

  • แอตทริบิวต์หรือตัวแปรส่วนตัว (ใช้ภายในโดยโมดูลหรือคลาส) -_single_leading_underscore

  • แอตทริบิวต์ส่วนตัวที่ไม่ควรเป็นคลาสย่อย -__double_leading_underscore

  • คุณสมบัติเวทมนตร์ -__double_underscores__(ใช้มันอย่าสร้าง)

class GetSet(object):

   instance_count = 0 # public
   
   __mangled_name = 'no privacy!' # special variable

   def __init__(self, value):
      self._attrval = value # _attrval is for internal use only
      GetSet.instance_count += 1

   @property
   def var(self):
      print('Getting the "var" attribute')
      return self._attrval

   @var.setter
   def var(self, value):
      print('setting the "var" attribute')
      self._attrval = value

   @var.deleter
   def var(self):
      print('deleting the "var" attribute')
      self._attrval = None

cc = GetSet(5)
cc.var = 10 # public name
print(cc._attrval)
print(cc._GetSet__mangled_name)

เอาต์พุต

setting the "var" attribute
10
no privacy!