Python zorientowany obiektowo - zaawansowane funkcje

W tym artykule przyjrzymy się niektórym zaawansowanym funkcjom, które zapewnia Python

Podstawowa składnia w naszym projekcie klasy

W tym artykule przyjrzymy się, jak Python pozwala nam wykorzystać operatory w naszych klasach. Python to w dużej mierze obiekty i metody wywołujące obiekty, a dzieje się to nawet wtedy, gdy jest ukryte przez jakąś wygodną składnię.

>>> 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']

Więc jeśli musimy dodać magiczną metodę __add__ do naszych własnych klas, czy możemy to zrobić. Spróbujmy to zrobić.

Mamy klasę o nazwie Sumlist, która ma konstruktora __init__, który przyjmuje list jako argument o nazwie 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])

Wynik

[103, 206, 309, 412, 515]

Ale jest wiele metod, którymi wewnętrznie zarządzają inne magiczne metody. Poniżej znajduje się kilka z nich,

'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__()

Dziedziczenie z typów wbudowanych

Klasy mogą również dziedziczyć po typach wbudowanych, co oznacza, że ​​dziedziczy po każdym wbudowanym i korzysta ze wszystkich dostępnych tam funkcji.

W poniższym przykładzie dziedziczymy ze słownika, ale potem implementujemy jedną z jego metod __setitem__. Ten (setitem) jest wywoływany, gdy ustawiamy klucz i wartość w słowniku. Ponieważ jest to metoda magiczna, zostanie ona wywołana niejawnie.

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]))

Wynik

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

Rozszerzmy nasz poprzedni przykład, poniżej wywołaliśmy dwie magiczne metody o nazwach __getitem__ i __setitem__, które są lepiej wywoływane, gdy mamy do czynienia z indeksem listy.

# 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!

Wynik

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

W powyższym przykładzie ustawiamy listę trzech pozycji w Mylist i niejawnie wywoływana jest metoda __init__, a kiedy drukujemy element x, otrzymujemy listę trzech pozycji (['a', 'b', 'c']). Następnie dodajemy kolejny element do tej listy. Później prosimy o indeks 1 i indeks 4. Ale jeśli zobaczysz wynik, otrzymujemy element z (indeks-1), o który prosiliśmy. Jak wiemy, indeksowanie listy zaczyna się od 0, ale tutaj indeksowanie zaczyna się od 1 (dlatego otrzymujemy pierwszą pozycję z listy).

Konwencje nazewnictwa

W tym artykule przyjrzymy się nazwom, których będziemy używać dla zmiennych, zwłaszcza zmiennych prywatnych i konwencji używanych przez programistów Pythona na całym świecie. Chociaż zmienne są oznaczone jako prywatne, ale w Pythonie nie ma prywatności i jest to zgodne z projektem. Jak każdy inny dobrze udokumentowany język, Python ma konwencje nazewnictwa i stylów, które promuje, chociaż ich nie wymusza. Istnieje przewodnik stylistyczny napisany przez „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 oznacza propozycję ulepszenia języka Python i jest serią dokumentacji rozpowszechnianej wśród społeczności Pythona w celu omówienia proponowanych zmian. Na przykład zaleca się wszystkim,

  • Nazwy modułów - all_lower_case
  • Nazwy klas i nazwy wyjątków - CamelCase
  • Nazwy globalne i lokalne - all_lower_case
  • Funkcje i nazwy metod - all_lower_case
  • Stałe - ALL_UPPER_CASE

To tylko zalecenia, możesz je zmieniać, jeśli chcesz. Ale ponieważ większość programistów postępuje zgodnie z tymi zaleceniami, może mi się zdaje, że Twój kod jest mniej czytelny.

Po co dostosowywać się do konwencji?

Możemy postępować zgodnie z zaleceniami PEP, które nam pozwala,

  • Bardziej znany większości programistów
  • Jaśniejsze dla większości czytelników twojego kodu.
  • Będzie pasował do stylu innych współpracowników, którzy pracują na tej samej bazie kodu.
  • Znak profesjonalnego programisty
  • Wszyscy cię zaakceptują.

Nazewnictwo zmiennych - „Publiczne” i „Prywatne”

W Pythonie, gdy mamy do czynienia z modułami i klasami, określamy niektóre zmienne lub atrybut jako prywatne. W Pythonie nie istnieje zmienna instancji „prywatna”, do której nie można uzyskać dostępu poza obiektem. Prywatne oznacza po prostu, że po prostu nie są przeznaczone do użytku przez użytkowników kodu, a zamiast tego mają być używane wewnętrznie. Ogólnie rzecz biorąc, większość programistów Pythona przestrzega pewnej konwencji, na przykład nazwy poprzedzonej podkreśleniem. _attrval (przykład poniżej) należy traktować jako niepubliczną część interfejsu API lub dowolny kod Pythona, niezależnie od tego, czy jest to funkcja, metoda czy element członkowski danych. Poniżej znajduje się konwencja nazewnictwa, którą przestrzegamy,

  • Atrybuty lub zmienne publiczne (przeznaczone do wykorzystania przez importera tego modułu lub użytkownika tej klasy) -regular_lower_case

  • Prywatne atrybuty lub zmienne (wewnętrzne użycie przez moduł lub klasę) -_single_leading_underscore

  • Prywatne atrybuty, które nie powinny być podklasą -__double_leading_underscore

  • Magiczne atrybuty -__double_underscores__(używaj ich, nie twórz ich)

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)

Wynik

setting the "var" attribute
10
no privacy!