ШОРТЫ НА ПИТОНЕ

Объяснение объектно-ориентированного программирования для специалистов по данным

Простое руководство по началу использования классов в Python 3

Объектно-ориентированное программирование или ООП может быть сложной для понимания концепцией для новичков. И это главным образом потому, что во многих местах это не совсем правильно объясняется. Обычно многие книги начинают с объяснения ООП с трех основных терминов - инкапсуляция, наследование и полиморфизм. Но когда книга сможет объяснить эти темы, любой, кто только начинает, уже почувствует себя потерянным.

Итак, я подумал о том, чтобы сделать эту концепцию немного проще для коллег-программистов, специалистов по данным и питонистов. Я собираюсь сделать это, удалив весь жаргон и рассмотрев несколько примеров. Я бы начал с объяснения классов и объектов. Затем я бы объяснил, почему классы важны в различных ситуациях и как они решают некоторые фундаментальные проблемы. Таким образом, читатель также сможет понять три больших термина к концу сообщения.

В этой серии публикаций под названием Python Shorts я объясню некоторые простые, но очень полезные конструкции, предоставляемые Python, некоторые важные советы и некоторые варианты использования, которые я регулярно придумываю в своей работе с Data Science.

Этот пост посвящен простому объяснению ООП.

Что такое объекты и классы?

Проще говоря, все в Python - это объект, а классы - это план объектов. Итак, когда мы пишем:

a = 2
b = "Hello!"

Мы создаем объект a класса int, содержащий значение 2 And, и объект b класса str, содержащий значение «Hello!». В некотором смысле эти два конкретных класса предоставляются нам по умолчанию, когда мы используем числа или строки.

Помимо этого, многие из нас в конечном итоге работают с классами и объектами, даже не осознавая этого. Например, вы фактически используете класс, когда используете любую scikit Learn Model.

clf = RandomForestClassifier()
clf.fit(X,y)

Здесь ваш классификатор clf - это объект, а fit - это метод, определенный в классе RandomForestClassifier.

Но почему классы?

Итак, мы часто используем их, когда работаем с Python. Но почему на самом деле. Что с классами? Могу я сделать то же самое с функциями?

Да, ты можешь. Но классы действительно предоставляют вам гораздо больше возможностей по сравнению с функциями. Чтобы процитировать пример, класс str имеет множество функций, определенных для объекта, к которым мы можем получить доступ, просто нажав вкладку. Можно также написать все эти функции, но в этом случае их нельзя будет использовать простым нажатием кнопки табуляции.

Это свойство классов называется инкапсуляцией. Из Википедии - инкапсуляция означает объединение данных с методами, которые работают с этими данными, или ограничение прямого доступа к некоторым компонентам объекта .

Итак, здесь класс str связывает данные («Привет!») Со всеми методами, которые будут работать с нашими данными. Я объясню вторую часть этого утверждения в конце поста. Таким же образом классRandomForestClassifier объединяет все методы классификатора (fit, predict и т. Д.)

Помимо этого, использование класса также может помочь нам сделать код более модульным и простым в обслуживании. Скажем, мы должны были создать такую ​​библиотеку, как Scikit-Learn. Нам нужно создать много моделей, и каждая модель будет иметь метод подгонки и прогнозирования. Если мы не будем использовать классы, у нас будет множество функций для каждой из наших моделей, например:

RFCFit
RFCPredict
SVCFit
SVCPredict
LRFit
LRPredict 
and so on.

Такая структура кода - просто кошмар для работы, и, следовательно, Scikit-Learn определяет каждую из моделей как класс, имеющий методы fit и predict.

Создание класса

Итак, теперь мы понимаем, зачем использовать классы и почему они так важны, как мы на самом деле их использовать? Итак, создать класс довольно просто. Ниже приведен шаблонный код для любого класса, который вы в конечном итоге напишите:

class myClass:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    def somefunc(self, arg1, arg2):
        #SOME CODE HERE

Здесь мы видим много новых ключевых слов. Основными являются _14 _, _ 15_ и self. Так что это? Опять же, это легко объяснить на каком-то примере.

Предположим, вы работаете в банке, у которого много счетов. Мы можем создать класс с именем account, который будет использоваться для работы с любой учетной записью. Например, ниже я создаю элементарный класс игрушек Account, который хранит данные для пользователя, а именно account_name и balance. Он также предоставляет нам два метода _20 _ / _ 21_ денег на / с банковского счета. Прочтите это. Он имеет ту же структуру, что и код выше.

class Account:
    def __init__(self, account_name, balance=0):
        self.account_name = account_name
        self.balance = balance
    
    def deposit(self, amount):
        self.balance += amount
    
    def withdraw(self,amount):
        if amount <= self.balance:
            self.balance -= amount
        else:
            print("Cannot Withdraw amounts as no funds!!!")

Мы можем создать учетную запись с именем Rahul и суммой 100, используя:

myAccount = Account("Rahul",100)

Мы можем получить доступ к данным для этой учетной записи, используя:

Но как эти атрибуты balance и account_name уже установлены на 100 и «Рахул» соответственно? Мы никогда не вызывали метод __init__, так почему же объект получил эти атрибуты? Ответ здесь заключается в том, что __init__ - это магический метод (есть много других магических методов, которые я бы расширил в своем следующем посте о магических методах), который запускается всякий раз, когда мы создаем объект. Поэтому, когда мы создаем myAccount, автоматически запускается функция __init__.

Итак, теперь мы понимаем __init__, давайте попробуем внести немного денег на наш счет. Мы можем сделать это:

И наш баланс вырос до 200. Но вы заметили, что нашей функции deposit нужны два аргумента, а именно self и amount,, но мы предоставили только один, и все же она работает.

Итак, что это за self? Мне нравится объяснять себя, вызывая одну и ту же функцию, хотя и по-другому. Ниже я вызываю ту же функцию deposit, принадлежащую классу account, и передаю ей объект myAccount и amount. И теперь функция принимает два аргумента, как и должна.

И наш баланс myAccount увеличивается на 100, как и ожидалось. Итак, это та же функция, которую мы вызвали. Это могло произойти, только если self и myAccount - это один и тот же объект. Когда я вызываю myAccount.deposit(100), Python предоставляет для вызова функции тот же объект myAccount, что и аргумент self. Вот почему self.balance в определении функции действительно относится к myAccount.balance.

Но, тем не менее, некоторые проблемы остаются

Мы знаем, как создавать классы, но все же есть еще одна важная проблема, которую я еще не затронул.

Итак, предположим, что вы работаете с Apple iPhone Division и вам нужно создать отдельный класс для каждой модели iPhone. В этом простом примере предположим, что первая версия нашего iPhone в настоящее время выполняет только одну функцию - выполняет вызов и имеет некоторую память. Мы можем записать класс как:

class iPhone:
    def __init__(self, memory, user_id):
         self.memory = memory
         self.mobile_id = user_id
    def call(self, contactNum):
         # Some Implementation Here

Теперь Apple планирует выпустить iPhone1, и эта iPhone Модель представляет новую функциональность - возможность делать снимки. Один из способов сделать это - скопировать и вставить приведенный выше код и создать новый класс iPhone1, например:

class iPhone1:
    def __init__(self, memory, user_id):
         self.memory = memory
         self.mobile_id = user_id
         self.pics = []
    def call(self, contactNum):
         # Some Implementation Here
    def click_pic(self):
         # Some Implementation Here
         pic_taken = ...
         self.pics.append(pic_taken)

Но, как вы можете видеть, это много ненужного дублирования кода (выделено жирным шрифтом выше), и у Python есть решение для удаления этого дублирования кода. Один хороший способ написать наш класс iPhone1:

Class iPhone1(iPhone):
    def __init__(self,memory,user_id):
         super().__init__(memory,user_id)
         self.pics = []
    def click_pic(self):
         # Some Implementation Here
         pic_taken = ...
         self.pics.append(pic_taken)

И это концепция наследования. Согласно Википедии: Наследование - это механизм базирования объекта или класса на другом объекте или классе, сохраняющем аналогичную реализацию. Проще говоря, iPhone1 теперь имеет доступ ко всем переменным и методам, определенным в классе iPhone.

В этом случае нам не нужно дублировать код, поскольку мы унаследовали (взяли) все методы от нашего родительского класса iPhone. Таким образом, нам не нужно снова определять функцию call. Кроме того, мы не устанавливаем mobile_id и memory в функции __init__ с помощью super.

Но что это за super().__init__(memory,user_id)?

В реальной жизни ваши __init__ функции не будут такими красивыми двухстрочными функциями. Вам нужно будет определить множество переменных / атрибутов в вашем классе, и копирование и вставка их для дочернего класса (здесь iphone1) становится громоздким. Таким образом, существует super (). Здесь super().__init__() фактически вызывает метод __init__ родительского класса iPhone. Итак, здесь, когда функция __init__ класса iPhone1 запускается, она автоматически устанавливает memory и user_id класса, используя функцию __init__ родительского класса.

Где мы видим это в ML / DS / DL? Ниже показано, как мы создаем модель PyTorch. Эта модель наследует все от класса nn.Module и вызывает функцию __init__ этого класса с помощью вызова super.

class myNeuralNet(nn.Module):
    def __init__(self):
        super().__init__()
        # Define all your Layers Here
        self.lin1 = nn.Linear(784, 30)
        self.lin2 = nn.Linear(30, 10)
    def forward(self, x):
        # Connect the layer Outputs here to define the forward pass
        x = self.lin1(x)
        x = self.lin2(x)
        return x

Но что такое полиморфизм? Мы все лучше понимаем, как работают классы, поэтому я бы попытался объяснить полиморфизм сейчас. Посмотрите на приведенный ниже класс.

Здесь у нас есть базовый class Shape и другие производные классы - Rectangle и Circle. Также посмотрите, как мы используем несколько уровней наследования в классе Square, который является производным от Rectangle, который, в свою очередь, является производным от Shape. Каждый из этих классов имеет функцию area, которая определяется в соответствии с формой. Таким образом, концепция, согласно которой функция с одним и тем же именем может выполнять несколько действий, стала возможной благодаря полиморфизму в Python. Фактически, это буквальное значение слова «полиморфизм»: «нечто, что принимает множество форм». Итак, наша функция area принимает несколько форм.

Другой способ, которым полиморфизм работает с Python, - это isinstance метода. Итак, используя приведенный выше класс, если мы это сделаем:

Таким образом, тип экземпляра объекта mySquare - Square, Rectangleи Shape. Следовательно, объект полиморфен. У него много хороших свойств. Например, мы можем создать функцию, которая работает с объектом Shape, и она будет полностью работать с любым из производных классов (Square, Circle, Rectangle и т. Д.), Используя полиморфизм.

Дополнительная информация:

Почему мы видим имена функций или атрибутов, начинающиеся с одинарного и двойного подчеркивания? Иногда мы хотим сделать наши атрибуты и функции в классах закрытыми и не позволять пользователю их видеть. Это часть инкапсуляции, где мы хотим «ограничить прямой доступ к некоторым компонентам объекта». Например, допустим, мы не хотим, чтобы пользователь мог видеть память (RAM) нашего iPhone после его создания. В таких случаях мы создаем атрибут, используя символы подчеркивания в именах переменных.

Поэтому, когда мы создадим класс iPhone описанным ниже способом, вы не сможете получить доступ к своему телефону memory или privatefunc с помощью Tab в записных книжках ipython, потому что теперь атрибут становится закрытым с помощью _.

Но вы все равно сможете изменить значение переменной, используя (хотя и не рекомендуется),

Вы также можете использовать метод _privatefunc, используя myphone._privatefunc(). Если вы хотите избежать этого, вы можете использовать двойное подчеркивание перед именем переменной. Например, нижеприведенный вызов print(myphone.__memory) вызывает ошибку. Кроме того, вы не можете изменить внутренние данные объекта с помощью myphone.__memory = 1.

Но, как вы видите, вы можете получить доступ и изменить эти self.__memory значения в определении вашего класса, например, в функции setMemory.

Заключение

Я надеюсь, что это было полезно для вас, чтобы понять классы. Осталось еще так много классов, что я расскажу в своем следующем посте о магических методах. Будьте на связи. Кроме того, в этом посте мы узнали об ООП и создании классов, а также о различных основах ООП:

  • Инкапсуляция: объект содержит все данные для себя.
  • Наследование: мы можем создать иерархию классов, в которой методы родительских классов передаются дочерним классам.
  • Полиморфизм: функция может принимать разные формы или объект может иметь несколько типов.

В завершение этого поста я хотел бы дать вам упражнение, которое, как мне кажется, может прояснить для вас некоторые концепции. Создайте класс, позволяющий управлять трехмерными объектами (сферой и кубом) с объемами и площадями. Базовый шаблонный код приведен ниже:

import math
class Shape3d:
    def __init__(self, name):
        self.name = name
    def surfaceArea(self):
        pass
        
    def volume(self):
        pass
    
    def getName(self):
        return self.name
        
class Cuboid():
    pass
class Cube():
    pass
        
class Sphere():
    pass

Я оставлю ответ в комментариях к этой статье.

Если вы хотите узнать больше о Python, я хотел бы назвать отличный курс Learn Intermediate level Python от Мичиганского университета. Проверьте это.

Я собираюсь писать больше таких постов в будущем. Сообщите мне, что вы думаете о сериале. Подпишитесь на меня в Medium или подпишитесь на мой блог, чтобы быть в курсе о них. Как всегда, я приветствую отзывы и конструктивную критику, и с ними можно связаться в Twitter @mlwhiz.

Также небольшой отказ от ответственности - в этом посте могут быть партнерские ссылки на соответствующие ресурсы, так как обмен знаниями никогда не является плохой идеей.