Класс памяти определяет время жизни объекта и место его размещения в памяти (относительно границ сегмента памяти, выделенного для программы в целом).
Существует 4 класса памяти: static (статический), extern (внешний), auto (автоматический), register (регистровый).
Объекты, имеющие класс памяти static, существуют (занимают место в памяти) в течение всего времени работы программы. Их место в памяти определяется на этапе компиляции. К ним по умолчанию относятся глобальные переменные.
Объекты, имеющие класс памяти auto, существуют (занимают место в памяти) лишь во время выполнения блока, в котором объявлены. К ним по умолчанию относятся локальные переменные. Память для них выделяется при входе в блок в пределах определенной области, называемой программным стеком.
После выхода из блока занимаемая ими память освобождается и может быть использована для других автоматических переменных. Это позволяет экономить память, а также осуществлять рекурсию (при вызове функцией самой себя создается новая копия всех ее автоматических переменных). Но это приводит к тому, что при повторном входе в блок значение, ранее присвоенное переменной, теряется.
Python.06.05 О разбиении программы на функции
Кроме того, общий размер стека должен быть задан на этапе компиляции и, таким образом, ограничен; в большинстве компиляторов по умолчанию (если не менять настроек) он равен всего лишь 1 мегабайт.
При любом вызове функции в программном стеке размещаются ее параметры, а также сохраняется содержимое регистров процессора на момент, предшествующий вызову функции, и адрес возврата из функции для того, чтобы при выходе из нее можно было продолжить выполнение вызывающей функции.
Класс памяти extern похож на static,но означает, что объект объявлен позже в этом или другом файле (см. ниже). Он может применяться к глобальным переменным.
Класс памяти register похож на auto; но он рекомендует компилятору разместить (если возможно) переменную не в стеке, а непосредственно в регистрах процессора. Регистровая память позволяет увеличить быстродействие программы, но к размещаемым в ней объектам в языке Си (но не С++) не применима операция получения адреса « но тогда программа станет менее наглядной; а если уже есть глобальная переменная с таким именем, то вообще возникнет конфликт).
Объявление локальной переменной как register может ускорить работу с ней. Однако число регистров ограничено. Компилятор (при включенной оптимизации) и сам пытается разместить в регистрах наиболее «критичные по быстродействию» переменные. Поэтому указывать класс памяти register имеет смысл лишь для того, чтобы «обратить внимание» компилятора именно на те переменные, обработка которых является «узким местом» для повышения быстродействия программы.
Описание глобальной переменной или функции как extern указывает, что ее определение содержится ниже, либо в другом файле; а данное описание лишь сообщает компилятору типы. Во всех файлах, составляющих исходную программу, должно содержаться только одно определение данной функции или глобальной переменной. Другие файлы могут содержать описание extern для доступа к ней.
Язык C++ с нуля | #35 Разделение программного кода на несколько файлов в c++
Инициализация объектов классов памяти static и extern происходит один раз при запуске программы. Если инициализация не указана, объект обязательно инициализируется нулем (или пустым значением — для нечисловых переменных).
Инициализация объектов классов памяти auto или register происходит каждый раз при входе в блок, где они объявлены. Если инициализация не указана, объект не инициализируется; его начальное значение тогда непредсказуемо. Исключение составляют переменные некоторых типов (например, String тогда инициализируется пустым значением).
Источник: studopedia.ru
Функции
С увеличением объема программы становится неудобно хранить ее в одном файле. Разбиение программы на функции является первым шагом в повышении уровня абстракции программы, следующий — группировка функций и связанных с ними данных в отдельные файлы (модули), компилируемые раздельно.
Получившиеся в результате компиляции объектные модули объединяются в исполняемую программу с помощью компоновщика. Разбиение на модули уменьшает время перекомпиляции и облегчает процесс отладки . Чем более независимы модули, тем легче отлаживать программу.
Модуль содержит данные и функции их обработки. Для того чтобы использовать модуль , нужно знать только его интерфейс . Интерфейсом модуля являются заголовки всех функций и описания доступных извне типов, переменных и констант. Описания глобальных программных объектов во всех модулях программы должны быть согласованы.
Модульность в языке С++ поддерживается с помощью директив препроцессора , пространств имен, классов памяти , исключений и раздельной компиляции .
Директивы препроцессора
Препроцессором называется первая фаза компилятора. Инструкции препроцессора называются директивами. Они должны начинаться с символа #, перед которым в строке могут находиться только пробельные символы .
Директива #include
Директива # include вставляет содержимое указанного файла в ту точку исходного файла, где она записана. Включаемый файл также может содержать директивы # include .
Поиск файла, если не указан полный путь , ведется в стандартных каталогах включаемых файлов . Вместо угловых скобок могут использоваться кавычки (» «) — в этом случае поиск файла ведется в каталоге, содержащем исходный файл, а затем уже в стандартных каталогах.
Директива # include является простейшим средством обеспечения согласованности объявлений в различных файлах, включая в них информацию об интерфейсе из заголовочных файлов . Заголовочные файлы обычно имеют расширение .h и могут содержать:
- определения типов, констант, встроенных функций , шаблонов, перечислений;
- объявления функций, данных, имен, шаблонов;
- пространства имен;
- директивы препроцессора ;
- комментарии.
В заголовочном файле не должно быть определений функций и данных.
Директива #define
Директива # define определяет подстановку в тексте программы. Она используется для определения:
- символических констант. Формат определения символической константы :
/* Все вхождения имени заменяются на текст подстановки */ #define имя текст_подстановки
#define имя( параметры ) текст_подстановки
#define имя
#define M 1000 #define Vasia «Василий Иванович» #define MAX(a,b) ((x)>(y)?(x):(y)) #define __cplusplus
Параметры используются при макроподстановке, например, если в тексте программы используется вызов макроса y = MAX(sum1, sum2); , он будет заменен на
y=((sum1)>(sum2)?(sum1):(sum2));
Использования макросов и символических констант в программах следует избегать.
Области действия идентификаторов
Каждый программный объект имеет область действия , которая определяется видом и местом его объявления. Существуют следующие области действия : блок, файл, функция, прототип функции , класс и поименованная область.
Блок. Идентификаторы, описанные внутри блока, являются локальными. Область действия идентификатора начинается в точке определения и заканчивается в конце блока, видимость — в пределах блока и внутренних блоков, время жизни — локальное. После выхода из блока память освобождается.
Файл. Идентификаторы, описанные вне любого блока, функции, класса или пространства имен, имеют глобальную видимость и время жизни и могут использоваться с момента их определения.
Функция. Единственными идентификаторами, имеющими такую область действия , являются метки операторов. В одной функции все метки должны различаться, но могут совпадать с метками других функций.
Прототип функции. Идентификаторы, указанные в списке параметров прототипа ( объявления) функции , имеют областью действия только прототип функции .
Класс. Элементы структур, объединений и классов (за исключением статических элементов ) являются видимыми лишь в пределах класса. Они образуются при создании переменной указанного типа и разрушаются при ее уничтожении.
Поименованная область. С++ позволяет явным образом задать область определения имен как часть глобальной области с помощью оператора namespace .
Область видимости совпадает с областью действия за исключением ситуации, когда во вложенном блоке описана переменная с таким же именем. В этом случае внешняя переменная во вложенном блоке невидима, хотя он и входит в ее область действия . Тем не менее к этой переменной, если она глобальная, можно обратиться, используя операцию доступа к области видимости . Способ обратиться к скрытой локальной переменной отсутствует.
В каждой области действия различают так называемые пространства имен. Пространство имен — область, в пределах которой идентификатор должен быть уникальным. В разных пространствах имена могут совпадать, поскольку разрешение ссылок осуществляется по контексту идентификатора в программе, например:
struct Node < int Node; int i; >Node;
В данном случае противоречия нет, поскольку имена типа, переменной и элемента структуры относятся к разным пространствам. В С++ определено четыре раздельных класса идентификаторов.
- К одному пространству имен относятся имена переменных, функций , типов, определенных пользователем ( typedef ) и констант перечислений в пределах одной области видимости . Все они, кроме имен функций, могут быть переопределены во вложенных блоках.
- Другой класс имен образуют имена типов перечислений, структур, классов и объединений. Каждое имя должно отличаться от имен других типов в той же области видимости .
- Отдельный класс составляют элементы каждой структуры, класса и объединения. Имя элемента должно быть уникально внутри структуры, но может совпадать с именами элементов других структур.
- Метки образуют отдельное пространство имен.
Внешние объявления
Любая функция автоматически видна во всех модулях программы. Если требуется ограничить область действия функции файлом, в котором она описана, используется модификатор static .
Для того чтобы сделать доступной в нескольких модулях переменную или константу, необходимо:
- определить ее ровно в одном модуле как глобальную;
- в других модулях объявить ее как внешнюю с помощью модификатора extern .
Другой способ — поместить это объявление в заголовочный файл и включить его в нужные модули 1 Напомню, что объявление, в отличие от определения, не создает переменную. Объявление с extern не должно содержать инициализацию: если она присутствует, модификатор extern игнорируется .
Все описания одной и той же переменной должны быть согласованы.
Пример описания двух глобальных переменных в файлах one. cpp и two. cpp с помощью заголовочного файла my_header.h :
// my_header.h — внешние объявления extern int a; extern double b; . // one.cpp #include «my_header.h» int a; . // two.cpp #include «my_header.h» double b; .
Если переменная описана как static , область ее действия ограничивается файлом, в котором она описана.
Поименованные области
Поименованные области служат для логического группирования объявлений. Чем больше программа, тем более актуально использование поименованных областей. Простейшим примером применения является отделение кода, написанного одним человеком, от кода, написанного другим.
Объявление поименованной области (ее также называют пространством имен) имеет формат:
namespace [ имя_области ]< /* Объявления */>
Поименованная область может объявляться неоднократно, причем последующие объявления рассматриваются как расширения предыдущих.
Если имя области не задано, компилятор определяет его самостоятельно с помощью уникального идентификатора, различного для каждого модуля. Объявление объекта в непоименованной области равнозначно его описанию как глобального с модификатором static .
Нельзя получить доступ из одного файла к элементу неименованной области другого файла.
namespace demo < int i = 1; int k = 0; void func1(int); void func2(int) < /* . */>> namespace demo< // Расширение // int i = 2; Неверно — двойное определение void func1(double); // Перегрузка void func2(int); // Верно (повторное объявление) >
В объявлении поименованной области могут присутствовать как объявления, так и определения. Логично помещать в нее только объявления, а определять их позднее с помощью имени области и оператора доступа к области видимости . например:
void demo::func1(int) < /* . */>
Это применяется для разделения интерфейса и реализации. Таким способом нельзя объявить новый элемент пространства имен.
Объекты, объявленные внутри области, являются видимыми с момента объявления. К ним можно явно обращаться с помощью имени области и оператора доступа к области видимости . например:
demo::i = 100; demo::func2(10);
Если имя часто используется вне своего пространства, можно объявить его доступным с помощью оператора using :
using demo::i;
После этого можно использовать имя без явного указания области.
Если требуется сделать доступными все имена из какой-либо области, используется оператор using namespace :
using namespace demo;
Операторы using и using namespace можно использовать и внутри объявления поименованной области, чтобы сделать в ней доступными объявления из другой области.
Пространства имен стандартной библиотеки
Объекты стандартной библиотеки определены в пространстве имен std . Например, объявления стандартных средств ввода/вывода С в заголовочном файле помещены в пространство имен следующим образом:
// stdio.h namespace std < int printf(const char**); . >using namespace std;
Это обеспечивает совместимость сверху вниз. Для тех, кто не желает присутствия неявно доступных имен, определен новый заголовочный файл :
// cstdio namespace std
Если в программу включен файл , нужно указывать имя пространства имен явным образом:
std::printf(«*»);
Механизм пространств имен вместе с директивой # include обеспечивают необходимую при написании больших программ гибкость путем сочетания логического группирования связанных величин и ограничения доступа к ненужным средствам.
Источник: intuit.ru
Введение в функциональное программирование на Python
Рассуждая о функциональном программировании, люди часто начинают выдавать кучу «функциональных» характеристик. Неизменяемые данные, функции первого класса и оптимизация хвостовой рекурсии. Это свойства языка, помогающие писать функциональные программы. Они упоминают мапирование, каррирование и использование функций высшего порядка.
Это приёмы программирования, использующиеся для написания функционального кода. Они упоминают распараллеливание, ленивые вычисления и детерменизм. Это преимущества функциональных программ.
Забейте. Функциональный код отличается одним свойством: отсутствием побочных эффектов. Он не полагается на данные вне текущей функции, и не меняет данные, находящиеся вне функции. Все остальные «свойства» можно вывести из этого.
a = 0 def increment1(): global a a += 1
def increment2(a): return a + 1
Вместо проходов по списку используйте map и reduce
Map
Принимает функцию и набор данных. Создаёт новую коллекцию, выполняет функцию на каждой позиции данных и добавляет возвращаемое значение в новую коллекцию. Возвращает новую коллекцию.
Простой map, принимающий список имён и возвращающий список длин:
name_lengths = map(len, [‘Маша’, ‘Петя’, ‘Вася’]) print name_lengths # => [4, 4, 3]
Этот map возводит в квадрат каждый элемент:
squares = map(lambda x: x * x, [0, 1, 2, 3, 4]) print squares # => [0, 1, 4, 9, 16]
Он не принимает именованную функцию, а берёт анонимную, определённую через lambda. Параметры lambda определены слева от двоеточия. Тело функции – справа. Результат возвращается неявным образом.
Нефункциональный код в следующем примере принимает список имён и заменяет их случайными прозвищами.
import random names = [‘Маша’, ‘Петя’, ‘Вася’] code_names = [‘Шпунтик’, ‘Винтик’, ‘Фунтик’] for i in range(len(names)): names[i] = random.choice(code_names) print names # => [‘Шпунтик’, ‘Винтик’, ‘Шпунтик’]
Алгоритм может присвоить одинаковые прозвища разным секретным агентам. Будем надеяться, что это не послужит источником проблем во время секретной миссии.
Перепишем это через map:
import random names = [‘Маша’, ‘Петя’, ‘Вася’] secret_names = map(lambda x: random.choice([‘Шпунтик’, ‘Винтик’, ‘Фунтик’]), names)
Упражнение 1. Попробуйте переписать следующий код через map. Он принимает список реальных имён и заменяет их прозвищами, используя более надёжный метод.
names = [‘Маша’, ‘Петя’, ‘Вася’] for i in range(len(names)): names[i] = hash(names[i]) print names # => [6306819796133686941, 8135353348168144921, -1228887169324443034]
Моё решение:
names = [‘Маша’, ‘Петя’, ‘Вася’] secret_names = map(hash, names)
Reduce
Reduce принимает функцию и набор пунктов.
Возвращает значение, получаемое комбинированием всех пунктов.
Пример простого reduce. Возвращает сумму всех пунктов в наборе:
sum = reduce(lambda a, x: a + x, [0, 1, 2, 3, 4]) print sum # => 10
x – текущий пункт, а – аккумулятор. Это значение, которое возвращает выполнение lambda на предыдущем пункте. reduce() перебирает все значения, и запускает для каждого lambda на текущих значениях а и х, и возвращает результат в а для следующей итерации.
А чему равно а в первой итерации? Оно равно первому элементу коллекции, и reduce() начинает работать со второго элемента. То есть, первый х будет равен второму предмету набора.
Следующий пример считает, как часто слово «капитан» встречается в списке строк:
sentences = [‘капитан джек воробей’, ‘капитан дальнего плавания’, ‘ваша лодка готова, капитан’] cap_count = 0 for sentence in sentences: cap_count += sentence.count(‘капитан’) print cap_count # => 3
Тот же код с использованием reduce:
sentences = [‘капитан джек воробей’, ‘капитан дальнего плавания’, ‘ваша лодка готова, капитан’] cap_count = reduce(lambda a, x: a + x.count(‘капитан’), sentences, 0)
А откуда здесь берётся начальное значение а? Оно не может быть вычислено из количества повторений в первой строке. Поэтому оно задаётся как третий аргумент функции reduce().
Почему map и reduce лучше?
Во-первых, они обычно укладываются в одну строку.
Во-вторых, важные части итерации,– коллекция, операция и возвращаемое значение,– всегда находятся в одном месте map и reduce.
В-третьих, код в цикле может изменить значение ранее определённых переменных, или влиять на код, находящийся после него. По соглашению, map и reduce – функциональны.
В-четвёртых, map и reduce – элементарные операции. Вместо построчного чтения циклов читателю проще воспринимать map и reduce, встроенные в сложные алгоритмы.
В-пятых, у них есть много друзей, позволяющих полезное, слегка изменённое поведение этих функций. Например, filter, all, any и find.
Упражнение 2: перепишите следующий код, используя map, reduce и filter. Filter принимает функцию и коллекцию. Возвращает коллекцию тех вещей, для которых функция возвращает True.
people = [, , ] height_total = 0 height_count = 0 for person in people: if ‘рост’ in person: height_total += person[‘ рост ‘] height_count += 1 if height_count > 0: average_height = height_total / height_count print average_height # => 120
Моё решение:
people = [, , ] heights = map(lambda x: x[‘рост’], filter(lambda x: ‘рост’ in x, people)) if len(heights) > 0: from operator import add average_height = reduce(add, heights) / len(heights)
Пишите декларативно, а не императивно
Следующая программа эмулирует гонку трёх автомобилей. В каждый момент времени машина либо двигается вперёд, либо нет. Каждый раз программа выводит пройденный автомобилями путь. Через пять промежутков времени гонка заканчивается.
from random import random time = 5 car_positions = [1, 1, 1] while time: # decrease time time -= 1 print » for i in range(len(car_positions)): # move car if random() > 0.3: car_positions[i] += 1 # draw car print ‘-‘ * car_positions[i]
Код императивен. Функциональная версия была бы декларативной – она бы описывала, что нужно сделать, а не то, как это надо сделать.
Используем функции
Декларативности можно достичь, вставляя код в функции:
from random import random def move_cars(): for i, _ in enumerate(car_positions): if random() > 0.3: car_positions[i] += 1 def draw_car(car_position): print ‘-‘ * car_position def run_step_of_race(): global time time -= 1 move_cars() def draw(): print » for car_position in car_positions: draw_car(car_position) time = 5 car_positions = [1, 1, 1] while time: run_step_of_race() draw()
Для понимания программы читатель просматривает основной цикл. «Если осталось время, пройдём один шаг гонки и выведем результат. Снова проверим время». Если читателю надо будет разобраться, как работает шаг гонки, он сможет прочесть его код отдельно.
Комментарии не нужны, код объясняет сам себя.
Разбиение кода на функции делает код более читаемым. Этот приём использует функции, но лишь как подпрограммы. Они упаковывают код, но не делают его функциональным. Функции влияют на окружающий их код и меняют глобальные переменные, а не возвращают значения. Если читатель встречает переменную, ему потребуется найти, откуда она взялась.
Вот функциональная версия этой программы:
from random import random def move_cars(car_positions): return map(lambda x: x + 1 if random() > 0.3 else x, car_positions) def output_car(car_position): return ‘-‘ * car_position def run_step_of_race(state): return def draw(state): print » print ‘n’.join(map(output_car, state[‘car_positions’])) def race(state): draw(state) if state[‘time’]: race(run_step_of_race(state)) race()
Теперь код разбит на функциональные функции. Тому есть три признака. Первый – нет расшаренных переменных. time и car_positions передаются прямиком в race().
Второе – функции принимают параметры. Третье – переменные не меняются внутри функций, все значения возвращаются. Каждый раз, когда run_step_of_race() проделывает следующий шаг, он передаётся опять в следующий.
Вот вам две функции zero() и one():
def zero(s): if s[0] == «0»: return s[1:] def one(s): if s[0] == «1»: return s[1:]
zero() принимает строку s. Если первый символ – 0, то возвращает остаток строки. Если нет – тогда None. one() делает то же самое, если первый символ – 1.
Представим функцию rule_sequence(). Она принимает строку и список из функций-правил, состоящий из функций zero и one. Она вызывает первое правило, передавая ему строку. Если не возвращено None, то берёт возвращённое значение и вызывает следующее правило. И так далее. Если возвращается None, rule_sequence() останавливается и возвращает None.
Иначе – значение последнего правила.
Примеры входных и выходных данных:
print rule_sequence(‘0101’, [zero, one, zero]) # => 1 print rule_sequence(‘0101’, [zero, zero]) # => None
Императивная версия rule_sequence():
def rule_sequence(s, rules): for rule in rules: s = rule(s) if s == None: break return s
Упражнение 3. Этот код использует цикл. Перепишите его в декларативном виде с использованием рекурсии.
Моё решение:
def rule_sequence(s, rules): if s == None or not rules: return s else: return rule_sequence(rules[0](s), rules[1:])
Используйте конвейеры (pipelines)
Теперь перепишем другой вид циклов при помощи приёма под названием конвейер.
Следующий цикл изменяет словари, содержащие имя, неправильную страну происхождения и статус некоторых групп.
bands = [, , ] def format_bands(bands): for band in bands: band[‘country’] = ‘Canada’ band[‘name’] = band[‘name’].replace(‘.’, ») band[‘name’] = band[‘name’].title() format_bands(bands) print bands # => [, # , # ]
Название функции «format» слишком общее. И вообще, код вызывает некоторое беспокойство. В одном цикле происходят три разные вещи. Значение ключа ‘country’ меняется на ‘Canada’. Убираются точки и первая буква имени меняется на заглавную.
Сложно понять, что код должен делать, и сложно сказать, делает ли он это. Его тяжело использовать, тестировать и распараллеливать.
print pipeline_each(bands, [set_canada_as_country, strip_punctuation_from_name, capitalize_names])
Всё просто. Вспомогательные функции выглядят функциональными, потому что они связаны в цепочку. Выход предыдущей – вход следующей. Их просто проверить, использовать повторно, проверять и распараллеливать.
pipeline_each() перебирает группы по одной, и передаёт их функциям преобразования, вроде set_canada_as_country(). После применения функции ко всем группам, pipeline_each() делает из них список и передаёт следующей.
Посмотрим на функции преобразования.
def assoc(_d, key, value): from copy import deepcopy d = deepcopy(_d) d[key] = value return d def set_canada_as_country(band): return assoc(band, ‘country’, «Canada») def strip_punctuation_from_name(band): return assoc(band, ‘name’, band[‘name’].replace(‘.’, »)) def capitalize_names(band): return assoc(band, ‘name’, band[‘name’].title())
Каждая связывает ключ группы с новым значением. Без изменения оригинальных данных это тяжело сделать, поэтому мы решаем это с помощью assoc(). Она использует deepcopy() для создания копии переданного словаря. Каждая функция преобразовывает копию и возвращает эту копию.
Всё вроде как нормально. Оригиналы данных защищены от изменений. Но в коде есть два потенциальных места для изменений данных. В strip_punctuation_from_name() создаётся имя без точек через вызов calling replace() с оригинальным именем. В capitalize_names() создаётся имя с первой прописной буквой на основе title() и оригинального имени.
Если replace и time не функциональны, то и strip_punctuation_from_name() с capitalize_names() не функциональны.
К счастью, они функциональны. В Python строки неизменяемы. Эти функции работают с копиями строк. Уфф, слава богу.
Такой контраст между строками и словарями (их изменяемостью) в Python демонстрирует преимущества языков типа Clojure. Там программисту не надо думать, не изменит ли он данные. Не изменит.
Упражнение 4. Попробуйте сделать функцию pipeline_each. Задумайтесь над последовательностью операций. Группы – в массиве, передаются по одной для первой функции преобразования. Затем полученный массив передаётся по одной штучке для второй функции, и так далее.
Моё решение:
def pipeline_each(data, fns): return reduce(lambda a, x: map(x, a), fns, data)
Все три функции преобразования в результате меняют конкретное поле у группы. call() можно использовать, чтобы создать абстракцию для этого. Она принимает функцию и ключ, к которому она будет применена.
set_canada_as_country = call(lambda x: ‘Canada’, ‘country’) strip_punctuation_from_name = call(lambda x: x.replace(‘.’, »), ‘name’) capitalize_names = call(str.title, ‘name’) print pipeline_each(bands, [set_canada_as_country, strip_punctuation_from_name, capitalize_names])
Или, жертвуя читаемостью:
print pipeline_each(bands, [call(lambda x: ‘Canada’, ‘country’), call(lambda x: x.replace(‘.’, »), ‘name’), call(str.title, ‘name’)])
def assoc(_d, key, value): from copy import deepcopy d = deepcopy(_d) d[key] = value return d def call(fn, key): def apply_fn(record): return assoc(record, key, fn(record.get(key))) return apply_fn
Что тут у нас происходит.
Один. call – функция высшего порядка, т.к. принимает другую функцию как аргумент и возвращает функцию.
Два. apply_fn() похожа на функции преобразования. Получает запись (группу). Ищет значение record[key]. Вызывает fn. Присваивает результат в копию записи и возвращает её.
Три. call сам ничего не делает. Всю работу делает apply_fn(). В примере использования pipeline_each(), один экземпляр apply_fn() задаёт ‘country’ значение ‘Canada’. Другой – делает первую букву прописной.
Четыре. При выполнении экземпляра apply_fn() функции fn и key не будут доступны в области видимости. Это не аргументы apply_fn() и не локальные переменные. Но доступ к ним будет. При определении функции она сохраняет ссылки на переменные, которые она замыкает – те, что были определены снаружи функции, и используются внутри.
При запуске функции переменные ищутся среди локальных, затем среди аргументов, а затем среди ссылок на замкнутые. Там и найдутся fn и key.
Пять. В call нет упоминания групп. Это оттого, что call можно использовать для создания любых конвейеров, независимо от их содержимого. Функциональное программирование, в частности, строит библиотеку общих функций, пригодных для композиций и для повторного использования.
Молодцом. Замыкания, функции высшего порядка и область видимости – всё в нескольких параграфах. Можно и чайку с печеньками выпить.
Остаётся ещё одна обработка данных групп. Убрать всё, кроме имени и страны. Функция extract_name_and_country():
def extract_name_and_country(band): plucked_band = <> plucked_band[‘name’] = band[‘name’] plucked_band[‘country’] = band[‘country’] return plucked_band print pipeline_each(bands, [call(lambda x: ‘Canada’, ‘country’), call(lambda x: x.replace(‘.’, »), ‘name’), call(str.title, ‘name’), extract_name_and_country]) # => [, # , # ]
extract_name_and_country() можно было бы написать в обобщённом виде под названием pluck(). Использовалась бы она так:
print pipeline_each(bands, [call(lambda x: ‘Canada’, ‘country’), call(lambda x: x.replace(‘.’, »), ‘name’), call(str.title, ‘name’), pluck([‘name’, ‘country’])])
Упражнение 5. pluck принимает список ключей, которые надо извлечь из записей. Попробуйте её написать. Это буде функция высшего порядка.
Моё решение:
def pluck(keys): def pluck_fn(record): return reduce(lambda a, x: assoc(a, x, record[x]), keys, <>) return pluck_fn
Функциональный код хорошо сочетается с традиционным. Преобразования из этой статьи можно использовать в любом языке. Попробуйте и вы для своего кода.
Вспомните про Машу, Петю и Васю. Превратите итерации по спискам в map и reduces.
Вспомните гонки. Разбейте код на функции, и сделайте их функциональными. Превратите цикл в рекурсию.
Вспомните про группы. Превратите последовательность операций в конвейер.
- Python
- Программирование
- Функциональное программирование
Источник: habr.com