Коли я повинен використовувати класи в Python?


177

Я програю в python близько двох років; здебільшого дані (панди, mpl, numpy), а також сценарії автоматизації та невеликі веб-додатки. Я намагаюся стати кращим програмістом і збільшити свої пітонські знання, і одна з речей, яка мене турбує, - це те, що я ніколи не використовував клас (за винятком копіювання випадкового фляш-коду для невеликих веб-додатків). Я, як правило, розумію, що вони є, але я не можу, мовби обернути голову, навіщо мені це потрібно через просту функцію.

Щоб додати специфіку моєму запитанню: я пишу тони автоматизованих звітів, які завжди передбачають витяг даних з декількох джерел даних (mongo, sql, postgres, apis), виконуючи багато чи невелику зміну та форматування даних, записуючи дані в csv / excel / html, надішліть його електронною поштою. Сценарії варіюються від ~ 250 рядків до ~ 600 рядків. Чи буде якась причина для мене використовувати заняття для цього і чому?


15
немає нічого поганого в коді без класів, якщо ви можете керувати своїм кодом приємніше. Програмісти OOP, як правило, перебільшують проблеми через обмеження мовної конструкції або поверхневе розуміння різних моделей.
Джейсон Ху

Відповіді:


133

Заняття - це стовп об'єктно-орієнтованого програмування . OOP сильно переймається організацією коду, повторним використанням та інкапсуляцією.

По-перше, відмова від відповідальності: OOP частково на відміну від функціонального програмування , що є різною парадигмою, яка багато використовується в Python. Не всі, хто програмує на Python (або, мабуть, більшість мов), використовує OOP. Ви можете багато зробити в Java 8, що не дуже об’єктно-орієнтоване. Якщо ви не хочете використовувати OOP, не робіть цього. Якщо ви просто пишете одноразові сценарії для обробки даних, які більше ніколи не використовуєте, продовжуйте писати так, як ви є.

Однак є маса причин використовувати OOP.

Деякі причини:

  • Організація: OOP визначає добре відомі та стандартні способи опису та визначення даних і процедур у коді. І дані, і процедура можуть зберігатися на різних рівнях визначення (у різних класах), і є стандартні способи говорити про ці визначення. Тобто, якщо ви використовуєте OOP стандартним способом, це допоможе вашому пізніше самому та іншим зрозуміти, редагувати та використовувати ваш код. Крім того, замість того, щоб використовувати складний, довільний механізм зберігання даних (дикти диктів чи списків або дикти або списки диктовок наборів, чи будь-що інше), ви можете називати фрагменти структур даних і зручно посилатися на них.

  • Штат: OOP допомагає визначити та відстежувати стан. Наприклад, у класичному прикладі, якщо ви створюєте програму, яка обробляє учнів (наприклад, програму оцінок), ви можете зберігати всю необхідну інформацію про них в одному місці (ім'я, вік, стать, рівень, курси, оцінки, викладачі, однолітки, дієта, особливі потреби тощо), і ці дані зберігаються до тих пір, поки об’єкт живий і легко доступний.

  • Інкапсуляція : при інкапсуляції процедура та дані зберігаються разом. Методи (термін OOP для функцій) визначаються прямо поряд із даними, над якими вони працюють та виробляють. У такій мові, як Java, яка дозволяє контролювати доступ , або в Python, залежно від того, як ви описуєте свій публічний API, це означає, що методи та дані можуть бути приховані від користувача. Це означає, що якщо вам потрібно або хочете змінити код, ви можете робити все, що завгодно, щоб реалізувати код, але зберігати загальнодоступні API-інтерфейси однаковими.

  • Спадщина : Спадкування дозволяє визначити дані та процедуру в одному місці (в одному класі), а потім переозначити або розширити цю функціональність пізніше. Наприклад, у Python я часто бачу людей, які створюють підкласи dictкласу, щоб додати додаткову функціональність. Загальна зміна - це метод, який видає виняток, коли ключ запитується зі словника, який не існує, щоб дати значення за замовчуванням на основі невідомого ключа. Це дозволяє розширити власний код зараз або пізніше, дозволити іншим розширити ваш код і дозволяє розширити код інших людей.

  • Повторність використання: Усі ці причини та інші дозволяють збільшити повторне використання коду. Об'єктно-орієнтований код дозволяє один раз написати твердий (перевірений) код, а потім повторно використовувати знову і знову. Якщо вам потрібно щось налаштувати для конкретного випадку використання, ви можете успадкувати існуючий клас і замінити наявну поведінку. Якщо вам потрібно щось змінити, ви можете все це змінити, зберігаючи існуючі підписи публічного методу, і ніхто не мудріший (сподіваємось).

Знову ж таки, є кілька причин не використовувати OOP, і вам цього не потрібно. Але на щастя з такою мовою, як Python, ви можете використовувати трохи або багато, це вирішувати вам.

Приклад випадку використання студентом (немає гарантії на якість коду, лише приклад):

Об'єктно-орієнтована

class Student(object):
    def __init__(self, name, age, gender, level, grades=None):
        self.name = name
        self.age = age
        self.gender = gender
        self.level = level
        self.grades = grades or {}

    def setGrade(self, course, grade):
        self.grades[course] = grade

    def getGrade(self, course):
        return self.grades[course]

    def getGPA(self):
        return sum(self.grades.values())/len(self.grades)

# Define some students
john = Student("John", 12, "male", 6, {"math":3.3})
jane = Student("Jane", 12, "female", 6, {"math":3.5})

# Now we can get to the grades easily
print(john.getGPA())
print(jane.getGPA())

Стандартний дікт

def calculateGPA(gradeDict):
    return sum(gradeDict.values())/len(gradeDict)

students = {}
# We can set the keys to variables so we might minimize typos
name, age, gender, level, grades = "name", "age", "gender", "level", "grades"
john, jane = "john", "jane"
math = "math"
students[john] = {}
students[john][age] = 12
students[john][gender] = "male"
students[john][level] = 6
students[john][grades] = {math:3.3}

students[jane] = {}
students[jane][age] = 12
students[jane][gender] = "female"
students[jane][level] = 6
students[jane][grades] = {math:3.5}

# At this point, we need to remember who the students are and where the grades are stored. Not a huge deal, but avoided by OOP.
print(calculateGPA(students[john][grades]))
print(calculateGPA(students[jane][grades]))

Через "урожайність" інкапсуляція Python часто чистіша з генераторами та менеджерами контексту, ніж з класами.
Дмитро Рубанович

4
@meter Я додав приклад. Я сподіваюся, що це допомагає. Примітка тут полягає в тому, що замість того, щоб покладатися на те, що ключі ваших диктів мають правильне ім'я, інтерпретатор Python робить це обмеження для вас, якщо ви зіпсуєте і змусить вас використовувати визначені методи (хоча не визначені поля (хоча Java та інші Мови OOP не дозволяють визначати поля поза класами, такими як Python)).
Дантістон

5
@meter також, як приклад інкапсуляції: скажімо, сьогодні ця реалізація прекрасна, оскільки мені потрібно отримати середній бал лише 50 000 студентів в моєму університеті один раз на термін. Тепер завтра ми отримуємо грант і потрібно давати поточний середній бал кожному студенту щосекунди (звичайно, ніхто цього не просив би, але просто зробити його обчислювально складним). Потім ми могли «запам'ятати» GPA і обчислити його лише тоді, коли він зміниться (наприклад, встановивши змінну методом setGrade), інше поверне кешовану версію. Користувач все ще використовує getGPA (), але реалізація змінилася.
Дантістон

4
@dantiston, цей приклад потребує collection.namedtuple. Ви можете створити новий тип Student = collection.namedtuple ("Студент", "ім'я, вік, стать, рівень, оцінки"). І тоді ви можете створити екземпляри john = Student ("John", 12, "male", grade = {'math': 3.5}, рівень = 6). Зауважте, що ви використовуєте як позиційні, так і іменовані аргументи так само, як і при створенні класу. Це тип даних, який вже реалізований для вас у Python. Потім ви можете звернутися до john [0] або john.name, щоб отримати 1-й елемент кортежу. Ви можете отримати оцінки Джона як john.grades.values ​​() зараз. І це вже зроблено для вас.
Дмитро Рубанович

2
для мене інкапсуляція - достатньо вагомий привід завжди використовувати OOP. Я намагаюся побачити цінність НЕ використовуючи OOP для будь-якого проекту кодування з розумним розміром. Я думаю, мені потрібні відповіді на зворотне запитання :)
Сан-Джей

23

Кожного разу, коли вам потрібно підтримувати стан своїх функцій, це не може бути виконано генераторами (функції, які приносять результат, а не повертаються). Генератори підтримують власну державу.

Якщо ви хочете замінити будь-якого зі стандартних операторів, вам потрібен клас.

Кожного разу, коли ви використовуєте шаблон для відвідувачів, вам потрібні заняття. Будь-яка інша модель дизайну може бути досягнута більш ефективно та чисто з генераторами, менеджерами контексту (які також краще реалізуються як генератори, ніж як класи) та типами POD (словники, списки та кортежі тощо).

Якщо ви хочете написати "пітонічний" код, вам слід віддати перевагу контекстним менеджерам та генераторам над класами. Це буде чистіше.

Якщо ви хочете розширити функціонал, ви майже завжди зможете домогтися його стримування, а не спадкування.

Як кожне правило, це має виняток. Якщо ви хочете швидко інкапсулювати функціональність (тобто написати тестовий код, а не код багаторазового використання на рівні бібліотеки), ви можете інкапсулювати стан у класі. Це буде просто і не потребуватиме багаторазового використання.

Якщо вам потрібен деструктор стилю C ++ (RIIA), ви точно не хочете використовувати класи. Ви хочете, щоб менеджери контексту.


1
Закриття @DmitryRubanovich не реалізовано через генератори в Python.
Елі Корвіго

1
@DmitryRubanovich я мав на увазі "закриття реалізовані як генератори в Python", що не відповідає дійсності. Закривання набагато гнучкіше. Генератори зобов'язані повернути Generatorекземпляр (спеціальний ітератор), тоді як закриття може мати будь-який підпис. В основному ви можете уникати занять більшу частину часу, створюючи закриття. І закриття - це не просто "функції, визначені в контексті інших функцій".
Елі Корвіго

3
@ Елі Корвіго, насправді, генератори - це значний синтаксичний стрибок. Вони створюють абстракцію черги так само, як і функції абстракції стека. І більшість потоків даних можна зібрати разом із примітивів стека / черги.
Дмитро Рубанович

1
@DmitryRubanovich ми говоримо тут про яблука та апельсини. Я кажу, що генератори корисні у дуже обмеженій кількості випадків і жодним чином не можуть вважатися заміною загальноприйнятих загальнозміцнюючих викликів. Ви мені говорите, наскільки вони великі, не суперечивши моїм точкам.
Елі Корвіго

1
@ Елі Корвіго, і я кажу, що дзвінки - це лише узагальнення функцій. Які самі синтаксичні цукру над обробкою стеків. У той час як генератори синтаксичний цукор над обробкою черг. Але саме це вдосконалення синтаксису дозволяє створювати складніші конструкції легко та з більш чітким синтаксисом. '.next ()' майже ніколи не використовується, btw.
Дмитро Рубанович

11

Я думаю, що ти робиш це правильно. Класи розумні, коли вам потрібно моделювати певну ділову логіку чи складні реальні процеси із складними відносинами. Наприклад:

  • Кілька функцій із загальним станом
  • Більше однієї копії однакових змінних стану
  • Розширити поведінку існуючого функціоналу

Я також пропоную вам подивитися це класичне відео


3
Немає необхідності використовувати клас, коли функція зворотного дзвінка потребує постійного стану в Python. Використання урожайності Python замість повернення робить функцію повторного вступу.
Дмитро Рубанович

4

Клас визначає сутність реального світу. Якщо ви працюєте над тим, що існує індивідуально і має свою логіку, яка є окремою від інших, вам слід створити для цього клас. Наприклад, клас, який інкапсулює підключення до бази даних.

Якщо це не так, не потрібно створювати клас


0

Це залежить від вашої ідеї та дизайну. якщо ви хороший дизайнер, то OOP вийдуть природним чином у вигляді різних моделей дизайну. Для простого рівня сценаріїв обробка OOP може бути накладними. Прості розгляньте основні переваги OOP, як багаторазове використання та розширення, і переконайтесь, потрібні вони чи ні. ООС роблять складні речі простішими, а простіші - складними. Просто зберігає речі просто в будь-якому випадку, використовуючи OOP або не використовуючи OOP. що коли-небудь простіше використовувати це.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.