ШОРТЫ НА ПИТОНЕ
Объяснение объектно-ориентированного программирования для специалистов по данным
Простое руководство по началу использования классов в 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.
Также небольшой отказ от ответственности - в этом посте могут быть партнерские ссылки на соответствующие ресурсы, так как обмен знаниями никогда не является плохой идеей.