Подтвердить что ты не робот

Правила большого пальца для того, когда использовать перегрузку оператора в python

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

Вот пример, который использует побитовое или операцию для имитации конвейера unix. Это предназначено в качестве встречного примера для большинства правил.

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



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

Ответ 4

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

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

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

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

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