Страницы

Поиск по вопросам

Показаны сообщения с ярлыком yield. Показать все сообщения
Показаны сообщения с ярлыком yield. Показать все сообщения

четверг, 2 января 2020 г.

Генератор yield

#python #yield


Пример из изучаемой мною книжки

def fibonacci_generator():
    a = b = 1
    while True:
        yield a 
        a, b = b, a + b
        print(a, b) #поможет лучше понять работу
fib = fibonacci_generator()

for i in fib:
    if i > 100:
        break
    else:
        print('Generated:', i)


Что в данном коде выполняет функция yield? В книге написано - "Она [генераторная
инструкция], начинается с ключевого слова yield и определяет объект-генератор (то есть
переменную a?), который возвращается оператору (print?), вызвавшему функцию. Когда
генераторная инструкция исполняется, состояние объекта генератора "замораживается"
и сохраняется. Объект, возвращаемый генераторной инструкцией, может быть присвоен переменной
(это мы сделали на 7 строке кода?)."
Но я так и не понял, как это отражается в результате кода?

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

Generated: 1
1 2
Generated: 1
2 3
Generated: 2
3 5
Generated: 3
5 8
Generated: 5
8 13
...


Что после операций a, b = b, a + b становится содержимым функции fibonacci_generator?
Результат суммы a и b? Переменная а на второй итерации равна 2, потому что "при этом
она сохраняет состояние своего последнего вызова и при следующем вызове продолжает
работу с той же точки."? Почему, к примеру, на первой итерации, вывод переменной b
отображает 2, ведь сумма а +  b просто какое-то, ничему не присвоенное выражение?
    


Ответы

Ответ 1



yield определяет объект-генератор (то есть переменную a?) Нет, под объектом генератором подразумевается вся функция целиком. Она преобразуется в генератор, если в её теле присутствует инструкция yield. Переменная a — это то, что будет генерироваться и возвращаться. который возвращается оператору (print?) Нет, генератор в Вашем коде возвращается и присваивается переменной fib: fib = fibonacci_generator() print(type(fib)) # Объект, возвращаемый генераторной инструкцией, может быть присвоен переменной (это мы сделали на 7 строке кода?) Нет, в 7 строке кода Вы возвращаете новое сгенерированное значение и замораживаете генератор, а присваивание этого значения переменной неявно происходит в 9 строке кода: for i in fib: ... Инструкция for на каждой итерации будет присваивать i какое-то значение. Для этого она будет обращаться к функции (через методы итерирования). Вы вручную можете получать новые значения из генератора, используя функцию next: i = next(fib) Попробуйте отказаться от цикла for и использовать только инструкцию выше, чтобы лучше понять, как генератор возвращает новое значение. a, b = b, a + b Это сокращённая запись кортежного присваивания: (a, b) = (b, a + b) Элементы из левого кортежа будут сопоставлены с элементами из правого (количество должно быть одинаково) и им будут присвоены соответствующие значения.

Ответ 2



a, b = c, d это почти то же самое, что a = c b = d только вычисляется не последовательно, а справа налево. Чтобы поменять местами два значения можно написать a, b = b, a У тебя почти то же самое, но вместо второй переменной сумма: a, b = b, a + b

Ответ 3



Генераторы это очень интересная штука. https://docs.python.org/3/reference/expressions.html#generator-expressions Вот документация. Простым языком: yield - это выражение, которое используется при создании функции-генераторы или асинхронного генератора. Еще проще: генератор - это конструкция, в которой реализован протокол итератора, т.е. любой генератор = итерируемая последовательность, которую можно перебирать в цикле, как списки, только значения она возвращает вызовом метода next(), значит - не сразу, а последовательно, за каждое обращение.

суббота, 21 декабря 2019 г.

Yield и потоки и все все все

#c_sharp #многопоточность #yield


Оператор yield

class UserCollection
{
    public static IEnumerable Power()
    {
        yield return "Hello world!";
    }
}


Оператор yield из .Net Reflector - перенесенный в рялии C# и Visual Studio

class UserCollection
{
    public static IEnumerable Power()
    {
        return new ClassPower(-2);
    }

    private sealed class ClassPower : IEnumerable,  IEnumerator,
IEnumerator, IDisposable
    {
        // Поля.
        private int state;
        private object current;
        private int initialThreadId;

        // Конструктор.
        public ClassPower(int state)
        {
            this.state = state;
            this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        //private bool IEnumerator.MoveNext() // Так в Рефлекторе
        bool IEnumerator.MoveNext()
        {
            switch (this.state)
            {
                case 0:
                    this.state = -1;
                    this.current = "Hello world!";
                    this.state = 1;
                    return true;

                case 1:
                    this.state = -1;
                    break;
            }
            return false;
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            if ((Thread.CurrentThread.ManagedThreadId == this.initialThreadId) &&
(this.state == -2))
            {
               this.state = 0;
               return this;
            }
            return new UserCollection.ClassPower(0);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            // Так в Рефлекторе 
            //return this.System.Collections.Generic.IEnumerable.GetEnumerator(); 

            return (this as IEnumerable).GetEnumerator();
        }

        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

        void IDisposable.Dispose()
        {
        }

        // Свойства.
        object IEnumerator.Current
        {
            get
            {
                return this.current;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return this.current;
            }
        }
    }
}


Меня интересует строка и как ее комментирует автор

this.initialThreadId = Thread.CurrentThread.ManagedThreadId;


данная строка свидетельствует о синхронизации и доступа к этой коллекции как к разделяемому
ресурсу.
Почему именно при работе с yield коллекция воспринимается как разделяемый ресурс
и нужна вообще в принципе работа с потоками?

См. рисунок как я понял работу оператора yield
и может т.к это в эту коробку все операторы yield помещают свое значение , то может
поэтому нужна работа с потоками? 

    


Ответы

Ответ 1



Обратите внимание на функцию IEnumerable.GetEnumerator(). Данный объект реализует как IEnumerable, так и IEnumerator. Состояние -2 означает, что объект «свежий», только что вернулся из вызова функции Power, и его энумерация ещё не начиналась. В этой ситуации, когда тут же вызывается GetEnumerator в том же потоке (а это происходит в подавляющем большинстве случаев: например, при вызове foreach (var x in Power())), то из соображений эффективности можно вернуть этот же объект (т. к. он служит и энумератором тоже). Но если энумерация уже прошла, внутреннее состояние объекта может быть испорчено, для этого случая возвращают новый объект. Точно так же если энумерация производится в другом потоке, то чтобы избежать необходимости синхронизации, лучше создать новый объект. Остановимся специально на последнем пункте. Если вдруг IEnumerable уйдёт в другой поток, и оттуда буден вызван метод GetEnumerator(), и одновременно в основном потоке будет тоже вызван этот же метод, то без проверки thread id может случиться так, что оба потока пройдут одновременно проверку this.state == -2, и получат один и тот же объект! В результате при энумерации они будут мешать друг другу. В этом случае мы даём «выиграть» тому же потоку, который создал объект. Таким образом, это специальная оптимизация для обычного случая (выделение только одного объекта); «необычные» случаи использования проходят более сложным путём. Подробнее по теме: Jon Skeet, C# in Depth. Iterator block implementation details: auto-generated state machines.

суббота, 30 ноября 2019 г.

Допустимо ли использовать yield return внутри блокировки?

#c_sharp #lock #yield


Есть два вопроса к коду, приведенному ниже:


Допустимо ли использовать yield return внутри блокировки?
Что произойдёт с блокировкой, когда мы будет крутить метод Get() в цикле foreach?

public IEnumerable Get()
{
  _lock.EnterReadLock();
  try
  {
    foreach (var item in _dictionary)
    {
      yield return new SomeObject(item.Key, item.Value);
    }
  }
  finally
  {
    _lock.ExitReadLock();
  }
}

private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();


    


Ответы

Ответ 1



Смотрите, это возможно на уровне языка — компилятор не имеет возможности проверить. Но делать так не рекомендуется. Общее правило такое: следует избегать вызова чужого кода под блокировкой. Допустим, вы нарушили правило и выполнили yield под блокировкой. Клиентский код может при этом сделать вызов в другой поток (если у нас нету многопоточности, то блокировки нам всё равно не нужны, правильно?). foreach (SomeObject o in Get()) { Dispatcher.Invoke(() => Process(o)); } Если в методе Process будет браться та же блокировка, у вас будет взаимная блокировка, и код зависнет: Process будет дожидаться отпускания блокировки, а блокирующий код — возвращения из yield (то есть, следующей итерации цикла).

Ответ 2



Ограниченно допустимо. Когда вы такую последовательность будете обходить в цикле foreach - то перед входом в цикл блокировка будет взята, а после выхода - освобождена. Также блокировка будет освобождена при любом прерывании цикла. Однако, использование IEnumerable не ограничивается простыми циклами. При использовании блокировок всегда важно небольшое время удержания блокировки - а тут вы его растягиваете на не зависящее от вас время! Так можно легко и до взаимоблокировки доиграться. Обычно лучше всего под блокировкой получить копию данных, и работать в дальнейшем уже с ней: public IEnumerable Get() { IEnumerable> d; _lock.EnterReadLock(); try { d = _dictionary.ToList(); } finally { _lock.ExitReadLock(); } foreach (var item in d) { yield return new SomeObject(item.Key, item.Value); } } Или даже вот так: public IEnumerable Get() { _lock.EnterReadLock(); try { return _dictionary.Select(item => new SomeObject(item.Key, item.Value)).ToList(); } finally { _lock.ExitReadLock(); } }

понедельник, 12 ноября 2018 г.

Yield и потоки и все все все

Оператор yield
class UserCollection { public static IEnumerable Power() { yield return "Hello world!"; } }
Оператор yield из .Net Reflector - перенесенный в рялии C# и Visual Studio
class UserCollection { public static IEnumerable Power() { return new ClassPower(-2); }
private sealed class ClassPower : IEnumerable, IEnumerator, IEnumerator, IDisposable { // Поля. private int state; private object current; private int initialThreadId;
// Конструктор. public ClassPower(int state) { this.state = state; this.initialThreadId = Thread.CurrentThread.ManagedThreadId; }
//private bool IEnumerator.MoveNext() // Так в Рефлекторе bool IEnumerator.MoveNext() { switch (this.state) { case 0: this.state = -1; this.current = "Hello world!"; this.state = 1; return true;
case 1: this.state = -1; break; } return false; }
IEnumerator IEnumerable.GetEnumerator() { if ((Thread.CurrentThread.ManagedThreadId == this.initialThreadId) && (this.state == -2)) { this.state = 0; return this; } return new UserCollection.ClassPower(0); }
IEnumerator IEnumerable.GetEnumerator() { // Так в Рефлекторе //return this.System.Collections.Generic.IEnumerable.GetEnumerator();
return (this as IEnumerable).GetEnumerator(); }
void IEnumerator.Reset() { throw new NotSupportedException(); }
void IDisposable.Dispose() { }
// Свойства. object IEnumerator.Current { get { return this.current; } }
object IEnumerator.Current { get { return this.current; } } } }
Меня интересует строка и как ее комментирует автор
this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
данная строка свидетельствует о синхронизации и доступа к этой коллекции как к разделяемому ресурсу. Почему именно при работе с yield коллекция воспринимается как разделяемый ресурс и нужна вообще в принципе работа с потоками?
См. рисунок как я понял работу оператора yield и может т.к это в эту коробку все операторы yield помещают свое значение , то может поэтому нужна работа с потоками?


Ответ

Обратите внимание на функцию IEnumerable.GetEnumerator()
Данный объект реализует как IEnumerable, так и IEnumerator. Состояние -2 означает, что объект «свежий», только что вернулся из вызова функции Power, и его энумерация ещё не начиналась. В этой ситуации, когда тут же вызывается GetEnumerator в том же потоке (а это происходит в подавляющем большинстве случаев: например, при вызове foreach (var x in Power())), то из соображений эффективности можно вернуть этот же объект (т. к. он служит и энумератором тоже).
Но если энумерация уже прошла, внутреннее состояние объекта может быть испорчено, для этого случая возвращают новый объект. Точно так же если энумерация производится в другом потоке, то чтобы избежать необходимости синхронизации, лучше создать новый объект.
Остановимся специально на последнем пункте. Если вдруг IEnumerable уйдёт в другой поток, и оттуда буден вызван метод GetEnumerator(), и одновременно в основном потоке будет тоже вызван этот же метод, то без проверки thread id может случиться так, что оба потока пройдут одновременно проверку this.state == -2, и получат один и тот же объект! В результате при энумерации они будут мешать друг другу. В этом случае мы даём «выиграть» тому же потоку, который создал объект.
Таким образом, это специальная оптимизация для обычного случая (выделение только одного объекта); «необычные» случаи использования проходят более сложным путём.

Подробнее по теме: Jon Skeet, C# in Depth. Iterator block implementation details: auto-generated state machines

среда, 3 октября 2018 г.

Допустимо ли использовать yield return внутри блокировки?

Есть два вопроса к коду, приведенному ниже:
Допустимо ли использовать yield return внутри блокировки? Что произойдёт с блокировкой, когда мы будет крутить метод Get() в цикле foreach?
public IEnumerable Get() { _lock.EnterReadLock(); try { foreach (var item in _dictionary) { yield return new SomeObject(item.Key, item.Value); } } finally { _lock.ExitReadLock(); } }
private ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();


Ответ

Смотрите, это возможно на уровне языка — компилятор не имеет возможности проверить. Но делать так не рекомендуется.
Общее правило такое: следует избегать вызова чужого кода под блокировкой. Допустим, вы нарушили правило и выполнили yield под блокировкой. Клиентский код может при этом сделать вызов в другой поток (если у нас нету многопоточности, то блокировки нам всё равно не нужны, правильно?).
foreach (SomeObject o in Get()) { Dispatcher.Invoke(() => Process(o)); }
Если в методе Process будет браться та же блокировка, у вас будет взаимная блокировка, и код зависнет: Process будет дожидаться отпускания блокировки, а блокирующий код — возвращения из yield (то есть, следующей итерации цикла).