Настройка динамического поведения Python

Beginner

This tutorial is from open-source community. Access the source code

Введение

Различные аспекты поведения 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):
     ...

Вызов метода

Вызов метода представляет собой двухэтапный процесс.

  1. Поиск: оператор .
  2. Вызов метода: оператор ()
>>> 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, чтобы улучшить свои навыки.