0,0
рейтинг
28 февраля 2014 в 14:45

Разработка → Открытые классы в ruby, заметки для питонистов

По своему опыту знаю, что открытые классы в ruby раздражают и вызывают непонимание в среде питонистов. Ну в самом деле, что за странность открыть класс String и переопределить там size?


А можно рассмотреть вопрос в другой стороны, попробуем разобрать небольшой пример. Все знают как в python разные типы проверяются на истинность — всё что пусто, то ложь. В ruby это не так, здесь ложь это nil и false. Иногда было бы удобнее как в python.

Как это можно решить например в rust, включая поддержку для встроенных типов:

trait Existable {
  fn exist(&self) -> bool;
}

impl Existable for int {
  fn exist(&self) -> bool { if *self == 0 { false } else { true } }
}

fn main () {
  assert!(5.exist() == true)
  assert!(0.exist() == false)
}


Представим что в python схема не такая, и мы хотим ее реализовать. В python нельзя определить новые методы для сторонних классов, поэтому можно сделать что-то вроде:

class Trait(object):
    def __init__(self):
        self.registry = {}

    def impl(self, typ):
        def reg(func):
            self.registry[typ] = func
            return func
        return reg

    def __call__(self, arg):
        return self.registry[arg.__class__](arg)

Existable = Trait()


@Existable.impl(int)
def int_exists(i):
    return False if i == 0 else True

if __name__ == '__main__':
    assert Existable(0) == False
    assert Existable(5) == True

Ну или использовать какую-нибудь библиотеку, которая толковее минутного наброска.

Как в ruby реализовали подобную штуку?

class Object
# File activesupport/lib/active_support/core_ext/object/blank.rb, line 13
  def blank?
    respond_to?(:empty?) ? empty? : !self
  end
end


Метод `empty?` определен на классах `Array` и `Hash` (как минимум). Определите на своем классе и метод `blank?` будет корректно работать. То есть здесь мы имеем trait в ruby — объекты отвечающие на `empty?` реализуют trait проверки на пустоту, тот самый `Existable`.

Это простейший корректный пример использования открытых классов в ruby, а есть и много других замечательных штук из ActiveSupport, одной из частей Ruby on Rails.

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

Если было не совсем понятно, что я имею в виду — открытые классы в ruby позволяют доопределять поведение любых, встроенных в том числе, классов, также как и traits в rust и других языках.

PS Я не утверждаю что в python нельзя реализовать подобный механизм — я простенький пример даже привел в самой статье. Еще раз — речь о правильном использовании открытых классов в ruby, а не о преимуществах какого-либо языка перед другим.
Кривушин Михаил @Deepwalker
карма
18,7
рейтинг 0,0
Программист
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (39)

  • 0
    разве MixIn не из этой области?

    • 0
      Mixin это всего лишь набор методов, так скажем. Вопрос в том как его включить для работы в нужные классы. В ruby вы можете его подключить во встроенные классы, благодаря их открытости.
      • 0
        Всю жизнь знаю, что mixin — это способ внесения изменений в классы, без наследования.

        Не точно, но кажется нельзя подмешивать что-либо в классы int и str (и прочие builtin-классы, которые немного не такие), но во все остальные — никаких проблем.
        • 0
          А можно пример? Я что-то ни в одном проекте такого не встречал. Вы имеете в виду игры с __mro__?
          • 0
            Если под «игры с __mro__» подразумевались вариации на тему someClass.__bases__ += ( mixinClass, ) — то да.

            Но я слабо представляю, когда такого рода штуки реально нужны/полезны/красивы.
  • +1
    Эм, почему «В python нельзя определить новые методы для сторонних классов»?

    class A(object):
        pass
    
    a = A()
    
    def new_method(self):
        print "It's working!"
    
    setattr(A, 'new_method', new_method)
    
    a.new_method()
    


    А вообще, ковыряться во внутренностях того, с чем надо взаимодействовать как с «чёрным ящиком» — плохая идея.
    • 0
      Я не говорил о принципиальной невозможности, просто так не делается. Да и setattr здесь лишний, можете просто присвоить.

      >>> A.kuku = new_method
      >>> a.kuku()
      It's working!
      


      И потом я об открытых классах ruby, а не о системе типов python.
      • 0
        Так на этом утверждении о невозможности Вы весь питоновский пример строите, а статью начинаете с «переопределить там size».

        Если говорим о переопределении, то и пример надо бы с переопределением.

        Если говорим перегрузке функций в зависимости от типов, то у нас для этого PEP 443 есть.
        • 0
          Типу int вы ничего не присвоите. Нечто похожее на PEP443 есть прямо в тексте. Я не говорю о переопределении, я говорю о расширении среды.
        • 0
          «переопределить там size» это стандартная питоновская страшилка про ruby с открытыми классами, которую статья призвана развеять.
      • 0
        setattr не лишний, он абстрактнее простого присвоения.
  • +3
    Помню, переносил как-то Ruby-библиотеку с одной версии языка на другую. Тем, что с новым Ruby всё вдруг поламалось, наверное, никого не удивишь. Но вот зато какое «удовольствие» было искать, где же всё-таки определён один такой перегруженный метод! Нужно знать не только, в каком файле он прописан, но и в какой последовательности загружаются модули. А это значит, что чтобы понять какую-то маленькую деталь, нужно сначала осознать поток контроля во всём приложении, что довольно сложно без понимания вот таких деталей.

    А пример неудачный. В Python вы для каждого типа определяете свою функцию проверки, а в Ruby обходитесь обобщённым методом. Никто не мешает в Питоне точно также определить одну единственную обобщённую функцию и просто вызывать её как `blank(obj)`, а не `obj.blank()`.
    • 0
      Вполне удачный, покажите мне реализацию этой функции.
      • 0
        Если дословно, то так:

        def blank(obj):
            if hasattr(obj, '__iter__'):  # или любая аналогичная проверка для нужных типов
                return len(obj)
           else: 
               return not bool(obj)
        

        В зависимости от нужного вам поведения проверять можно на конкретные типы, другие атрибуты или что угодно ещё. Возможно, в Ruby есть какие-то фишки, недоступные в Python, но написать внешнюю функцию, заменающую внутренний метод, можно всегда.
        • 0
          len(obj) != 0
          

          Ну, вы поняли.
        • 0
          Да неважно, что в ruby есть некий механизм, которого нет в python. Важно что открытые классы это не зло.
          • 0
            Дело вкуса. Я говорил про пример, что он не слишком удачный.
    • 0
      По поводу порядка сборки среди исполнения, а в ruby она именно что собирается из всего дерева исходников, это в принципе меня никогда не подлавливало в реальных проектах. Есть правила как именно надо раскладывать свой код, и чего делать не стоит. Может вам просто попалась плохая библиотека.
  • +1
    Механизм-то сам по себе замечательный, но приводит к тотальному манкипатчингу при полном непонимании как и когда его правильно использовать… Впрочем такая проблема есть и с паттернами из Банды Четырех.
    • 0
      Да ни к чему он не приводит, я не понимаю о чем вы таком говорите — в моих проектах и в библиотеках которые я использую, такого нет. А хреновый код пишется хреновыми программистами, вне связи с языками.
      • 0
        Я именно про это и написал :\
        • 0
          Впрочем такая проблема есть и с

          Вы пишете про некую проблему, которой нет ;) это мне напоминает войны java vs python на предмет private и protected.
          • 0
            Инкапсуляция не нужна! Проблема существует, если вы работаете с фрилансерами/удаленщиками и прочими джуниорами, которых вы, или ваши адекватные коллеги, не взращивали на правильных ценностях. И хоть «Банда Четырех» к таковым ценностям относится — чрезмерное использование паттернов в ООП ни к месту приводит к аду.
            • 0
              Ревью необходимо при подобном образе работы. Тонны говнокода которые я видел на любых языках не имеют абсолютно никакой корреляции с его, языка, особенностями.
              • 0
                Ревью необходимо при подобном образе работы

                Абсолютно согласен.

                Тонны говнокода которые я видел на любых языках не имеют абсолютно никакой корреляции с его, языка, особенностями.

                Ха
      • 0
        Впрочем, как уже отметили выше, в Питоне совсем наговнокодить не выйдет, ибо TypeError: can't set attributes of built-in/extension type INSERT_TYPE_NAME, а в Ruby (насколько я знаю, поправьте если я неправ) переопределение аттрибутов/методов встроенных типов/классов возможно… И вот здесь и начинается АДЪ.
        • 0
          В ruby все возможно. Только ада нет.
          • 0
            Скажите, вы когда-нибудь устанавливали Redmine? :)
            Возможность переопределения аттрибутов/методов у стандартных типов/классов — потенциальная дыра в безопасности приложения.
            • 0
              Не используйте интерпретируемые языки — в них часто можно переопределить множество вещей. Не используйте интернет — там можно ходить на другой компьютер. Не используйте компьютер — там можно писать в память.

              Чем возможность дописывать в стандартные классы так опасна в сравнении с возможностью писать не в стандартные? Откуда вообще опасность? Покажете мне пример эксплуатации? ;) В rails находили много дырявостей, но открытые классы еще ни в одном случае не были причиной ужасной опасности.
              • 0
                1) Дописывание — хорошо, переписывание — зло;
                2) Опасность в переопределение методов стандартных классов заключается в возможном исполнении этих методов (привет blackhat-сообщество);
                3) Не покажу, абсолютно не знаю Руби. По маштабам — представьте переопределение escape-методов для любой ORM;

                Призываю chikey в тред ;)
                • 0
                  Ну какие еще хакеры? Зачем вы даете им писать в свой проект? Ну если NSA приглашали, то им же удобнее, правда?
                  И потому, ну что вы в самом деле, escape никогда не определялся на встроенных типах.
                  • –2
                    Есть понятие «потенциальная дырка» и возможность существования таковой отлична от нуля. Человеческий фактор и социнженерия…
  • +3
    Объясните пожалуйста, я правильно понял (не знаю ruby), что вы взяли и какому-то стандартному классу поменяли поведение? Я б за такое по рукам бил. Берешь стандартный класс, используешь его, а у него другое поведение…
    • –1
      Нет.
  • +1
    Как же, всё-таки, прекрасно, что в питоне так не делают!
    • 0
      Это абсолютно логично, что в питоне так не делают, потому что в нем делают по другому, что никоим образом не делает способ, которым это делают в ruby хоть сколько-нибудь неверным.
      • +1
        В питоне вообще ни коим образом не меняют поведение базовых объектов, а monkey patching стандартных и не очень библиотек считается не просто дурным тоном, а чем-то, за что полагается смерть через колесование. Исключения есть, но они настолько явные, что никаких вопросов не остаётся.
        Я про то, что ни одному зрелому питонщику не придёт в голову писать статью про то, как круто править чужие объекты. Потому что это не круто.
        • 0
          Я же написал — не менять, а расширять среду. Менять нельзя, все же поломается.
          • 0
            Расширение, это изменение. А теперь представь, что изменения среды разных библиотек сконфликтовали друг с другом… Это может превратиться в такой адов пипец, что своим матом ты вызовешь Watman'a.

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