Простой Python — страница 26 из 66

>>> class Bill():

…·····def __init__(self, description):

…·········self.description = description

>>> class Tail():

…·····def __init__(self, length):

…·········self.length = length

>>> class Duck():

…·····def __init__(self, bill, tail):

…·········self.bill = bill

…·········self.tail = tail

…·····def about(self):

…·········print('This duck has a', bill.description, 'bill and a',

··················tail.length, 'tail')

>>> tail = Tail('long')

>>> bill = Bill('wide orange')

>>> duck = Duck(bill, tail)

>>> duck.about()

This duck has a wide orange bill and a long tail

Когда лучше использовать классы и объекты, а когда — модули

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

• Объекты наиболее полезны, когда вам нужно иметь некоторое количество отдельных экземпляров с одинаковым поведением (методами), но различающихся внутренним состоянием (атрибутами).

• Классы, в отличие от модулей, поддерживают наследование.

• Если вам нужен только один объект, модуль подойдет лучше. Независимо от того, сколько обращений к модулю имеется в программе, будет загружена только одна копия. (Программистам на Java и С++: если вы знакомы с книгой Эриха Гаммы «Приемы объектно-ориентированного проектирования. Паттерны проектирования» (Gamma E. Design Patterns: Elements of Reusable Object-Oriented Software), можете использовать модули в Python как синглтоны.)

• Если у вас есть несколько переменных, которые содержат разные значения и могут быть переданы как аргументы в несколько функций, лучше всего определить их как классы. Например, вы можете использовать словарь с ключами size и color, чтобы представить цветное изображение. Вы можете создать разные словари для каждого изображения в программе и передавать их в качестве аргументов в функции scale() и transform(). По мере добавления новых ключей и функций может начаться путаница. Более последовательно было бы определить класс Image с атрибутами size или color и методами scale() и transform(). В этом случае все данные и методы для работы с цветными изображениями будут определены в одном месте.

• Используйте простейшее решение задачи. Словарь, список или кортеж проще, компактнее и быстрее, чем модуль, который, в свою очередь, проще, чем класс.

Совет от Гвидо ван Россума: «Избегайте усложнения структур данных. Кортежи лучше объектов (можно воспользоваться именованными кортежами). Предпочитайте простые поля функциям, геттерам и сеттерам. Используйте больше чисел, строк, кортежей, списков, множеств, словарей. Взгляните также на библиотеку collections, особенно на класс deque».

Именованные кортежи. Поскольку Гвидо только что упомянул их, а я про них еще не говорил, самое время рассмотреть именованные кортежи. Именованный кортеж — это подкласс кортежей, с помощью которых вы можете получить доступ к значениям по имени (с помощью конструкции. name) и позиции (с помощью конструкции [offset]).

Рассмотрим пример из предыдущего раздела и преобразуем класс Duck в именованный кортеж, сохранив bill и tail как простые строковые аргументы. Функцию namedtuple можно вызвать, передав ей два аргумента:

• имя;

• строку, содержащую имена полей, разделенные пробелами.

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

>>> from collections import namedtuple

>>> Duck = namedtuple('Duck', 'bill tail')

>>> duck = Duck('wide orange', 'long')

>>> duck

Duck(bill='wide orange', tail='long')

>>> duck.bill

'wide orange'

>>> duck.tail

'long'

Именованный кортеж можно сделать также на основе словаря:

>>> parts = {'bill': 'wide orange', 'tail': 'long'}

>>> duck2 = Duck(**parts)

>>> duck2

Duck(bill='wide orange', tail='long')

В коде, показанном ранее, обратите внимание на конструкцию **parts. Это аргумент — ключевое слово. Он извлекает ключи и значения словаря parts и передает их как аргументы в Duck(). По эффекту это похоже на следующий код:

>>> duck2 = Duck(bill = 'wide orange', tail = 'long')

Именованные кортежи неизменяемы, но вы можете заменить одно или несколько полей и вернуть другой именованный кортеж:

>>> duck3 = duck2._replace(tail='magnificent', bill='crushing')

>>> duck3

Duck(bill='crushing', tail='magnificent')

Мы могли бы объявить duck как словарь:

>>> duck_dict = {'bill': 'wide orange', 'tail': 'long'}

>>> duck_dict

{'tail': 'long', 'bill': 'wide orange'}

Вы можете добавить поля в словарь:

>>> duck_dict['color'] = 'green'

>>> duck_dict

{'color': 'green', 'tail': 'long', 'bill': 'wide orange'}

Но не в именованный кортеж:

>>> duck.color = 'green'

Traceback (most recent call last):

··File "", line 1, in 

AttributeError: 'dict' object has no attribute 'color'

Вспомним плюсы использования именованного кортежа.

• Они выглядят и действуют как неизменяемый объект.

• Они более эффективны, чем объекты, с точки зрения времени и занимаемого места.

• Вы можете получить доступ к атрибутам с помощью точки вместо квадратных скобок, характерных для словарей.

• Вы можете использовать их как ключ словаря.

Упражнения

1. Создайте класс, который называется Thing, не имеющий содержимого, и выведите его на экран. Затем создайте объект example этого класса и также выведите его. Совпадают ли выведенные значения?

2. Создайте новый класс с именем Thing2 и присвойте его атрибуту letters значение 'abc'. Выведите на экран значение атрибута letters.

3. Создайте еще один класс, который, конечно же, называется Thing3. В этот раз присвойте значение 'xyz' атрибуту объекта, который называется letters. Выведите на экран значение атрибута letters. Понадобилось ли вам создавать объект класса, чтобы сделать это?

4. Создайте класс, который называется Element, имеющий атрибуты объекта name, symbol и number. Создайте объект этого класса со значениями 'Hydrogen', 'H' и 1.

5. Создайте словарь со следующими ключами и значениями: 'name': 'Hydrogen', 'symbol': 'H', 'number': 1. Далее создайте объект с именем hydrogen класса Element с помощью этого словаря.

6. Для класса Element определите метод с именем dump(), который выводит на экран значения атрибутов объекта (name, symbol и number). Создайте объект hydrogen из этого нового определения и используйте метод dump(), чтобы вывести на экран его атрибуты.

7. Вызовите функцию print(hydrogen). В определении класса Element измените имя метода dump на __str__, создайте новый объект hydrogen и затем снова вызовите метод print(hydrogen).

8. Модифицируйте класс Element, сделав атрибуты name, symbol и number закрытыми. Определите для каждого атрибута свойство получателя, возвращающее значение соответствующего атрибута.

9. Определите три класса: Bear, Rabbit и Octothorpe. Для каждого из них определите всего один метод — eats(). Он должен возвращать значения 'berries' (для Bear), 'clover' (для Rabbit) или 'campers' (для Octothorpe). Создайте по одному объекту каждого класса и выведите на экран то, что ест указанное животное.

10. Определите три класса: Laser, Claw и SmartPhone. Каждый из них имеет только один метод — does(). Он возвращает значения 'disintegrate' (для Laser), 'crush' (для Claw) или 'ring' (для SmartPhone). Далее определите класс Robot, который содержит по одному объекту каждого из этих классов. Определите метод does() для класса Robot, который выводит на экран все, что делают его компоненты.

Глава 7. Работаем с данными профессионально

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

•