Chained assignment python что такое

How do chained assignments work?

A quote from something:

Based on my understanding, they should be same because somefunction can only return exactly one value.

6 Answers 6

Note the order. The leftmost target is assigned first. (A similar expression in C may assign in the opposite order.) From the docs on Python assignment:

. assigns the single resulting object to each of the target lists, from left to right.

Disassembly shows this:

CAUTION: the same object is always assigned to each target. So as @Wilduck and @andronikus point out, you probably never want this:

In the above case x and y refer to the same list. Because lists are mutable, appending to x would seem to affect y.

Now you have two names referring to two distinct empty lists.

Chained assignment python что такое. Смотреть фото Chained assignment python что такое. Смотреть картинку Chained assignment python что такое. Картинка про Chained assignment python что такое. Фото Chained assignment python что такое

They will not necessarily work the same if somefunction returns a mutable value. Consider:

What if somefunction() returns different values each time it is called?

It would result in the same only if the function has no side-effects and returns a singleton in a deterministic manner (given its inputs).

Note that the result would be the same, but the process to achieve the result would not:

The content of x and y variables would be the same, but the instruction x = y = slow_is_computer_on() would last 10 seconds, and its counterpart x = slow_is_computer_on() ; y = slow_is_computer_on() would last 20 seconds.

It would be almost the same if the function has no side-effects and returns an immutable in a deterministic manner (given its inputs).

Note that the same catches explained in previous section applies.

Why I say almost? Because of this:

Ok, using is is something strange, but this illustrates that the return is not the same. This is important for the mutable case:

It is dangerous and may lead to bugs if the function returns a mutable

This has also been answered in this question. For the sake of completeness, I replay the argument:

Because in that scenario x and y are the same object, doing an operation like x.append(42) whould mean that both x and y hold a reference to a list which now has 4 elements.

It would not be the same if the function has side-effects

Considering a print a side-effect (which I find valid, but other examples may be used instead):

Instead of a print, it may be a more complex or more subtle side-effect, but the fact remains: the method is called once or twice and that may lead to different behaviour.

It would not be the same if the function is non-deterministic given its inputs

Maybe a simple random method:

But, things related to clock, global counters, system stuff, etc. is sensible to being non-deterministic given the input, and in those cases the value of x and y may diverge.

somefunction will be called twice instead of once.

Even if it returns the same result each time, this will be a noticeable if it takes a minute to return a result! Or if it has a side effect e.g. asking the user for his password.

As already stated by Bob Stein the order of assignment is important; look at the very interesting following case:

Conclusion

This example is more complicated to understand than other ones in other answers, but on the other hand, you can see much quicker that

is not the same as

because the second one will raise an exception if L is not previously defined while the first one will always work.

Источник

Pandas: Chained assignments [duplicate]

I get the SettingWithCopyWarning warnings for the following lines of codes, where data is a Panda dataframe and amount is a column (Series) name in that dataframe:

Looking at this code, is it obvious that I am doing something suboptimal? If so, can you let me know the replacement code lines?

I am aware of the below warning and like to think that the warnings in my case are false positives:

The chained assignment warnings / exceptions are aiming to inform the user of a possibly invalid assignment. There may be false positives; situations where a chained assignment is inadvertantly reported.

EDIT : the code leading to the first copy warning error.

Chained assignment python что такое. Смотреть фото Chained assignment python что такое. Смотреть картинку Chained assignment python что такое. Картинка про Chained assignment python что такое. Фото Chained assignment python что такое

1 Answer 1

The point of the SettingWithCopy is to warn the user that you may be doing something that will not update the original data frame as one might expect.

Here, data is a dataframe, possibly of a single dtype (or not). You are then taking a reference to this data[‘amount’] which is a Series, and updating it. This probably works in your case because you are returning the same dtype of data as existed.

However it could create a copy which updates a copy of data[‘amount’] which you would not see; Then you would be wondering why it is not updating.

Pandas returns a copy of an object in almost all method calls. The inplace operations are a convience operation which work, but in general are not clear that data is being modified and could potentially work on copies.

Much more clear to do this:

One further plus to working on copies. You can chain operations, this is not possible with inplace ones.

And just an FYI. inplace operations are neither faster nor more memory efficient. my2c they should be banned. But too late on that API.

You can of course turn this off:

Pandas runs with the entire test suite with this set to raise (so we know if chaining is happening) on, FYI.

Источник

Трюки Pandas от RealPython

Chained assignment python что такое. Смотреть фото Chained assignment python что такое. Смотреть картинку Chained assignment python что такое. Картинка про Chained assignment python что такое. Фото Chained assignment python что такое

К старту флагманского курса по Data Science делимся сокращённым переводом из блога RealPython о трюках с Pandas, материал начинается с конфигурирования запуска библиотеки и заканчиваются примерами работы с операторами и их приоритетом. Затрагивается тема экономии памяти, сжатие фреймов, интроспекция GroupBy через итерацию и другие темы. Подробности, как всегда, под катом.

1. Параметры запуска интерпретатора

Запустив сеанс интерпретатора, вы увидите, что сценарий запуска выполнен и Pandas автоматически импортируется с вашим набором опций:

Воспользуемся данными abalone в репозитории машинного обучения UCI, чтобы продемонстрировать заданное в файле запуска форматирование. Сократим данные до 14 строк с точностью до 4 цифр для чисел с плавающей точкой:

Позже вы увидите этот набор данных и в других примерах.

2. Игрушечные cтруктуры данных с помощью модуля тестирования Pandas

В модуле Pandas testing скрыт ряд удобных функций для быстрого построения квазиреалистичных Series и фреймов данных:

Их около 30, полный список можно увидеть, вызвав dir() на объекте модуля. Вот несколько вариантов:

Они полезны для бенчмаркинга, тестирования утверждений и экспериментов с не очень хорошо знакомыми методами Pandas.

3. Используйте преимущества методов доступа

Возможно, вы слышали о термине акcессор, который чем-то напоминает геттер (хотя геттеры и сеттеры используются в Python нечасто). В нашей статье будем называть аксессором свойство, которое служит интерфейсом для дополнительных методов. В Series [на момент написания оригинальной статьи] их три, сегодня их 4:

Да, приведённое выше определение многозначно, поэтому до обсуждения внутреннего устройства посмотрим на примеры.

.cat — для категориальных данных;

.str — для строковых (объектных) данных;

.dt — для данных, подобных времени.

4. Создание индекса времени даты из столбцов компонентов

Наконец, вы можете отказаться от старых отдельных столбцов и преобразовать их в Series:

Интуитивно суть передачи фрейма данных в том, что DataFrame похож на словарь Python, где имена столбцов — это ключи, а отдельные столбцы (Series) — значения словаря. Поэтому pd.to_datetime (df[datecols].to_dict (orient=’list’)) здесь также будет работать.

5. Использование категориальных данных для экономии времени и места

А что если бы мы могли взять перечисленные выше уникальные цвета и отобразить каждый из них в занимающее меньше места целое число? Наивная реализация:

Другой способ сделать то же самое в Pandas — pd.factorize (colors) :

Так или иначе объект кодируется как перечислимый тип (категориальная переменная).

«Использование памяти Categorical пропорционально количеству категорий плюс длина данных. Напротив, object dtype — это константа, умноженная на длину данных» (Источник).

В colors выше есть соотношение двух значений на каждое уникальное значение, то есть на категорию:

Экономия памяти от преобразования в Categorical хороша, но невелика:

Но, если у вас будет, например, много демографических данных, где мало уникальных значений, объём требуемой памяти уменьшится в 10 раз:

Можно воспроизвести что-то похожее на пример выше, который делался вручную:

Всё, что вам нужно сделать, чтобы в точности повторить предыдущий ручной вывод, — это изменить порядок кодов:

Обратите внимание, что dtype — это int8 NumPy, 8-битное знаковое целое, которое может принимать значения от −127 до 128. Для представления значения в памяти требуется только один байт. 64-битные знаковые int были бы излишеством с точки зрения потребления памяти. Грубый пример привёл к данным int64 по умолчанию, тогда как Pandas достаточно умна, чтобы привести категориальные данные к минимально возможному числовому dtype.

6. Интроспекция объектов Groupby через итерацию

При вызове df.groupby (‘x’) результирующие объекты Pandas groupby могут быть немного непрозрачными. Этот объект инстанцируется лениво и сам по себе не имеет никакого осмысленного представления. Продемонстрируем это на наборе данных abalone из первого примера:

7. Используйте этот трюк с отображением для бининга

Представьте: есть Series и соответствующая «таблица сопоставления», где каждое значение принадлежит к многочленной группе или вообще не принадлежит ни одной группе:

Другими словами, вам нужно сопоставить countries со следующим результатом:

Код значительно быстрее, чем вложенный цикл Python по группам для каждой страны:

Задача — сопоставить каждую группу в groups целому числу. Однако Series.map() не распознаёт ‘ab’ — ему нужна разбитая версия, где каждый символ из каждой группы отображён на целое число. Это делается охватом словаря:

Этот словарь может передаваться в s.map() для сопоставления или «перевода» его значений в соответствующие индексы групп.

8. Загрузка данных из буфера обмена

Это позволяет копировать структурированный текст непосредственно в DataFrame или Series. В Excel данные будут выглядеть примерно так:

Chained assignment python что такое. Смотреть фото Chained assignment python что такое. Смотреть картинку Chained assignment python что такое. Картинка про Chained assignment python что такое. Фото Chained assignment python что такое

Его текстовое представление может выглядеть так:

Просто выделите и скопируйте текст выше и вызовите pd.read_clipboard() :

9. Запись объектов Pandas в сжатый формат

Этот короткий пример завершает список. Начиная с версии Pandas 0.21.0 вы можете записывать объекты Pandas непосредственно для сжатия gzip, bz2, zip или xz, а не хранить несжатый файл в памяти и преобразовывать его. Вот пример, использующий данные abalone из первого трюка:

Коэффициент разницы в размерах равен 11,6:

Data Science — это не только статистика, но и написание кода, который с учётом работы с большими данными должен быть эффективным. В этом одна из причин высокой зарплаты специалиста в науке о данных, стать которым мы можем помочь вам на нашем курсе. Также вы можете узнать, как начать карьеру аналитика или инженера данных, начать с нуля или прокачаться в других направлениях, например, в Fullstack-разработке на Python:

Chained assignment python что такое. Смотреть фото Chained assignment python что такое. Смотреть картинку Chained assignment python что такое. Картинка про Chained assignment python что такое. Фото Chained assignment python что такое

Data Science и Machine Learning

Источник

Часто задаваемые вопросы

Некоторые не совсем очевидные вещи, с которыми сталкиваются начинающие программисты Python.

Почему я получаю исключение UnboundLocalError, хотя переменная имеет значение?

Может показаться неожиданным получить UnboundLocalError в ранее работающем коде, в который добавили операцию присваивания где-то внутри функции.

работает, но следующий код:

приводит к UnboundLocalError:

Это происходит потому, что, когда вы делаете присваивание переменной в области видимости, она становится локальной в этой области и скрывает другие переменные с таким же именем во внешних областях.

Когда последняя инструкция в foo присваивает новое значение переменной x, компилятор решает, что это локальная переменная. Следовательно, когда более ранний print пытается напечатать неинициализированную переменную, возникает ошибка.

В примере выше можно получить доступ к переменной, объявив её глобальной:

Это явное объявление требуется для того, чтобы напомнить вам, что (в отличие от внешне аналогичной ситуации с переменными класса и экземпляра), вы на самом деле, изменяете значение переменной во внешней области видимости:

Вы можете сделать подобную вещь во вложенной области видимости использованием ключевого слова nonlocal:

Каковы правила для глобальных и локальных переменных в Python?

В Python, переменные, на которые только ссылаются внутри функции, считаются глобальными. Если переменной присваивается новое значение где-либо в теле функции, считается, что она локальная, и, если вам нужно, то нужно явно указывать её глобальной.

Хотя это немного удивительно на первый взгляд, это легко объяснимо. С одной стороны, требование global для присваиваемых переменных предотвращает непреднамеренные побочные эффекты в bar. С другой стороны, если global был обязательным для всех глобальных ссылок, вы бы использовали global все время. Вы должны были бы объявить как глобальную каждую ссылку на встроенную функцию или компонент импортируемого модуля.

Почему анонимные функции (lambda), определенные в цикле с разными значениями, возвращают один и тот же результат?

Например, вы написали следующий код:

Чтобы избежать подобного, необходимо сохранять значения переменных локально:

Здесь, n=x создаёт локальную для функции переменную n и вычисляется в момент определения функции:

Как организовать совместный доступ к глобальным переменным для нескольких модулей?

По тем же соображениям, модули можно использовать как основу для имплементации синглтона.

Как правильнее использовать импортирование?

В общих случаях не используйте from modulename import *. Это засоряет пространство имён того, кто импортирует. Некоторые люди избегают этой идиомы даже для тех немногих модулей, которые были спроектированны, чтобы так импортироваться. Это такие модули как Tkinter и threading.

Импортируйте модули в начале файла. Это отвечает на вопрос, какие модули требует Ваш код и находится ли имя модуля в области видимости. Запись по одному импорту на строку упрощает добавление и удаление операторов импорта, но множественный импорт будет занимать меньше места на экране.

Хорошая практика, если Вы импортируете модули в следующем порядке:

Иногда бывает необходимо поместить импорт в функцию или класс, чтобы избежать проблем с циклическим импортом. Gordon McMillan советует:

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

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

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

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

Почему значения по умолчанию разделяются между объектами?

Этот тип ошибки часто встречается среди начинающих. Предположим, функция:

В первый раз, когда вы вызываете функцию, mydict содержит одно значение. Второй раз, mydict содержит 2 элемента, поскольку, когда foo() начинает выполняться, mydict уже содержит элемент.

Часто ожидается, что вызов функции создаёт новые объекты для значений по умолчанию. Но это не так. Значения по умолчанию создаются лишь однажды, когда функция определяется. Если этот объект изменяется, как словарь в нашем примере, последующие вызовы функции будут использовать изменённый объект.

По определению, неизменяемые объекты (числа, строки, кортежи и None), безопасны при изменении. Изменение изменяемых объектов, таких как словари, списки, и экземпляры пользовательских классов может привести к неожиданным последствиям.

Поэтому, хорошей практикой является не использовать изменяемые объекты в качестве значений по умолчанию. Вместо этого, используйте None и внутри функции, проверяйте аргумент на None и создавайте новый список/словарь. Например, не пишите:

Как передать опциональные или именованные параметры из одной функции в другую?

Получить такие параметры можно с помощью спецификаторов * и ** в списке аргументов функции; они возвращают кортеж позиционных аргументов и словарь именованых параметров. После этого Вы можете передать их в другую функцию, используя в её вызове * и **:

Почему изменение списка ‘y’ изменяет также список ‘x’?

Если вы написали код:

вы, возможно, будете удивлены тому, что добавление в y изменяет также и x.

Два факта приводят к такому результату:

После вызова append, содержимое объекта было изменено с [] на [10]. Поскольку x и y ссылаются на один и тот же объект, использование любого из них даёт нам [10].

Если мы используем неизменяемые объекты:

мы можем видеть, что x и y больше не равны, поскольку числа неизменяемы, и x = x + 1 не изменяет число 5 путем увеличения. Вместо этого, создаётся новый объект 6 и присваивается переменной x (то есть, изменяется то, на какой объект ссылается x). После этого у нас 2 объекта (6 и 5) и 2 переменные, которые на них ссылаются.

Некоторые операции (например y.append(10) и y.sort()) изменяют объект, в то время, как внешне похожие операции (например y = y + [10] и sorted(y)) создают новый объект. Вообще в Python (и во всех случаях в стандартной библиотеке), метод, который изменяет объект, возвращает None, чтобы помочь избежать ошибок. Поэтому, если вы написали

думая, что это даст вам отсортированную копию y, вы вместо этого получите None, что скорее всего приведёт к легко диагностируемой ошибке.

Однако, существует один класс операций, где одна и та же операция ведёт себя по-разному с различными типами: расширенные операторы присваивания. Например, += изменяет списки, но не кортежи или числа (a_list += [1, 2, 3] эквивалентно a_list.extend([1, 2, 3])) и изменяет список, в то время, как some_tuple += (1, 2, 3) и some_int += 1 создают новый объект.

Если вы хотите знать, ссылаются ли 2 переменные на один объект или нет, вы можете использовать оператор is, или встроенную функцию id.

Как создавать функции более высокого порядка?

Есть два пути: использовать вложенные функции или вызываемые объекты. Например, с использованием вложенных функций:

Использование вызываемого объекта:

даёт функцию, что (к примеру) taxes(10e6) == 0.3 * 10e6 + 2.

Объект может сохранять свое состояние для нескольких вызовов:

Здесь inc, dec, reset выступают в роли функций, которые разделяют одну и ту же переменную.

Как скопировать объект в Python?

В общем случае, с помощью модуля copy.

Некоторые объекты можно скопировать более просто. Словари имеют метод copy:

Последовательности могут быть скопированы путём срезов:

Как узнать доступные методы и атрибуты объекта?

dir(x) возвращает список методов и атрибутов.

Как можно узнать имя объекта?

Вообще говоря, никак, поскольку объекты в действительности не имеют имён. Важно: присваивание всегда связывает имя с объектом. Это верно и для инструкций def и class.

Возможно, класс имеет имя: однако, хотя он связан с двумя именами и запрашивается через имя B, созданный экземпляр всё ещё считается экземпляром класса A. Однако, невозможно сказать, имя экземпляра a или b, поскольку оба они связаны с одним и тем же значением.

Какой приоритет у оператора «запятая»?

Запятая не является оператором в Python.

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

Есть ли в Python эквивалент тернарного оператора «?:» в C?

Можно ли писать обфусцированные однострочники?

Не пытайтесь это делать дома!

Поскольку i % j имеет тот же знак, что j. А ещё

Как можно изменить строку?

Никак, поскольку строки неизменяемы. В большинстве ситуаций, нужно просто сделать новую строку из различных частей. Однако, если так нужно, можно использовать io.StringIO, либо модуль array:

Как использовать строки для вызова функций/методов?

Существует несколько приёмов.

Как удалить все символы новой строки в конце строки?

Можно использовать S.rstrip("\r\n") для удаления символов новой строки, без удаления конечных пробелов:

Как удалить повторяющиеся элементы в списке?

Как создать многомерный список?

Возможно, вы попробуете этот неудачный вариант:

Это выглядит правильно, если напечатать:

Но если вы присвоите значение, то оно появится в нескольких местах:

Причина в том, что оператор * не создаёт копию, а только ссылку на существующий объект. *3 создаёт список из 3 ссылок на один и тот же список. Изменение в одной строке изменяют другие, что, вероятно, не то, что вы хотите.

Возможные пути решения:

Или, можно использовать специальные модули, предоставляющие матрицы. Наиболее известным является NumPy.

Почему a_tuple[i] += [‘item’] не работает, а добавление работает?

Это обсуждение относится в общем, когда расширенные операторы присваивания применяются к элементам кортежа, которые указывают на изменяемые объекты, но мы будем использовать список и +=, как образец.

Причина исключения должна быть понятна: 1 добавляется к объекту a_tuple[0], но когда мы пытаемся присвоить результат, 2, к первому элементу в кортеже, мы получаем ошибку, поскольку мы не можем изменить элемент кортежа.

То есть, это выражение делает следующее:

Когда мы пишем что-то вроде:

Исключение немного более неожиданное, но более удивителен тот факт, что, несмотря на ошибку, элемент добавился!

Чтобы понять, что случилось, нужно знать, что:

Таким образом, наш пример с кортежом эквивалентен:

__iadd__ завершился успешно, и список увеличился, но присваивание законилось ошибкой.

Источник

PEP 572 (Выражения присваивания в python 3.8)

PEP 572 — Выражения Присваивания

Аннотация

Это соглашение расскажет о появившейся возможности присваивания внутри выражений, с помощью нового обозначения NAME := expr.

В рамках нововведений был обновлен порядок вычисления генераторов словарей (dictionary comprehension). Это гарантирует, что выражение ключа вычислится перед выражением значения (это позволяет привязывать ключ к переменной, а затем повторно использовать созданную переменную в вычислении значения, соответствующего ключу).

Во время обсуждения этого PEP, данный оператор стал неофициально известен как «моржовый оператор» (the walrus operator). Формальное имя конструкции — «Выражение присваивания» (согласно заголовку PEP: Assignment Expressions), но она может упоминаться, как «Именованные выражения» (Named Expressions). Например, эталонная реализация в CPython использует именно это название.

Обоснование

Именование является важной частью программирования, которая позволяет использовать «описательное» имя вместо более длинного выражения, а также упрощает повторное использование значений. В настоящее время это возможно сделать лишь в виде инструкции, что делает эту операцию недоступной при генерации списков (list comprehension), а также в других выражениях.

Кроме того, именование частей большого выражения может помочь при интерактивной отладке, предоставив инструменты отображения подсказок и промежуточных результатов. Без возможности захвата результатов вложенных выражений, потребуется изменение исходного кода, но используя выражения присваивания вам достаточно вставить несколько «маркеров» вида «имя := выражение». Это устраняет лишний рефакторинг, а значит снижает вероятность непреднамеренного изменения кода в процессе отладки (частая причина Heisenbugs [прим. гейзенбаги — ошибки, которые меняют свойства кода во время отладки и могут неожиданно проявиться в продакшене] ), а также данный код будет более понятен другому программисту.

Важность реального кода

Во время разработки этого PEP многие люди (как сторонники, так и критики) слишком концентрировались на игрушечных примерах с одной стороны, и на чрезмерно сложных примерах, с другой.

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

Тем не менее, для таких примеров есть хорошее применение: они помогают уточнить предполагаемую семантику. Поэтому мы приведём ниже некоторые из них. Однако, чтобы быть убедительными, примеры должны основываться на реальном коде, который был написан без размышлений об этом PEP-е. То есть код, являющийся частью реально полезного приложения (без разницы: большое оно, или маленькое). Тим Питерс очень сильно помог нам, просмотрев свои личные репозитории и выбрав примеры написанного им кода, которые (по его мнению) стали бы более понятным, если их переписать (без фанатизма) с использованием выражений присваивания. Его вывод таков: текущие изменения привнесли бы скромное, но явное улучшение в нескольких битах его кода.

Другой пример реального кода — это косвенное наблюдение за тем, насколько программисты ценят компактность. Гвидо ван Россум проверил кодовую базу Dropbox и обнаружил некоторые доказательства того, что программисты предпочитают писать меньше строк кода, нежели чем использовать несколько небольших выражений.

Показательный случай: Гвидо нашел несколько иллюстративных моментов, когда программист повторяет подвыражение (тем самым замедляя программу), но экономит лишнюю строку кода. Например, вместо того, чтобы писать:

Программисты предпочитали такой вариант:

Вот ещё пример, показывающий, что программисты иногда готовы сделать больше работы, чтобы сохранить «прежний уровень» отступов:

Этот код вычисляет pattern2, даже если pattern1 уже совпал (в этом случае второе под-условие никогда не выполнится). Поэтому следующее решение является более эффективным, но менее привлекательным:

Синтаксис и семантика

В большинстве случаев, где в Python используются произвольные выражения (arbitrary expressions), теперь можно применять выражения присваивания. Они имеют форму NAME := expr, где expr — любое допустимое выражение Python, кроме кортежа без скобок (unparenthesized tuple), а NAME — идентификатор. Значение такого выражения совпадает с исходным, но дополнительным эффектом является присвоение значения целевому объекту:

Исключительные случаи

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

Не заключенные в скобки выражения присваивания запрещены в значениях ключевого аргумента при вызове функции. Пример:

Это показывает, что не всё выглядящее, как оператор присваивания в f-строке, является таковым. Парсер f-строки использует символ «:» для указания параметров форматирования. Чтобы сохранить обратную совместимость, при использовании оператора присваивания внутри f-строк он должен быть заключен в скобки. Как отмечено в примере выше, такое использование оператора присваивания не рекомендуется.

Область видимости

Выражение присваивания не вводит новую область видимости. В большинстве случаев область видимости, в которой будет создана переменная, не требует пояснений: она будет текущей. Если прежде переменная использовала ключевые слова nonlocal или global, тогда выражение присваивания учтёт это. Только лямбда (будучи анонимным определением функции) считается для этих целей отдельной областью видимости.

Существует один особый случай: выражение присваивания, встречающееся в генераторах списков, множеств, словарей или же в самих «выражениях генераторах» (ниже все вместе именуемые «генераторами» (comprehensions) ), привязывает переменную к области видимости, которая содержит генератор, соблюдая модификатор globab или nonglobal, если таковой существует.

Обоснование для этого особого случая двояко. Во-первых, это позволяет нам удобно захватывать «участника» в выражениях any () и all(), например:

Во-вторых, это предоставляет компактный способ обновления переменной из генератора, например:

Однако имя переменной из выражения присваивания не может совпадать с именем, которое уже используется в генераторах циклом for для итерации. Последние имена являются локальными по отношению к генератору, в котором появляются. Было бы противоречиво, если бы выражения присваивания ссылались ещё и к области видимости внутри генератора.

Например, [i: = i + 1 for i in range(5)] недопустимо: цикл for устанавливает, что i является локальной для генератора, но часть «i := i+1» настаивает на том, что i является переменной из внешней области видимости. По той же причине следующие примеры не сработают:

Хотя технически возможно назначить согласованную семантику для таких случаев, но трудно определить, сработает то, как мы понимаем эту семантику, в вашем реальном коде. Именно поэтому эталонная реализация гарантирует, что такие случаи вызывают SyntaxError, а не выполняются с неопределённым поведением, зависящим от конкретной аппаратной реализации. Это ограничение применяется, даже если выражение присваивания никогда не выполняется:

Для тела генератора (часть перед первым ключевым словом «for») и выражения-фильтра (часть после «if» и перед любым вложенным «for») это ограничение применяется исключительно к именам перемененных, которые одновременно используются в качестве итерационных переменных. Как мы уже сказали, Лямбда-выражения вводят новую явную область видимости функции и следовательно могут использоваться в выражениях генераторов без дополнительных ограничений. [прим. опять же, кроме таких случаев: [i for i in range(2, (lambda: (s:=2)() ))] ]

Из-за конструктивных ограничений в эталонной реализации (анализатор таблицы символов не может распознать, используются ли имена из левой части генератора в оставшейся части, где находится итерируемое выражение), поэтому выражения присваивания полностью запрещены как часть итерируемых (в части после каждого «in» и перед любым последующим ключевым словом «if» или «for»). То есть все эти случаи недопустимы:

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

(Причиной последнего исключения является неявная область видимости функции, созданной генератором — в настоящее время нет runtime механизма для функций, чтобы сослаться на переменную, расположенную в области видимости класса, и мы не хотим добавлять такой механизм. Если эта проблема когда-либо будет решена, то этот особый случай (возможно) будет удалён из спецификации выражений присваивания. Обратите внимание, что эта проблема возникнет, даже если вы раннее создали переменную в области видимости класса и пытаетесь изменить её выражением присваивания из генератора.)

Смотрите приложение B для примеров того, как выражения присваивания находящиеся в генераторах, преобразуются в эквивалентный код.

Относительный приоритет :=

Оператор := группируется сильнее, чем запятая во всех синтаксических позициях где это возможно, но слабее, чем все другие операторы, включая or, and, not, и условные выражения (A if C else B). Как следует из раздела «Исключительные случаи» выше, выражения присваивания никогда не работают на том же «уровне», что и классическое присваивание =. Если требуется другой порядок операций, используйте круглые скобки.

Оператор := может использоваться непосредственно при вызове позиционного аргумента функции. Однако непосредственно в аргументе это не сработает. Некоторые примеры, уточняющие, что является технически разрешённым, а что невозможно:

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

Этот PEP рекомендует абсолютно всегда ставить пробелы вокруг :=, аналогично рекомендации PEP 8 для = для классического присваивания. (Отличие последней рекомендации в том, что она запрещает пробелы вокруг =, который используется для передачи ключевых аргументов функции.)

Изменение порядка вычислений.

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

Различия между выражениями присваивания и инструкциями присваивания.

Что наиболее важно, «:=» является выражением, а значит его можно использовать в случаях, когда инструкции недопустимы, включая лямбда-функции и генераторы. И наоборот, выражения присваивания не поддерживают расширенный функционал, который можно использовать в инструкциях присваивания:

Спецификация изменяется во время реализации

Следующие изменения были сделаны на основе полученного опыта и дополнительного анализа после первого написания данного PEP и перед выпуском Python 3.8:

Примеры

Примеры из стандартной библиотеки Python

site.py

env_base используется только в условии, поэтому присваивание можно поместить в if, как «заголовок» логического блока.

_pydecimal.py

Вы можете избегать вложенных if, тем самым удалив один уровень отступов.

copy.py

Код выглядит более классическим, а также позволяет избежать множественной вложенности условных операторов. (См. Приложение A, чтобы узнать больше о происхождении этого примера.)

datetime.py

tz используется только для s += tz. Перемещение его внутрь if помогает показать его логическую область использования.

sysconfig.py

Упрощение генераторов списков

Теперь генератор списка может эффективно фильтроваться путем «захвата» условия:

После этого переменная может быть повторно использована в другом выражении:

Ещё раз обратите внимание, что в обоих случаях переменная y находится в той же области видимости, что и переменные result и stuff.

«Захват» значений в условиях

Выражения присваивания могут быть эффективно использованы в условиях оператора if или while:

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

Пример из низкоуровневого мира UNIX: [прим. Fork() — системный вызов в Unix-подобных операционных системах, создающий новый под-процесс, по отношению к родительскому.]

Отклоненные альтернативны

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

Изменение области видимости для генераторов

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

Альтернативные варианты написания

В целом, предложенные выражения присваивания имеют ту же семантику, но пишутся по-другому.

Так как конструкция EXPR as NAME уже имеет семантический смысл в выражениях import, except и with, это могло создать ненужную путаницу и некоторые ограничения (например, запрет выражения присваивания внутри заголовков этих конструкций).

(Обратите внимание, что «with EXPR as VAR» не просто присваивает значение EXPR в VAR, а вызывает EXPR.__enter__() и уже после присваивает полученный результат в VAR.)

Этот синтаксис основан на таких языках, как R и Haskell, ну и некоторых программируемых калькуляторах. (Обратите внимание, что направление стрелки справа-налево y

Источник

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *