1. Python / Говнокод #26751

    +3

    1. 1
    2. 2
    3. 3
    4. 4
    5. 5
    6. 6
    7. 7
    8. 8
    import traceback
    
    a = (1,2,[3,4])
    try:
        a[2] += [4,5]
    except:
        traceback.print_exc()
    print(a)

    Traceback (most recent call last):
    File "prog.py", line 5, in <module>
    a[2] += [4,5]
    TypeError: 'tuple' object does not support item assignment

    (1, 2, [3, 4, 4, 5])


    Какой бароп )))

    https://m.habr.com/ru/company/domclick/blog/506138/

    Запостил: 3_dar, 13 Июня 2020

    Комментарии (43) RSS

    • https://ideone.com/Dom8s1
      Ответить
    • Статью не читал, но так то понятно почему:
      Сначала происходит +=, а потом пытается записать в tuple, но объект уже изменён.
      Ответить
      • Итак, списки мутабельны, поэтому к [3, 4] можно добавить [4, 5] и получить [3, 4, 4, 5].

        Кортежи же (туплы) немутабельны, поэтому изменять их нельзя. Однако, список нельзя хранить в кортеже по значению, вместо него хранится ссылка. a[2] — это именно ссылка на список, а не сам список.

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

        Всё верно?
        Ответить
        • Я так думал код эквивалентен следующему:
          x = a[2]
          x += [4, 5]
          a[2] = x
          Ответить
          • Скажу больше: третья строка ничего не делает, потому что в x уже лежит ссылка на список. При изменении списка ссылка на него не меняется.

            Точнее, она генерирует исключение, потому что кортежи иммутабельны.

            Вот реальный пример без третьей строки:
            https://ideone.com/4sxUjo
            Ответить
        • Да.
          «+=» в Путухе работает за счёт магического метода __iadd__(self, ...), который для списка делает «self.extend(...)» и возвращает self:
          >>> a = [1, 2, 3]
          >>> id(a)
          2056768382336
          >>> id(a.__iadd__([4, 5, 6]))
          2056768382336

          Далее интерпретатор пытается присвоить «новое» значение элементу кортежа и ожидаемо обламывается. А поскольку транзакций не завезли — исключение не откатывает изменения в списке.
          В общем-то, налицо багор в рахитектуре, но то, как отбитые путухи в статье пытаются агрессивно отстоять эту хуйню («всё логично», «Но не странно ну вот вообще») — выглядит очень смешно. Один особенный ещё и сишку приплёл, лол.
          Ответить
          • А была бы транзитивная иммутабельность как в D, то такой бы херни не возникло
            Ответить
            • А я за крестовый «const». Очень удобная штука, как ни странно.
              Ответить
              • В D и const есть
                Ответить
                • Я именно за крестовый «const».
                  class Huj {
                  public:
                      Huj() = default;
                      ~Huj() = default;
                  
                      const std::vector<Cell> & getCells() const { return cells; }
                  private:
                      std::vector<Cell> cells = {};
                  };


                  getCells() возвращает ссылку на вектор, но при этом попортить внутренности хуя нельзя: ссылка константна, и std::vector::operator[]() тоже возвращает константную ссылку.
                  Для такого же уровня надёжности в каком-нибудь путухе, например, придётся возвращать копию массива, как и в ЙАЖЕ (впрочем, в последней, вроде как, можно какими-то ебанутыми кастами провернуть нечто похожее).
                  Ответить
              • Жаль const& не дружат с многопоточностью
                Ответить
                • показать все, что скрытоvanished
                  Ответить
                  • Ну если никто не пишет в те данные, которые она читает - то да.
                    Ответить
                    • Но immutable более жесткая гарантия. Позволяет лишь одну инитиализацию.
                      Ответить
                  • Нет. Помимо замечания Борманда, есть ещё много способов выстрелить себе в ногу (это же кресты, в конце-концов), например (из очевидного и гарантированно работающего):
                    struct X {
                        // ...
                        int foo(...) const {
                            return x++;
                        }
                    
                    private:
                        // ...
                        mutable int x = 0;
                    };


                    Или вообще как-нибудь так:
                    struct X {
                        // ...
                        int foo(...) const {
                            return const_cast<int&>(x)++;
                        }
                    
                    private:
                        // ...
                        int x = 0;
                    };

                    Это будет работать, если сам объект изначально non-const. А если он объявлен как const — UB.

                    UPD: Или ещё из очевидного:
                    int foo(...) const {
                        static int x = 0;
                        return x++;
                    }
                    Ответить
                    • показать все, что скрытоvanished
                      Ответить
                      • UPD-пример. Или ещё:
                        int x = 0;
                        struct X {
                            int foo1() const
                            {
                                return x++;
                            }
                        };


                        Как и сказал Борманд, если функция может читать любые данные, в которые кто-то (включая её саму) может что-то записать — она не потокобезопасна (ну, по-умолчанию, без синхронизации).
                        Ответить
                        • показать все, что скрытоvanished
                          Ответить
                          • Так const — это вообще ни разу не про потокобезопасность.
                            Ответить
                            • показать все, что скрытоvanished
                              Ответить
                              • показать все, что скрытоvanished
                                Ответить
                              • Не было бы. Другие то методы могут быть не const. И даже если тебе передали конст ссылку на объект, это ещё не означает что у кого-то другого нет полноценной ссылки.

                                Т.е. это банальная защита от дурака чтобы случайно контракт не нарушить и не писнуть куда не надо, не более того.
                                Ответить
                                • показать все, что скрытоvanished
                                  Ответить
                                  • void foo(const string & str) const
                                    {
                                        lock_print();
                                        print_string(str);  // print_string(const string &) const noexcept
                                        unlock_print();
                                    }

                                    Казалось бы, выгледит потокобезопасно, но вот хуй:
                                    // global
                                    string str = "Hello World";
                                    
                                    // thread 1
                                    foo(str);
                                    
                                    // thread 2 
                                    str = "ByeBye Earth";
                                    
                                    // Вывести может, например, "Hello Earth", или вообще свалиться нахуй
                                    // если в строке произойдут какие-нибудь реаллокации


                                    Чтобы тип ссылки/метода обеспечивал потокобезопасность, компилятор должен гарантировать, что в любой момент времени во всех потоках на один и тот же объект существует либо ровно одна мутабельная ссылка, либо неограниченное количество иммутабельных. Казалось бы, при чём тут ржавчина?..
                                    Ответить
                                    • показать все, что скрытоvanished
                                      Ответить
                                      • Нет, не похуй.

                                        Другие то методы могут быть не const. И даже если тебе передали конст ссылку на объект, это ещё не означает что у кого-то другого нет полноценной ссылки.

                                        Т.е. это банальная защита от дурака чтобы случайно контракт не нарушить и не писнуть куда не надо, не более того.
                                        Ответить
                    • Да блин. В этих примерах пишут в те данные, которые читает функция. И похуй, что пишет она сама.
                      Ответить
              • показать все, что скрытоvanished
                Ответить
                • @жабоебы
                  Ах, как же омерзительны порой людские пороки... Один страшнее другого.
                  Ответить
                • Странно, что в крестах сделали что-то удобное.
                  Ответить
    • В комментариях, кстати, полезную ссылку вкинули:
      https://docs.python.org/3/faq/programming.html#faq-augmented-assignment-tuple-error
      Ответить
    • <?php
      
      $a = unserialize('O:8:"stdClass":3:{i:0;i:1;i:1;i:2;i:2;a:2:{i:0;i:3;i:1;i:4;}}');
      
      array_push($a->{2}, 4,5);
      
      var_dump($a);


      https://ideone.com/IUfM9r
      Ответить
      • показать все, что скрытоvanished
        Ответить
        • Затем, что нормальное программирование меня не интересует.
          Ответить
        • А теперь серьёзнее. Гусары, не ржать!

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

          Оказалось, что не так просто создать класс с нумерными полями. На $0, ${0}, ${'0'} парсер обламывается, хотя обычные переменные (не поля класса) можно сделать нумерными. При этом через десериализацию нумерные поля легко создаются.

          Есть вариант через каст из массива ((object)[1,2,[3,4]]), но тогда не работает функция array_push, потому что [3,4] в этом случае почему-то не остаётся массивом, а конвертируется в экземпляр класса, у которого какие-то методы массива не определены.

          А с именованными полями всё работает. В «PHP7» даже анонимные классы завезли:
          <?php
          
          $a = new class { 
              public $a = 1;
              public $b = 2;
              public $c = [3,4];
          };
          
          
          array_push($a->c, 4,5);
          
          var_dump($a);
          Ответить
          • показать все, что скрытоvanished
            Ответить
            • Как вариант можно ещё создать класс, который реализует интерфейс ArrayAccess, тогда можно получить туплу фиксированного размера с нужными свойствами. Но тогда придётся писать много бройлерплейта. В описании класса получится типичная «Йажа».
              Ответить
          • > [3,4] в этом случае почему-то не остаётся массивом

            Так создай stdClass через каст, а потом присвой массив 2-му элементу.
            Ответить
            • Сразу видно, что ты не оператор шаблонизатора. Ты умный.
              Ответить
              • Оператор шаблонизатора лебедевской артели?
                Ответить

    Добавить комментарий