Практические правила для использования перегрузки операторов в python - PullRequest
13 голосов
/ 12 октября 2009

Из того, что я помню из своего класса C ++, профессор сказал, что перегрузка операторов - это круто, но, поскольку для охвата всех конечных случаев требуется достаточно много времени и кода (например, при перегрузке +, вы, вероятно, также захотите перегрузка ++ и +=, а также обработка конечных случаев, таких как добавление объекта к себе и т. д.), вы должны учитывать это только в тех случаях, когда эта функция окажет серьезное влияние на ваш код, например при перегрузке операторы для класса матрицы в математическом приложении.

То же самое относится к питону? Вы бы порекомендовали переопределить поведение оператора в Python? И какие правила вы можете дать мне?

Ответы [ 4 ]

25 голосов
/ 12 октября 2009

Перегрузка операторов в основном полезна, когда вы создаете новый класс, который относится к существующему «Абстрактному базовому классу» (ABC) - действительно, многие из ABC в модуле стандартной библиотеки коллекции полагаются на Наличие определенных специальных методов (и специальных методов, имена которых начинаются и заканчиваются двойным подчеркиванием, «дундер» AKA - это именно тот способ, которым вы выполняете перегрузку операторов в Python). Это обеспечивает хорошее начальное руководство.

Например, Container класс должен переопределить специальный метод __contains__, т. Е. Оператор проверки членства item in container (например, if item in container: - не путать с for оператор, for item in container:, который опирается на __iter__! -). Аналогично, Hashable должен переопределить __hash__, Sized должен переопределить __len__, Sequence или Mapping должен переопределить __getitem__, и так далее. (Более того, ABC могут предоставить вашему классу смешанную функциональность - например, Sequence и Mapping могут предоставить __contains__ на основе предоставленного вами __getitem__ переопределения, и, таким образом, автоматически сделают ваш класс Container ).

Помимо collections, вы захотите переопределить специальные методы (т. Е. Предусмотреть перегрузку операторов) в основном, если ваш новый класс "является числом". Существуют и другие особые случаи, но они противостоят соблазну перегрузки операторов «просто ради крутости», без семантической связи с «нормальными» значениями, как это делают потоки C ++ для << и >> и строк Python (в Python 2.* к счастью, больше не в 3.* ;-) для % - когда такие операторы больше не означают «сдвиг битов» или «остаток деления», вы просто порождаете путаницу. Стандартная библиотека языка может сойти с рук (хотя это не должно происходить ;-), но если ваша библиотека не станет столь же широко распространенной, как стандартная библиотека языка, путаница повредит! -)

12 голосов
/ 12 октября 2009

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

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

Поэтому, если вы создаете новый класс RomanNumeral, имеет смысл перегружать сложение и вычитание и т. Д. Но не перегружайте его, если это не естественно: нет смысла определять сложение и вычитание для Car или Vehicle объект.

Еще одно практическое правило: не перегружайте ==. Это делает очень трудным (хотя и не невозможным) тестирование на предмет совпадения двух объектов. Я сделал эту ошибку и долго платил за нее.

Что касается времени перегрузки +=, ++ и т. Д., Я бы на самом деле сказал: перегрузка дополнительных операторов возможна только в том случае, если у вас много спроса на эту функциональность. Проще иметь один способ сделать что-то, чем пять. Конечно, это означает, что иногда вам придется писать x = x + 1 вместо x += 1, но больше кода в порядке, если он понятнее.

В целом, как и во многих «причудливых» функциях, легко подумать, что вы чего-то хотите, когда вы на самом деле этого не делаете, реализовывать кучу вещей, не замечать побочных эффектов, а потом выяснить это позже. Ошибка на консервативной стороне.

РЕДАКТИРОВАТЬ: Я хотел добавить пояснительную записку о перегрузке ==, потому что, кажется, различные комментаторы неправильно понимают это, и это застало меня врасплох. Да, is существует, но это другая операция. Скажем, у меня есть объект x, который либо из моего пользовательского класса, либо является целым числом. Я хочу посмотреть, если x - это число 500. Но если вы установите x = 500, а затем протестируйте x is 500, вы получите False из-за того, как Python кэширует числа. С 50 он вернул бы True. Но вы не можете использовать is, потому что вы можете захотеть, чтобы x == 500 вернул True, если x является экземпляром вашего класса. Смешение? Определенно. Но это та деталь, которую вам нужно понять, чтобы успешно перегрузить операторы.

5 голосов
/ 12 октября 2009

Вот пример, в котором используется побитовая операция или операция для имитации конвейера Unix. Это задумано как контрпример к большинству эмпирических правил.

Я только что нашел Дровосек , который использует этот синтаксис в реальном коде



class pipely(object):
    def __init__(self, *args, **kw):
        self._args = args
        self.__dict__.update(kw)

    def __ror__(self, other):
        return ( self.map(x) for x in other if self.filter(x) )

    def map(self, x):
        return x

    def filter(self, x):
        return True

class sieve(pipely):
    def filter(self, x):
        n = self._args[0]
        return x==n or x%n

class strify(pipely):
    def map(self, x):
        return str(x)

class startswith(pipely):
    def filter(self, x):
        n=str(self._args[0])
        if x.startswith(n):
            return x

print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | strify() | startswith(5):
    print i

print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | startswith(5):
    print i

print"*"*80
for i in xrange(2,100) | sieve(2) | sieve(3) | sieve(5) | sieve(7) | pipely(map=str) | pipely(filter=lambda x: x.startswith('5')):
    print i

3 голосов
/ 12 октября 2009

Перегрузка Python в целом "безопаснее", чем в C ++ - например, оператор присваивания не может быть перегружен, и += имеет разумную реализацию по умолчанию.

В некоторых случаях перегрузка в Python все еще "сломана", как в C ++. Программисты должны ограничивать желание «повторно использовать» оператор для несвязанных целей, таких как C ++, повторное использование битовых сдвигов для форматирования и анализа строк. Не перегружайте оператор другой семантикой из вашей реализации только для того, чтобы получить более красивый синтаксис.

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

  • %: модуль и форматирование строки
  • +: сложение и объединение последовательностей
  • *: умножение и повторение последовательности

Итак, эмпирическое правило? Если ваша операторская реализация удивит людей, не делайте этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...