面向物件Python - 高階特性



在本節中,我們將深入瞭解Python提供的一些高階特性。

我們類設計中的核心語法

在本節中,我們將瞭解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']

所以,如果我們必須將魔法方法__add__新增到我們自己的類中,我們也可以這樣做嗎?讓我們嘗試一下。

我們有一個名為Sumlist的類,它有一個建構函式__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。但是,如果您檢視輸出,我們會從我們請求的(index-1)獲取元素。正如我們所知,列表索引從0開始,但此處索引從1開始(這就是為什麼我們得到列表的第一個元素的原因)。

命名約定

在本節中,我們將瞭解用於變數(尤其是私有變數)的名稱以及全球Python程式設計師使用的約定。儘管變數被指定為私有,但Python中不存在隱私,這是設計使然。與任何其他有良好文件記錄的語言一樣,Python具有它所提倡的命名和樣式約定,儘管它不強制執行這些約定。Python的創始人“Guido van Rossum”編寫了一份樣式指南,描述了最佳實踐和名稱的使用,稱為PEP8。以下是此指南的連結:https://python.club.tw/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!
廣告