Как мне разработать класс в Python? - PullRequest
141 голосов
/ 17 ноября 2010

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

Теперь у меня есть данные , которые состоят из:

  • около 30 собак;
  • каждый имеет 24 измерения (разделенных на несколько подгрупп);
  • каждое измерение имеет как минимум 4 контакта (по одному на каждую лапу), а
    • каждый контакт делится на 5 частей, а
    • имеет несколько параметров, таких как время контакта, местоположение,полная сила и т. д.

alt text

Очевидно, что склеивание всего в один большой объект не приведет к его разрезанию, поэтому я решил, что мне нужно использоватьклассы вместо текущего множества функций.Но хотя я прочитал главу Learning Python о классах, я не смог применить ее к своему собственному коду ( ссылка на GitHub )

Я также чувствую, что обрабатывать все данные довольно странно каждый раз, когда я хочу получить некоторую информацию.Как только я узнаю расположение каждой лапы, у меня нет оснований снова рассчитывать это.Кроме того, я хочу сравнить все лапы одной и той же собаки, чтобы определить, какой контакт принадлежит какой лапе (передняя / задняя, ​​левая / правая).Это может привести к путанице, если я продолжу использовать только функции.

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

Ответы [ 6 ]

429 голосов
/ 17 ноября 2010

Как спроектировать класс.

  1. Запишите слова.Вы начали это делать.Некоторые люди не знают и удивляются, почему у них проблемы.

  2. Расширьте свой набор слов в простые утверждения о том, что будут делать эти объекты.То есть запишите различные вычисления, которые вы будете делать для этих вещей.Ваш короткий список из 30 собак, 24 измерений, 4 контактов и нескольких «параметров» на контакт интересен, но это только часть истории.Ваши «местоположения каждой лапы» и «сравнить все лапы одной и той же собаки, чтобы определить, какой контакт принадлежит какой лапе» являются следующим шагом в проектировании объекта.

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

  4. Просмотрите существительные.Общие существительные, такие как «параметр» и «измерение», должны быть заменены конкретными, конкретными существительными, которые относятся к вашей проблеме в вашей проблемной области.Особенности помогут прояснить проблему.Обобщения просто исключают детали.

  5. Для каждого существительного («контакт», «лапа», «собака» и т. Д.) Запишите атрибуты этого существительного и действия, в которых этот объектвходит в зацепление.Не сокращайте это.Каждый атрибут.Например, важно, чтобы «набор данных содержал 30 собак».

  6. Для каждого атрибута определите, является ли это отношением к определенному существительному, или какому-либо другому типу «примитива» или «атома»«данные, такие как строка или число с плавающей точкой или что-то неприводимое.

  7. Для каждого действия или операции вы должны определить, какое существительное несет ответственность, а какие просто участвуют.Это вопрос "изменчивости".Некоторые объекты обновляются, другие нет.Изменяемые объекты должны нести полную ответственность за свои мутации.

  8. С этого момента вы можете начать преобразовывать существительные в определения классов.Некоторые коллективные существительные являются списками, словарями, кортежами, наборами или именованными кортежами, и вам не нужно много работать.Другие классы являются более сложными, либо из-за сложных производных данных, либо из-за выполняемого обновления / мутации.

Не забудьте протестировать каждый класс изолированно с помощью unittest.

Кроме того, нет закона, согласно которому классы должны быть изменяемыми.В вашем случае, например, у вас почти нет изменяемых данных.То, что у вас есть, это производные данные, созданные функциями преобразования из исходного набора данных.

22 голосов
/ 22 ноября 2013

Следующие советы (аналогичные советам @ S.Lott) взяты из книги Начало Python: от новичка до профессионала

  1. Запишитеописание вашей проблемы (что должна делать проблема?).Подчеркните все существительные, глаголы и прилагательные.

  2. Пройдите через существительные в поисках потенциальных классов.

  3. Пройдите по глаголам, глядядля потенциальных методов.

  4. Просмотрите прилагательные, ища потенциальные атрибуты

  5. Назначьте методы и атрибуты своим классам

Чтобы уточнить класс, книга также рекомендует сделать следующее:

Запишите (или придумайте) набор вариантов использования - сценарии использования вашей программы.Постарайтесь охватить все функционально.

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

13 голосов
/ 22 ноября 2013

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

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

Тогда самое интересное ... рефакторинг.После того, как у вас есть работающее программное обеспечение, вы можете увидеть сложные части.Часто появляются небольшие карманы поведения, предлагающие новый класс, но если нет, просто ищите способы упростить код.Извлечение сервисных объектов и объектов стоимости.Упростите свои методы.

Если вы используете git правильно (не так ли?), Вы можете очень быстро поэкспериментировать с каким-то конкретным разложением во время рефакторинга, а затем отказаться от него и вернуться, еслиэто не упрощает вещи.

При написании проверенного рабочего кода сначала вы должны получить глубокое понимание проблемной области, которую вы не могли бы легко получить с помощью подхода, ориентированного на первый дизайн.Написание тестов и кода подталкивает вас к параличу «с чего начать».

3 голосов
/ 17 ноября 2010

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

dog.footstep(0)

Теперь,может случиться так, что для вашего случая вам нужно прочитать файл необработанных данных и вычислить расположение шагов.Все это может быть скрыто в функции footstep (), чтобы это происходило только один раз.Примерно так:

 class Dog:
   def __init__(self):
     self._footsteps=None 
   def footstep(self,n):
     if not self._footsteps:
        self.readInFootsteps(...)
     return self._footsteps[n]

[Теперь это своего рода шаблон кэширования.В первый раз, когда он идет и читает данные шага, в последующие раз он просто получает их из self._footsteps.]

Но да, получить правильную структуру ОО может быть сложно.Подумайте больше о том, что вы хотите сделать со своими данными, и это сообщит, какие методы вам нужно применить к каким классам.

2 голосов
/ 24 ноября 2013

После просмотра связанного кода мне кажется, что вам лучше не разрабатывать класс Dog в данный момент. Скорее вы должны использовать Pandas и dataframes . Фрейм данных - это таблица со столбцами. Ваш фрейм данных будет иметь такие столбцы, как: dog_id, contact_part, contact_time, contact_location и т. Д. За кулисами Pandas использует массивы Numpy, и у вас есть много удобных методов:

  • Выберите собаку, например, : my_measurements['dog_id']=='Charly'
  • сохранить данные: my_measurements.save('filename.pickle')
  • Попробуйте использовать pandas.read_csv() вместо чтения текстовых файлов вручную.
2 голосов
/ 23 ноября 2013

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

Представьте, что у вас есть объект Query и объект Database:

Объект Query поможет вам создать и сохранить запрос - хранилище, ключ к которому здесь, так какфункция может помочь вам создать ее так же легко.Может быть, вы могли бы остаться: Query().select('Country').from_table('User').where('Country == "Brazil"').Не имеет значения, какой именно синтаксис - это ваша работа!- ключ в том, что объект помогает вам скрыть что-то , в этом случае данные, необходимые для хранения и вывода запроса.Сила объекта заключается в синтаксисе его использования (в данном случае некоторого умного связывания) и необходимости узнавать, что он хранит, чтобы заставить его работать.Если все сделано правильно, объект Query может выводить запросы для более чем одной базы данных.Внутри он будет хранить определенный формат, но при выводе может легко конвертировать в другие форматы (Postgres, MySQL, MongoDB).

Теперь давайте подумаем об объекте Database.Что это скрывает и хранит?Очевидно, что он не может хранить все содержимое базы данных, потому что именно поэтому у нас есть база данных!Так в чем смысл?Цель состоит в том, чтобы скрыть, как работает база данных от людей, которые используют объект Database.Хорошие классы упростят рассуждения при манипулировании внутренним состоянием.Для этого объекта Database вы можете скрыть, как работают сетевые вызовы, или выполнять пакетные запросы или обновления, или предоставлять уровень кэширования.

Проблема в том, что Database объект ОГРОМНЫЙ.Он представляет, как получить доступ к базе данных, поэтому под прикрытием он может делать все что угодно.Очевидно, что с сетью, кэшированием и пакетированием довольно сложно справиться в зависимости от вашей системы, поэтому было бы очень полезно скрыть их.Но, как заметят многие, база данных безумно сложна, и чем дальше вы получаете необработанные вызовы БД, тем сложнее настроить производительность и понять, как все работает.

Это фундаментальный компромисс ООП.Если вы выберете правильную абстракцию, это упростит кодирование (String, Array, Dictionary), если вы выберете слишком большую абстракцию (Database, EmailManager, NetworkingManager), она может стать слишком сложной, чтобы действительно понять, как она работает, или чтоожидать.Цель состоит в том, чтобы скрыть сложность , но некоторая сложность необходима.Хорошее практическое правило - избегать Manager объектов и вместо этого создавать классы, подобные structs - все, что они делают, - это хранят данные с некоторыми вспомогательными методами для создания / манипулирования данными, чтобы сделать вашу жизнь проще.Например, в случае EmailManager начните с функции с именем sendEmail, которая принимает объект Email.Это простая отправная точка, и код очень прост для понимания.

Что касается вашего примера, подумайте, какие данные должны быть вместе, чтобы рассчитать то, что вы ищете.Например, если вы хотите узнать, как далеко зашло животное, у вас могут быть классы AnimalStep и AnimalTrip (коллекция AnimalSteps).Теперь, когда каждая Поездка имеет все данные о Шаге, она должна иметь возможность разобраться с этим, возможно, AnimalTrip.calculateDistance() имеет смысл.

...