Python orientato agli oggetti - Funzionalità avanzate

In questo esamineremo alcune delle funzionalità avanzate fornite da Python

Sintassi principale nel design della nostra classe

In questo vedremo come Python ci permette di trarre vantaggio dagli operatori nelle nostre classi. Python è in gran parte che oggetti e metodi chiamano oggetti e questo continua anche quando è nascosto da una sintassi conveniente.

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

Quindi, se dobbiamo aggiungere il metodo magico __add__ alle nostre classi, potremmo farlo anche noi. Proviamo a farlo.

Abbiamo una classe chiamata Sumlist che ha un controllore __init__ che accetta list come argomento chiamato 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])

Produzione

[103, 206, 309, 412, 515]

Ma ci sono molti metodi che sono gestiti internamente da altri metodi magici. Di seguito sono riportati alcuni di loro,

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

Ereditare da tipi predefiniti

Le classi possono anche ereditare dai tipi incorporati, ciò significa che eredita da qualsiasi built-in e sfruttare tutte le funzionalità che si trovano lì.

Nell'esempio sotto stiamo ereditando dal dizionario ma poi stiamo implementando uno dei suoi metodi __setitem__. Questo (setitem) viene invocato quando impostiamo chiave e valore nel dizionario. Poiché si tratta di un metodo magico, verrà chiamato implicitamente.

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

Produzione

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

Estendiamo il nostro esempio precedente, di seguito abbiamo chiamato due metodi magici chiamati __getitem__ e __setitem__ meglio invocati quando abbiamo a che fare con list index.

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

Produzione

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

Nell'esempio precedente, impostiamo un elenco di tre elementi in Mylist e implicitamente viene chiamato il metodo __init__ e quando stampiamo l'elemento x, otteniamo l'elenco dei tre elementi (['a', 'b', 'c']). Quindi aggiungiamo un altro elemento a questo elenco. Successivamente chiediamo l'indice 1 e l'indice 4. Ma se vedi l'output, stiamo ottenendo l'elemento da (indice-1) ciò che abbiamo richiesto. Come sappiamo, l'indicizzazione della lista parte da 0 ma qui l'indicizzazione inizia da 1 (ecco perché stiamo ottenendo il primo elemento della lista).

Convenzioni di denominazione

In questo esamineremo i nomi che useremo per le variabili, in particolare le variabili private e le convenzioni usate dai programmatori Python in tutto il mondo. Sebbene le variabili siano designate come private, ma non c'è privacy in Python e questo è previsto. Come ogni altro linguaggio ben documentato, Python ha convenzioni di denominazione e stile che promuove sebbene non le imponga. C'è una guida di stile scritta da "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 sta per proposta di miglioramento di Python ed è una serie di documentazione che viene distribuita alla comunità Python per discutere le modifiche proposte. Ad esempio è consigliato a tutti,

  • Nomi dei moduli: all_lower_case
  • Nomi di classi e nomi di eccezioni - CamelCase
  • Nomi globali e locali: all_lower_case
  • Funzioni e nomi dei metodi: all_lower_case
  • Costanti - ALL_UPPER_CASE

Queste sono solo le raccomandazioni, puoi variare se vuoi. Ma poiché la maggior parte degli sviluppatori segue queste raccomandazioni, potrei forse che il tuo codice sia meno leggibile.

Perché conformarsi alla convenzione?

Possiamo seguire la raccomandazione PEP che ci permette di ottenere,

  • Più familiare alla stragrande maggioranza degli sviluppatori
  • Più chiaro per la maggior parte dei lettori del tuo codice.
  • Corrisponderà allo stile di altri contributori che lavorano sulla stessa base di codice.
  • Marchio di uno sviluppatore di software professionale
  • Tutti ti accetteranno.

Denominazione variabile: "Pubblico" e "Privato"

In Python, quando abbiamo a che fare con moduli e classi, designiamo alcune variabili o attributi come privati. In Python, non esiste alcuna variabile di istanza "Private" a cui non è possibile accedere se non all'interno di un oggetto. Privato significa semplicemente che semplicemente non sono destinati ad essere utilizzati dagli utenti del codice, invece sono destinati ad essere utilizzati internamente. In generale, una convenzione viene seguita dalla maggior parte degli sviluppatori Python, ad esempio un nome preceduto da un carattere di sottolineatura. _attrval (esempio sotto) dovrebbe essere trattato come una parte non pubblica dell'API o di qualsiasi codice Python, sia che si tratti di una funzione, un metodo o un membro dati. Di seguito è riportata la convenzione di denominazione che seguiamo,

  • Attributi o variabili pubblici (destinati ad essere utilizzati dall'importatore di questo modulo o dall'utente di questa classe) -regular_lower_case

  • Attributi o variabili privati ​​(uso interno da parte del modulo o della classe) -_single_leading_underscore

  • Attributi privati ​​che non dovrebbero essere sottoclassi -__double_leading_underscore

  • Attributi magici -__double_underscores__(usali, non crearli)

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)

Produzione

setting the "var" attribute
10
no privacy!