Введение
Различные аспекты поведения Python можно настраивать с помощью специальных или так называемых "магических" методов. В этом разделе представлена эта идея. Кроме того, обсуждается динамический доступ к атрибутам и связанные методы.
Введение
Классы могут определять специальные методы. Они имеют особое значение для интерпретатора Python. Они всегда предваряются и заканчиваются символами __. Например, __init__.
class Stock(object):
def __init__(self):
...
def __repr__(self):
...
Существует несколько десятков специальных методов, но мы рассмотрим только несколько конкретных примеров.
Особые методы для преобразования в строку
Объекты имеют две строковые репрезентации.
>>> from datetime import date
>>> d = date(2012, 12, 21)
>>> print(d)
2012-12-21
>>> d
datetime.date(2012, 12, 21)
>>>
Функция str() используется для создания приятного для печати вывода:
>>> str(d)
'2012-12-21'
>>>
Функция repr() используется для создания более детальной репрезентации для программистов.
>>> repr(d)
'datetime.date(2012, 12, 21)'
>>>
Эти функции, str() и repr(), используют пару специальных методов в классе для получения строки, которая будет отображаться.
class Date(object):
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
## Используется с `str()`
def __str__(self):
return f'{self.year}-{self.month}-{self.day}'
## Используется с `repr()`
def __repr__(self):
return f'Date({self.year},{self.month},{self.day})'
Примечание: Соглашение для __repr__() заключается в том, чтобы вернуть строку, которая, при передаче в eval(), будет воссоздавать исходный объект. Если это невозможно, вместо этого используется некоторый легко читаемый вид представления.
Особые методы для математики
Математические операторы вызывают следующие методы.
a + b a.__add__(b)
a - b a.__sub__(b)
a * b a.__mul__(b)
a / b a.__truediv__(b)
a // b a.__floordiv__(b)
a % b a.__mod__(b)
a << b a.__lshift__(b)
a >> b a.__rshift__(b)
a & b a.__and__(b)
a | b a.__or__(b)
a ^ b a.__xor__(b)
a ** b a.__pow__(b)
-a a.__neg__()
~a a.__invert__()
abs(a) a.__abs__()
Особые методы для доступа к элементам
Это методы для реализации контейнеров.
len(x) x.__len__()
x[a] x.__getitem__(a)
x[a] = v x.__setitem__(a,v)
del x[a] x.__delitem__(a)
Вы можете использовать их в своих классах.
class Sequence:
def __len__(self):
...
def __getitem__(self,a):
...
def __setitem__(self,a,v):
...
def __delitem__(self,a):
...
Вызов метода
Вызов метода представляет собой двухэтапный процесс.
- Поиск: оператор
. - Вызов метода: оператор
()
>>> s = stock.Stock('GOOG',100,490.10)
>>> c = s.cost ## Поиск
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c() ## Вызов метода
49010.0
>>>
Связанные методы
Метод, который еще не был вызван оператором вызова функции (), называется связанным методом. Он действует на экземпляр, откуда он произошел.
>>> s = stock.Stock('GOOG', 100, 490.10)
>>> s
<Stock object at 0x590d0>
>>> c = s.cost
>>> c
<bound method Stock.cost of <Stock object at 0x590d0>>
>>> c()
49010.0
>>>
Связанные методы часто являются источником невнимательных и неочевидных ошибок. Например:
>>> s = stock.Stock('GOOG', 100, 490.10)
>>> print('Cost : %0.2f' % s.cost)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: float argument required
>>>
Или скрытого поведения, которое трудно отлаживать.
f = open(filename, 'w')
...
f.close ## Упс, ничего не сделал. `f` по-прежнему открыт.
В обоих случаях ошибка возникает из-за забывания поставить конечные круглые скобки. Например, s.cost() или f.close().
Доступ к атрибутам
Существует альтернативный способ доступа, манипуляции и управления атрибутами.
getattr(obj, 'name') ## То же, что и obj.name
setattr(obj, 'name', value) ## То же, что и obj.name = value
delattr(obj, 'name') ## То же, что и del obj.name
hasattr(obj, 'name') ## Проверяет, существует ли атрибут
Пример:
if hasattr(obj, 'x'):
x = getattr(obj, 'x'):
else:
x = None
*Примечание: у getattr() также есть полезное значение по умолчанию *arg*.
x = getattr(obj, 'x', None)
Упражнение 4.9: Лучший вывод для печати объектов
Измените объект Stock, который вы определили в stock.py, так чтобы метод __repr__() выдавал более полезный вывод. Например:
>>> goog = stock.Stock('GOOG', 100, 490.1)
>>> goog
Stock('GOOG', 100, 490.1)
>>>
Посмотрите, что произойдет, когда вы прочитаете портфель акций и просмотрите результирующий список после внесения этих изменений. Например:
>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> portfolio
... посмотрите, какой будет вывод...
>>>
Упражнение 4.10: Пример использования getattr()
getattr() - это альтернативный механизм для чтения атрибутов. Его можно использовать для написания чрезвычайно гибкого кода. Сначала попробуйте этот пример:
>>> import stock
>>> s = stock.Stock('GOOG', 100, 490.1)
>>> columns = ['name','shares']
>>> for colname in columns:
print(colname, '=', getattr(s, colname))
name = GOOG
shares = 100
>>>
Обратите внимание, что выходные данные определяются полностью именами атрибутов, перечисленными в переменной columns.
В файле tableformat.py развяжите эту идею и расширьте ее до обобщенной функции print_table(), которая выводит таблицу с указанными пользователем атрибутами списка произвольных объектов. Как и раньше функция print_report(), print_table() должна также принимать экземпляр TableFormatter для управления форматом вывода. Вот, как это должно работать:
>>> import report
>>> portfolio = report.read_portfolio('portfolio.csv')
>>> from tableformat import create_formatter, print_table
>>> formatter = create_formatter('txt')
>>> print_table(portfolio, ['name','shares'], formatter)
name shares
---------- ----------
AA 100
IBM 50
CAT 150
MSFT 200
GE 95
MSFT 50
IBM 100
>>> print_table(portfolio, ['name','shares', 'price'], formatter)
name shares price
---------- ---------- ----------
AA 100 32.2
IBM 50 91.1
CAT 150 83.44
MSFT 200 51.23
GE 95 40.37
MSFT 50 65.1
IBM 100 70.44
>>>
Резюме
Поздравляем! Вы завершили лабораторную работу по специальным методам. Вы можете практиковаться в более лабораторных работах в LabEx, чтобы улучшить свои навыки.