Что такое кортеж программирование
Перейти к содержимому

Что такое кортеж программирование

  • автор:

Что такое кортеж программирование

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

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

tom = ("Tom", 23) print(tom) # ("Tom", 23)

Также для определения кортежа мы можем просто перечислить значения через запятую без применения скобок:

tom = "Tom", 23 print(tom) # ("Tom", 23)

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

tom = ("Tom",)

Для создания кортежа из другого набора элементов, например, из списка, можно передать список в функцию tuple() , которая возвратит кортеж:

data = ["Tom", 37, "Google"] tom = tuple(data) print(tom) # ("Tom", 37, "Google")

С помощью встроенной функции len() можно получить длину кортежа:

tom = ("Tom", 37, "Google") print(len(tom)) # 3

Обращение к элементам кортежа

Обращение к элементам в кортеже происходит также, как и в списке, по индексу. Индексация начинается также с нуля при получении элементов с начала списка и с -1 при получении элементов с конца списка:

tom = ("Tom", 37, "Google", "software developer") print(tom[0]) # Tom print(tom[1]) # 37 print(tom[-1]) # software developer

Но так как кортеж — неизменяемый тип (immutable), то мы не сможем изменить его элементы. То есть следующая запись работать не будет:

tom[1] = "Tim"

При необходимости мы можем разложить кортеж на отдельные переменные:

name, age, company, position = ("Tom", 37, "Google", "software developer") print(name) # Tom print(age) # 37 print(position) # software developer print(company) # Google

Получение подкортежей

Как и в списках, можно получить часть кортежа в виде другого кортежа

tom = ("Tom", 37, "Google", "software developer") # получем подкортеж с 1 по 3 элемента (не включая) print(tom[1:3]) # (37, "Google") # получем подкортеж с 0 по 3 элемента (не включая) print(tom[:3]) # ("Tom", 37, "Google") # получем подкортеж с 1 по послдений элемент print(tom[1:]) # (37, "Google", "software developer")

Кортеж как параметр и результат функций

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

def get_user(): name = "Tom" age = 22 company = "Google" return name, age, company user = get_user() print(user) # ("Tom", 37, "Google")

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

def print_person(name, age, company): print(f"Name: Age: Company: ") tom = ("Tom", 22) print_person(*tom, "Microsoft") # Name: Tom Age: 22 Company: Microsoft bob = ("Bob", 41, "Apple") print_person(*bob) # Name: Bob Age: 41 Company: Apple

Перебор кортежей

Для перебора кортежа можно использовать стандартные циклы for и while . С помощью цикла for:

tom = ("Tom", 22, "Google") for item in tom: print(item)

С помощью цикла while:

tom = ("Tom", 22, "Google") i = 0 while i < len(tom): print(tom[i]) i += 1

Проверка наличия значения

Как для списка с помощью выражения элемент in кортеж можно проверить наличие элемента в кортеже:

user = ("Tom", 22, "Google") name = "Tom" if name in user: print("Пользователя зовут Tom") else: print("Пользователь имеет другое имя")

Кортежи в языках программирования. Часть 1

Сейчас во многих языках программирования существует такая конструкция, как кортежи (tuples). Где-то кортежи в той или иной мере встроены в язык, иногда — опять же в той или иной мере — реализуются средствами библиотек. C++, C#, D, Python, Ruby, Go, Rust, Swift (а также Erlang, F#, Groovy, Haskell, Lisp, OCaml и многие другие)…
Что же такое кортеж? В Википедии дается достаточно точное определение: кортеж — упорядоченный набор фиксированной длины. Определение хоть и точное, но для нас пока бесполезное, и вот почему: задумывается ли большинство программистов, зачем понадобилась эта сущность? В программировании существует множество структур данных, как фиксированной, так и переменной длины; они позволяют хранить различные значения — как однитипные, так и разных типов. Всевозможные массивы, ассоциативные массивы, списки, структуры… зачем еще и кортежи? А в языках со слабой типизацией — и тем более, разница между кортежами и списками/векторами совсем размытая… ну нельзя добавлять в кортеж элементы, ну и что с того? Это может ввести в некоторое заблуждение. Поэтому стоит копнуть глубже и разобраться, зачем же на самом деле нужны кортежи, чем они отличаются от других языковых конструкций, и как сформировать идеальный синтаксис и семантику кортежей в идеальном (или близком к идеальному) языке программирования.

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

Первая важная вещь, о которой на Википедии не упомянули: кортеж — это структура времени компиляции. Иными словами, это некая сущность, объединяющая некоторые объекты на этапе компиляции. И это очень важно. Кортежи неявно используются во всех языках программирования, даже в Си и Ассемблере. Давайте поищем их в том же Си, С++, в любом компилируемом языке.
Так, список аргументов функции — это кортеж;
Список инициализации структуры или массива — это тоже кортеж;
Список аргументов шаблона или макроса — тоже кортеж
Описание структуры, и даже обычный блок кода — это тоже кортеж; только элементами его являются не объекты, а синтаксические конструкции.

Кортежей в программе гораздо больше чем кажется на первый взгляд. Но они все неявные; так или иначе они являются жестко прикрученными к каким-то синтаксическим конструкциям. Явное использование кортежей в старых языках было не предусмотрено. В более современных языках некоторые возможности явного использования стали появляться — но далеко не все. Здесь мы будем рассматривать в основном кортежи значений — или переменных, или констант. Возможно, в следующих частях я рассмотрю и кортежи произвольных синтаксических элементов.

Начнем с самого очевидного — возврата нескольких значений из функции. Еще со школьных времен меня удивляла такая несправедливость: почему функция может принимать сколько угодно значений, а возвращать только одно? В самом деле, почему y=x*x — нормальная парабола, а y = sqrt(x) — какая-то обрезанная наполовину фигня? Разве это не нарушение математической гармонии? В программировании конечно можно возвратить структурный объект, но суть остается та же: возвращается один объект, а не несколько.

Непосредственная реализация множественного возврата есть в Go. Функция может явно вернуть несколько значений. Синтаксис позволяет присвоить эти несколько значений нескольким переменным, а также выполнять групповые присваивания и даже перестановки аргументов за одну операцию. Однако, никаких других групповых действий кроме присваивания не предусмотрено.

func foo() (r1 int, r2 int) < return 7, 4 >x, y := foo() x, y = 1, 2 x, y = y, x

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

func bar(x int, y int) < >bar(foo())

Такая пакетная передача сама по себе крайне интересна. С одной стороны, она кажется весьма элегантной; но с другой — она слишком «неявная», неуниверсальная. Например, если попытаться добавить в bar третий аргумент, и попытаться совместить «пакетную» передачу и обычную

bar(foo(), 100)

то ничего не получится — ошибка компиляции.

Еще один интересный аспект — неиспользование возвращаемых значений. Вспомним С/С++. В них (а также в подавляющем большинстве других языков — Java, C#, ObjC, D. ) можно было спокойно игнорировать возвращаемые значения при вызове функции. В Go такое тоже возможно, причем можно игнорировать как единственное возвращаемое значение, так и группу. Однако, попытка использовать первое возвращаемое значение и неявно проигнорировать второе приводит к ошибке компиляции. Игнорировать возможно, но явно — с использованием специального символа "_":

x, _ := foo()

Т.е. работает принцип «все или ничего»: можно или игнорировать все возвращаемые значения, или использовать — но также все.

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

let x = (1i, 2i, 3i); let y = (2i, 3i, 4i); if x == y < println!("yes"); >else

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

В языке Swift возможности в целом аналогичны. Из интересного — обращение к элементам кортежа по константному индексу через точку; возможность назначать элементам кортежа имена и обращаться к элементам через них.

let httpStatus = (statusCode: 200, description: "OK") print("The status code is \(httpStatus.0)") print("The status code is \(httpStatus.statusCode)") 

Такие кортежи уже близки к структурам, но все-же структурами не являются. И здесь я бы хотел отойти от примеров и перейти к своим собственным мыслям. Разница между кортежами и структурами в том, что кортеж — это не тип данных, это нечто более низкоуровневое; можно сказать — что кортеж — это просто (возможно именованная) группа (возможно именованных) объектов времени компиляции. В этом месте вспомним языки C/С++. Простейшие конструкции инициализации массива и структуры выглядят так:

int arr[] = ; Point3D pt = ;

Обратите внимание, списки инициализации в данном случае вообще идентичны. И тем ни менее, они инициализируют совершенно разные объекты данных. Такое поведение в общем нетипично для типа данных. Но зато это близко к другой интересной фиче, которая иногда (но редко) встречается в языках программирования — структурной типизации. Конструкция в фигурных скобках — типичный кортеж. Кстати, в Си есть именованная инициализация полей структуры (идея кстати весьма похожа на Swift), которую пока так и не протащили в C++17:

Point3D pt = ;

В С++ пошли немного в другом направлении: ввели понятие " унифицированный синтаксис инициализации и списки инициализации". Синтаксически это те же кортежи, которые можно использовать для инициализации объектов; в дополнение к старым возможностям, унифицированный синтаксис инициализации позволяет передавать объекты в функции и возвращать из функций в виде кортежей.

 Point3D pt; // новый синтаксис инициализации Point3D foo(Point3D a) < return ; // возвращаем "кортеж" > foo( ); // передаем "кортеж" 

Другая интересная возможность — списки инициализации.Они используются для начальной инициализации динамических структур данных — таких как вектора и списки. Списки инициализации в С++ должны быть однородными, то есть все элементы списка должны быть одного типа. Технически такие списки формируют константные массивы в памяти, для доступа к которым применяются итераторы std::initializer_list. Можно сказать, что шаблонный тип std::initializer_list — специальный определенный на уровне компилятора интерфейс к однородным кортежам (а фактически к константным массивам). Разумеется, списки инициализации можно использовать не только в конструкторах, но и как аргументы любых функций и методов. Думаю, если бы в С++ изначально был бы некий шаблонный тип данных, соответствующий литеральному массиву и содержащий информацию о длине этого массива, он бы вполне подошел на роль std::initializer_list.

Также в стандартной библиотеке С++ (и в Boost) существуют кортежи, реализованные с помощью шаблонов. Поскольку такая реализация не является частью языка, синтаксис получился слегка громоздким и неуниверсальным. Так, тип кортежа приходится объявлять явно с указанием типов всех полей; для конструирования объектов применяется функция std::make_tuple; для создания кортежа «на лету» (из существующих переменных) применяется другой шаблон — tie, а получение доступа к элементам осуществляется с помощью специального шаблонного метода, который требует константного индекса.

std::tuple t1(10,'x'); auto t2 = std::make_tuple ("test", 3.1, 14, 'y'); int myint; char mychar; std::tie (myint, mychar) = t1; // unpack elements std::tie (std::ignore, std::ignore, myint, mychar) = t2; // unpack (with ignore) std::get(t2) = 100; char mychr = std::get(t2);

В примере применяется распаковка со специальным значением std::ignore. Это в точности соответствует символу подчеркивания "_", применяемому для тех же целей при групповых возвратах из функций в Go и Rust.

Похожим способом (хотя и упрощенно по сравнению с С++) реализованы кортежи в C#. Для создания используются методы Tuple.Create(), набора шаблонных классов Tuple<>, для доступа к элементам — поля с фиксированными именами Item1… item8 (чем достигается константность индекса).

В языке D есть достаточно богатая поддержка кортежей. С помощью конструкции tuple можно сформировать кортеж, и — в том числе — осуществить множественный возврат из функции. Для доступа к элементам кортежа используется индексация константными индексами. Также кортеж можно сконструировать с помощью шаблона Tuple, что позволяет создать кортеж с именованными полями.

auto t = Tuple!(int, "number", string, "message")(123, "hello"); writeln("by index 0 : ", t[0]); writeln("by .number : ", t.number); writeln("by index 1 : ", t[1]); writeln("by .message: ", t.message);

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

void bar(int i, double d, char c) < >auto t = tuple(1, "2", 3.3, '4'); bar(t[0], t[$-2..$]);

В D существует еще множество возможностей, связанных с кортежами — Compile-time foreach для обхода кортежей на этапе компиляции, шаблон AliasSeq, оператор tupleof… в общем все это требует отдельной большой статьи.

А напоследок рассмотрим реализацию кортежей в малоизвестном расширении языка Си — CForAll или C∀ (забавно, но на момент написания статьи я не смог нагуглить сайт языка — весьма вероятно что он давно закрылся и упоминаний просто не осталось; вот поэтому я регулярно сканирую сеть в поисках новых языков программирования и скачиваю все до чего смогу дотянуться).

Кортежи в C∀можно объявить на уровне языка, заключив список объектов в квадратные скобки. Тип кортежа создается аналогично — в квадратные скобки заключается список типов. Объекты и типы кортежей можно объявлять явно. Кортежи можно передавать в функции, где они разворачиваются в списки аргументов (в отличие от Go, где такое возможно только при точном совпадении кортежа и списка аргументов функции).

[ int, int ] w1; // объект-кортеж из двух элементов [ int, int, int ] w2; // объект-кортеж из трех элементов void f (int, int, int); // функция, принимающая три аргумента f( 1, 2, 3 ); // просто числа f( [ 1, 2, 3 ] ); // кортеж-объект объявленный прямо на месте f( w1, 3 ) // кортеж и число f( w2 ) // кортеж-объект

Еще одна интересная тема — вложенные кортежи и правила их раскрытия. В С/С++ также применяется вложенность — при инициализации массивов структур, элементы которых также являются массивами и структурами. В C∀ существуют правила, называемые «tuple coercions», в частности — раскрытие кортежей с внутренней структурой (flattering) и наоборот, структурирование (structuring), когда «плоский» кортеж подстраивается под сложную внутреннюю структуру (хотя эта возможность весьма спорная, обсуждение будет в следующей части). И все это относится лишь к присваиванию, никаких упоминаний использования этих возможностей с другими операциями нет.

[ a, b, c, d ] = [ 1, [ 2, 3 ], 4 ];

В C∀ предусмотрено как групповое, так и множественное присваивание.

[ x, y, z ] = 1.5; [ x, y, z ] = [ 1, 2, 3 ];

и даже использование кортежей для доступа к полям структур

obj.[ f3, f1, f2 ] = [ x, 11, 17 ];

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

Кортежи

Кортежи (tuple) в Python – это те же списки за одним исключением. Кортежи неизменяемые структуры данных. Так же как списки они могут состоять из элементов разных типов, перечисленных через запятую. Кортежи заключаются в круглые, а не квадратные скобки.

>>> a = (10, 2.13, "square", 89, 'C') >>> a (10, 2.13, 'square', 89, 'C')

Из кортежа можно извлекать элементы и брать срезы:

>>> a[3] 89 >>> a[1:3] (2.13, 'square')

Однако изменять его элементы нельзя:

>>> a[0] = 11 Traceback (most recent call last): File "", line 1, in TypeError: 'tuple' object does not support item assignment

Также у типа tuple нет методов для добавления и удаления элементов.

Возникает резонный вопрос. Зачем в язык программирования был введен этот тип данных, по-сути представляющий собой неизменяемый список? Дело в том, что иногда надо защитить список от изменений. Преобразовать же кортеж в список, если это потребуется, как и выполнить обратную операцию легко с помощью встроенных в Python функций list() и tuple() :

>>> a = (10, 2.13, "square", 89, 'C') >>> b = [1, 2, 3] >>> c = list(a) >>> d = tuple(b) >>> c [10, 2.13, 'square', 89, 'C'] >>> d (1, 2, 3)

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

def add_num(seq, num): for i in range(len(seq)): seq[i] += num return seq origin = [3, 6, 2, 6] changed = add_num(origin, 3) print(origin) print(changed)

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

[6, 9, 5, 9] [6, 9, 5, 9]

То есть исходный список был также изменен. Параметр seq содержал ссылку не на свой локальный список, а на список-оригинал. Таким образом, в операторе return здесь нет смыла. Если функция замысливалась как изменяющая глобальный список, то программа должна выглядеть так:

def add_num(seq, num): for i in range(len(seq)): seq[i] += num origin = [3, 6, 2, 6] add_num(origin, 3) print(origin)

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

def add_num(seq, num): new_seq = [] for i in seq: new_seq.append(i + num) return new_seq origin = [3, 6, 2, 6] changed = add_num(origin, 3) print(origin) print(changed)
[3, 6, 2, 6] [6, 9, 5, 9]

Исходный список в функции не меняется. Его элементы лишь перебираются.

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

Хотя преобразовывать к кортежу можно как при передаче в функцию, так и в самой функции, лучше сразу делать глобальный список кортежем. Поскольку неизменяемые объекты передаются по значению, а не по ссылке, то в функцию будет поступать копия структуры, а не оригинал. Даже если туда передается оригинал, изменить его невозможно. Можно лишь, как вариант, скопировать его и/или изменить тип, создав тем самым локальную структуру, и делать с ней все, что заблагорассудится.

def add_num(seq, num): seq = list(seq) for i in range(len(seq)): seq[i] += num return seq origin = (3, 6, 2, 6) changed = add_num(origin, 3) print(origin) print(changed)

Списки в кортежах

Кортежи могут содержать списки, также как списки быть вложенными в другие списки.

>>> nested = (1, "do", ["param", 10, 20])

Как вы думаете, можем ли мы изменить список ["param", 10, 20] вложенный в кортеж nested ? Список изменяем, кортеж – нет. Если вам кажется, что нельзя, то вам кажется неправильно. На самом деле можно:

>>> nested[2][1] = 15 >>> nested (1, 'do', ['param', 15, 20])

Примечание. Выражения типа nested[2][1] используются для обращения к вложенным объектам. Первый индекс указывает на позицию вложенного объекта, второй – индекс элемента внутри вложенного объекта. Так в данном случае сам список внутри кортежа имеет индекс 2, а элемент списка 10 – индекс 1 в списке.

Странная ситуация. Кортеж неизменяем, но мы все-таки можем изменить его. На самом деле кортеж остается неизменяемым. Просто в нем содержится не сам список, а ссылка на него. Ее изменить нельзя. Но менять сам список можно.

Чтобы было проще понять, перепишем кортеж так:

>>> l = ["param", 10, 20] >>> t = (1, "do", l) >>> t (1, 'do', ['param', 10, 20])

Кортеж содержит переменную-ссылку. Поменять ее на другую ссылку нельзя. Но кортеж не содержит самого списка. Поэтому его можно менять как угодно:

>>> l.pop(0) 'param' >>> t (1, 'do', [10, 20])

Однако такой номер не проходит с неизменяемыми типами:

>>> a = "Kat" >>> t = (a, l) >>> t ('Kat', [10, 20]) >>> a = "Bat" >>> t ('Kat', [10, 20])

Они передаются в кортеж как и в функцию – по значению. То есть их значение копируется в момент передачи.

Практическая работа

  1. Чтобы избежать изменения исходного списка, не обязательно использовать кортеж. Можно создать его копию с помощью метода списка copy() или взять срез от начала до конца [:] . Скопируйте список первым и вторым способом и убедитесь, что изменение копий никак не отражается на оригинале.
  2. Заполните один кортеж десятью случайными целыми числами от 0 до 5 включительно. Также заполните второй кортеж числами от -5 до 0. Для заполнения кортежей числами напишите одну функцию. Объедините два кортежа с помощью оператора +, создав тем самым третий кортеж. С помощью метода кортежа count() определите в нем количество нулей. Выведите на экран третий кортеж и количество нулей в нем.

Примеры решения и дополнительные уроки в pdf-версии курса

X Скрыть Наверх

Python. Введение в программирование

Кортеж

В математике корте́ж или n-ка (упорядоченная n-ка) — упорядоченный конечный набор длины n(где n— любое натуральное число либо 0), каждый из элементов которого x_iпринадлежит некоторому множеству X_i, 1\leqslant i \leqslant n. Элементы кортежа могут повторяться в нём любое число раз (этим, в частности, он отличается от упорядоченного множества, куда каждый элемент может входить только в одном экземпляре).

В математике кортеж обычно записывается перечислением элементов в круглых или угловых скобках.

В теории множеств кортеж обычно определяется индуктивно:

  • пустое множество — это кортеж (с нулевым количеством элементов);
  • для каждого кортежа (a_1, \ldots, a_n)=T, множество (a, a_1, \ldots, a_n)=\<a, \<a, T\>\> также является кортежем.

Элементы кортежа называются его компонентами, или координатами.

Кортеж длины нуль называется пустым.

Частными случаями кортежа является (по числу элементов) упорядоченная пара, тройка, четвёрка.

Многие математические объекты формально определяются как кортежи. Например, Ориентированный граф определяется как кортеж (V,E), где V — это набор вершин, а E — подмножество V × V, обозначающее рёбра. Точка в n-мерном пространстве действительных чисел определяется как кортеж длины n, составленный из элементов множества действительных чисел.

В программировании

В некоторых языках программирования, например, Python или Lisp, кортеж — особый тип структуры данных. В языке C++ поддержка кортежей реализована как шаблон класса std::tuple [источник?] . В языке Python кортеж (англ. tuple ) отличается от списка тем, что элементы кортежа нельзя изменять.

thing = "rose" colour = "red" print "%s is %s" % (thing, colour) 

Эта программа на Python, использующая кортеж (thing, colour) , выведет: rose is red

В императивных языках множество аргументов подпрограммы описывается и передаётся кортежем.

Кортеж является стандартным типом в платформе .NET начиная с версии 4.0 [1]

В базах данных

В базах данных кортежем называется группа взаимосвязанных элементов данных. В реляционных базах данных кортеж — это элемент отношения, строка таблицы; упорядоченный набор из N элементов.

Примечания

Ссылки

  • Найти и оформить в виде сносок ссылки на авторитетные источники, подтверждающие написанное.

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

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