Объектно-ориентированный 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 имеет соглашения об именах и стилях, которые он продвигает, хотя и не обеспечивает их соблюдение. Есть руководство по стилю, написанное "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 не существует «частной» переменной экземпляра, к которой нельзя получить доступ, кроме как внутри объекта. Private просто означает, что они просто не предназначены для использования пользователями кода, а предназначены для внутреннего использования. В общем, большинство разработчиков 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!