|
Предыдущие частиОглавление
ВведениеНам осталось рассмотреть последний тип данных, существующий в Vim. Это словари, которые представляют собой хранилище данных, доступ к которым осуществляется по строковому ключу. Подобные типы данных в других языках программирования называют или картами (map), или ассоциативными массивами. Важное свойство словарей заключается в том, что хранящиеся в нем данные не упорядочены, как в списке, поэтому доступ к элементам невозможен по порядковому номеру, а возможен исключительно по ключу. Словари в Vim очень похожи на одноименный тип данных Python, основное отличие заключается в том, что в Vim в качестве ключа может выступать только строка, но в качестве хранимых данных могут выступать любые типы данных, в том числе и другие словари. С помощью словарей в Vim можно организовать и объектно-ориентированный подход к программированию скриптов, о чем мы тоже поговорим. Создание словарейДля начала разберемся с тем как создаются словари, а создавать их можно двумя способами. Первый способ заключается в том, что при создании словаря сразу указывается некоторое количество пар ключ-значение (ключ-данные), которые сразу же окажутся в словаре. Синтаксис такого подхода очень напоминает Python. Давайте создадим несколько словарей, каждый из которых будет содержать различную информацию о работниках: имя (поле 'name'), должность (поле 'occupation') и зарплата (поле 'salary'): let worker1 = {'name': 'Петя', 'occupation': 'Админ', 'salary': 1500} let worker2 = {'name': 'Иван Иваныч', 'occupation': 'Начальник', 'salary': 'Не говорит'} echo worker1 echo worker2 Как Vim выводит словари вы можете увидеть на следующем скриншоте. ![]() Такое применение словарей уже напоминает объектно-ориентированный подход, поэтому в дальнейших примерах мы будем придерживаться такой же структуры словарей. Как видите, в одном словаре в качестве данных могут выступать разные типы данных (в данном случае - строки и целые числа). Второй подход к созданию словарей состоит в том, чтобы сначала создать пустой словарь или словарь с начальными элементами, а потом уже его дополнить нужными данными. let worker = {} let worker['name'] = 'Петя' let worker['occupation'] = 'Админ' let worker['salary'] = 1500 echo worker Такая запись встречается во многих языках программирования, и думаю, что какие-то пояснения здесь не нужны. Но хочется обратить ваше внимание на порядок отображения этого словаря в Vim командой 'echo': {'occupation': 'Админ', 'name': 'Петя', 'salary': 1500} Здесь уже порядок вывода полей не совпадает с тем в какой последовательности мы добавляли их, что еще раз доказывает то, что на этот порядок нельзя рассчитывать - он может быть любым. Выше говорилось, что в качестве ключа может выступать только строка, это верно, но при написании скриптов мы можем использовать в качестве ключа и целые числа, но они будут автоматически преобразованы в строки. Для того, чтобы в этом убедиться, немного изменим предыдущий пример: let worker = {} let worker[10] = 'Петя' let worker[20] = 'Админ' let worker[30] = 1500 echo worker В результате на экране мы увидим следующую строку: {'10': 'Петя', 'occupation': '20', '30': 1500} Таким образом словари можно использовать в качестве разреженных массивов, когда у элементов массива используются большие индексы, но самих элементов не так много, и большая часть массива остается пустой. В этом случае не надо заводить действительно большой массив (список), а достаточно использовать целые числа в качестве индексов, в результате будет казаться, что мы работаем с огромным массивом. У второго способа создания (заполнения) словарей есть еще сокращенная форма, которая по записи еще ближе нас подводит к объектно-ориентированному программированию. Эта запись выглядит следующим образом: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 echo worker Эти два примера равносильны. А теперь вспомните, что у нас в арсенале есть такой замечательный тип данных, как указатели на функции, которые мы тоже можем хранить в словаре, таким образом мы получим и инкапсуляцию (включение) членов, и наличие методов. У таких функций будут дополнительные возможности для работы со словарями, куда они входят, но об этом речь пойдет чуть позже, в последующих разделах. Доступ к элементам словарейИтак, создавать словари мы уже умеем, теперь рассмотрим операции, которые мы можем с ними проделывать. Начнем с извлечения данных по ключу, это одна из самых часто используемых операций, ведь мы же не просто так сохраняем данные, надо научиться их и читать. Извлечение данных по синтаксису ничем не отличается от заполнения. Сразу рассмотрим пример: let worker = {} let worker['name'] = 'Петя' let worker['occupation'] = 'Админ' let worker['salary'] = 1500 echo worker['name'] echo worker['occupation'] echo worker['salary'] Или то же самое в объектно-ориентированном стиле: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 echo worker.name echo worker.occupation echo worker.salary При работе со словарями иногда необходимо перебирать все хранящиеся в нем элементы, чтобы их как-то обработать. Для подобных операций в Vim существует несколько функций.
Давайте для начала посмотрим что вернут эти функции для нашего последнего примера. let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 echo keys (worker) echo values (worker) echo items (worker) Результат работы этого скрипта показан на следующем скриншоте. ![]() Рассмотрим наиболее типичные применения этих функций: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 for key in keys(worker) echo key "-" worker[key] endfor Здесь мы получаем список ключей и, перебирая их в цикле for, получаем и выводим также соответствующее ключу значение. ![]() Чтобы на каждой итерации цикла не надо было бы вручную получать соответствующие значения, можем воспользоваться функцией items() и получать поочередно пары ключ-значение одновременно: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 for [key, value] in items(worker) echo key "-" value endfor Также есть полезная функция has_key(), которая принимает словарь и строковое значение и возвращает 1 в случае, если в словаре есть элемент под соответствующем ключом: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 echo has_key (worker, 'salary') echo has_key (worker, 'age') В результате мы увидим: ![]() Удаление элементов из словарейТеперь мы уже умеем создавать, заполнять словарь содержимым, а также извлекать из него данные. Теперь осталось научиться удалять из словаря элементы, ставшие ненужными. Для этого есть два подхода. Первый - использовать команду unlet, уже используемую нами при удалении переменных. Второй способ - это воспользоваться функцией remove(). Рассмотрим эти подходы по очереди. let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 unlet worker['salary'] echo worker В результате на экран будет выведена надпись: ![]() Ту же операцию мы можем переписать в объектно-ориентированном стиле: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 unlet worker.salary echo worker Теперь воспользуемся функцией remove(), она принимает в качестве параметров словарь и строку, являющуюся ключом к элементу, который должен быть удален. Сама функция remove() возвращает значение, которое хранилось по указанному ключу. Это продемонстрировано в следующем скрипте. let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 echo remove(worker, 'salary') echo worker Результат работы этого скрипта показан на следующем скриншоте. ![]() Также может быть полезна функция empty(), которая принимает в качестве параметра словарь (или список) и возвращает 1, если словарь (или список) пуст, и 0 в противном случае. Как и для списков, чтобы узнать количество элементов в словаре, можно воспользоваться функцией len(). Глубокие копииМы уже обсуждали вопросы копирования в третьей части, когда разбирались со списками. Все сказанное там в полной мере относится и к словарям, поэтому лишь кратко вспомним в чем состоит проблема. Дело в том, что при присвоении одной переменной значения другой переменной, которая является словарем, мы, по сути, присваиваем только указатель на словарь. При этом сам словарь остается в единственном экземпляре, и его могут изменить обе переменные. let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 let worker2 = worker echo worker echo worker2 let worker2.salary = 2000 echo worker echo worker2 В результате мы увидим: ![]() Чтобы создать обычную (не глубокую) копию словаря можно воспользоваться функцией copy(): let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 let worker2 = copy (worker) echo worker echo worker2 let worker2.salary = 2000 echo worker echo worker2 В данном случае это помогло: ![]() Однако, если в словаре будет храниться другой словарь или список, то в копию словаря попадет не заново скопированный словарь/список, а лишь указатель на него, в результате внутри обоих словарей будет храниться указатель на один и тот же внутренний словарь/список: let foo = {'key1': 'val1', 'key2': {'bar': 'val_bar'} } echo foo let spam = copy (foo) let spam.key2.bar = 'new_val' echo foo echo spam ![]() Чтобы указать интерпретатору, что нужно копировать и внутренние списки/словари, нужно воспользоваться функцией deepcopy(): let foo = {'key1': 'val1', 'key2': {'bar': 'val_bar'} } echo foo let spam = deepcopy (foo) let spam.key2.bar = 'new_val' echo foo echo spam В результате копирования создается так называемая "глубокая копия", в которой все внутренние структуры скопированы рекурсивно (максимум до 100 уровня вложенности): ![]() Объектно-ориентированное программирование в VimИтак, для доступа к элементам словаря в Vim можно использовать запись, напоминающую объектно-ориентированное программирование (ООП), что мы уже делали. Но ведь словари могут хранить и указатели на функции, что еще ближе нас подводит к ООП. Мы можем написать следующий код: function! s:hello() echo 'Привет!' endfunction let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 let worker.hello = function ('s:hello') call worker.hello() В результате на экране мы увидим приветствие: Привет! Но такой вызов функции хоть и может полезен тем, что функция и объект-словарь становятся связанными, но от ООП мы обычно ожидаем большего. В данном примере основная проблема состоит в том, что связь функции и объекта односторонняя: объект имеет доступ к функции, а функция к своему владельцу - нет, поэтому функция не может обращаться к членам объекта. А нам, допустим, хотелось бы, чтобы эта функция не просто писала приветствие, но и обращалась к объекту по имени через член name. Мы можем в явном виде передать в функцию наш словарь: function! s:hello(self) echo printf ('Привет, %s!', a:self.name) endfunction let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 let worker.hello = function ('s:hello') call worker.hello(worker) Теперь скрипт выведет строку: Привет, Петя! Но согласитесь, что такое решение не особо красивое, мы вынуждены дважды указывать объект worker при вызове функции. Было бы здорово, если бы параметр self передавался неявно, как это сделано с одноименным параметром в Python или this в C++. И разработчики Vim дали такую возможность, для этого достаточно после объявления функции добавить ключевое слово dict, тогда в функцию неявно будет передаваться указатель на объект-словарь, из которого функция была вызвана. Имя параметра так и останется self, с той лишь разницей, что перед именем этой переменной не нужно ставить префикс a:, показывающий, что это аргумент функции. Теперь предыдущий пример у нас будет выглядеть следующим образом: function! s:hello() dict echo printf ('Привет, %s!', self.name) endfunction let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 let worker.hello = function ('s:hello') call worker.hello() Кроме того, существует возможность еще немного сократить запись, указав имя объекта при объявлении функции: let worker = {} let worker.name = 'Петя' let worker.occupation = 'Админ' let worker.salary = 1500 function! worker.hello() dict echo printf ('Привет, %s!', self.name) endfunction call worker.hello() Здесь мы использовали запись function! worker.hello() dict, что позволило избавиться от строки let worker.hello = function ('s:hello'). Теперь мы можем создавать полноценные объекты с членами и методами. На этот раз не будет какого-либо жизненного примера, поскольку мне не хватило фантазии придумать какой-нибудь короткий пример, где бы использование словарей не было бы притянуто за уши, а использование объектно-ориентированный подход чаще всего оправдывает себя в сравнительно крупных скриптах. Но из-за этого расстраиваться не стоит, теперь мы разобрали все типы данных, существующие в Vim, поэтому следующая часть статьи будет посвящена полностью написанию плагинов. Часть 8. Более подробно о плагинах Пожалуйста, оцените материал
|