Архив метки: программирование

Запись файлов с помощью Python



























5/5 — (2 голоса)

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

Основы записи файлов в Python

Общие методы для работы с файлами: open(), чтобы открыть файл, seek(), чтобы установить текущую позицию файла на заданное смещение, и close(), чтобы закрыть файловый объект, когда вы закончите его использовать. Метод open() возвращает ссылку на файл, представляющий файловый объект, чтобы получить доступ к чтению, записи или добавлению.

Запись в файл требует несколько решений — имя файла, куда записывать данные, и режим доступа к файлу. Доступны два режима: запись в новый файл (и перезапись любых существующих данных) и добавление данных в конец уже существующего файла. Соответствующие сокращения — “w” и “a”, их нужно определить перед открытием файла.

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

Запись одной строки в файл

Первый пример похож на запись файлов в двух популярных языках программирования C и C++, что вы увидите в листинге 1. Процесс довольно прямолинеен. Сначала мы открываем файл, используя метод open() для записи, пишем одиночную строку текста в файл с помощью метода write(), а затем закрываем файл с помощью метода close(). Имейте в виду, что из-за способа, которым мы открыли файл “helloworld.txt”, он либо будет создан, если его не существовало, либо полностью перезаписан.

filehandle = open('helloworld.txt', 'w')  

filehandle.write('Hello, world!n')  

filehandle.close()  

Листинг 1

Весь процесс можно сократить, применив выражение withЛистинг 2 показывает, как это сделать. Как уже сказано ранее, из-за способа, которым мы открыли файл “helloworld.txt”, он либо будет создан, если его не существовало, либо полностью перезаписан.

with open('helloworld.txt', 'w') as filehandle:  

    filehandle.write('Hello, world!n')

Листинг 2

Запись списка строк в файл

В реальности файл содержит не одну строку, а гораздо больше. Поэтому содержимое файла хранится в списке, представляющем файловый буфер. Чтобы записать файл целиком, мы воспользуемся методом writelines()Листинг 3 даёт вам пример.

filehandle = open("helloworld.txt", "w")  

filebuffer = ["a first line of text", "a second line of text", "a third line"]  

filehandle.writelines(filebuffer)  

filehandle.close()  

Листинг 3

Запуск программы на Python показан в листинге 3. Используя команду cat, мы можем видеть, что файл “helloworld.txt” имеет следующее содержимое:

$ cat helloworld.txt

a first line of texta second line of texta third line  

Листинг 4

Это присходит потому, что метод writelines() не добавляет автоматически разделители строк, когда пишет файл. Листинг 5 показывает, как этого достичь, записывая каждую строку текста отдельно и добавляя разделитель “n”. Генератор каждой строки заменён на строку с разделителем. Также вы можете сформулировать это с помощью выражения with.

with open('helloworld.txt', 'w') as filehandle:  

    filebuffer = ["a line of text", "another line of text", "a third line"]

    filehandle.writelines("%sn" % line for line in filebuffer)

Листинг 5

Теперь выходной файл “helloworld.txt” выглядит нужным образом, как показано в листинге 6:

$ cat helloworld.txt

a first line of text  

a second line of text  

a third line  

Листинг 6

Что любопытно, есть способ перенаправить вывод в Python, чтобы печатать данные в файл. Listing 7 показывает, как это сделать в Python 2.x.

# определим имя выходного файла

filename = "helloworld.txt"



# определим содержимое

filecontent = ["Hello, world", "a second line", "and a third line"]



with open(filename, 'w') as filehandle:  

    # перенаправим вывод в файловый поток

    for line in filecontent:

        print >>filehandle, line

Листинг 7

В новейших версиях Python такой код больше не работает. Чтобы сделать подобное, мы должны использовать модуль sys. Он позволяет получить доступ к стандартному потоку вывода UNIX через sys.stdout, sys.stdin и sys.stderr. В таком случае мы сохраняем исходное значение потока вывода sys.stdout (строка 8 в коде ниже), передаём его обработчику выходного файла, (строка 15), печатаем данные как обычно (строка 18), и наконец возвращаем исходное значение выходного потока sys.stdout (line 21). Листинг 8 содержит пример кода.

# импортируем модуль sys

import sys



# определим имя выходного файла

filename = "helloworld.txt"



# сохраним поток stdout

original = sys.stdout



# определим содержимое

filecontent = ["Hello, world", "a second line", "and a third line"]



with open(filename, 'w') as filehandle:  

    # устанавливает новое значение выходного потока

    sys.stdout = filehandle



    for line in filecontent:

        print(line)



    # возвращаем исходное значение выходного потока

    sys.stdout = original

Листинг 8

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

Добавление данных в конец файла

До сих пор мы сохраняли данные в новых файлах или полностью перезаписывали существующие. Но что, если мы хотим добавить данные в конец имеющегося файла? В таком случае нам нужно изменить режим доступа на ‘a’ вместо ‘w’. Листинг 9 показывает, как с этим работать. А листинг 10 делает то же самое, но используя выражение with.

filehandle = open('helloworld.txt','a')  

filehandle.write('n' + 'Hello, world!n')  

filehandle.close()  

Листинг 9

with open('helloworld.txt', 'a') as filehandle:  

    filehandle.write('n' + 'Hello, world!n')

Листинг 10

С прежним файлом helloworld.txt запуск этого кода даст следующий результат:

$ cat helloworld.txt

Hello, world  

a second line  

and a third line



Hello, world!  

Вывод

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


2018-04-14T12:00:01
Программирование

Чтение файлов с помощью Python



























5/5 — (2 голоса)

Чтобы работать с сохранёнными данными, каждому программисту на Python необходимо знать про обработку файлов. Начиная с самого раннего выпуска, запись и чтение файлов встроены в Python. Если сравнить с другими языками типа С или Java, это довольно просто и требует немного строк кода. Более того, для этого не нужно загружать никаких дополнительных модулей.

Основы работы с файлами в Python

Общие методы для работы с файлами: open(), чтобы открыть файл, seek(), чтобы установить текущую позицию файла на заданное смещение, и close(), чтобы закрыть файловый объект, когда вы закончите его использовать. Метод open() возвращает ссылку на файл, представляющий файловый объект, чтобы получить доступ к чтению, записи или добавлению.

Когда открывается файл для чтения, Python нужно точно знать, как следует открыть файл в системе. Доступны два способа  — обычное чтение и чтение в двоичном режиме. Соответствующие флаги имеют вид  rи rbи должны быть определены, когда файл открывается встроенным методом open(). Первый режим включает в себя интерпретацию специальных символов вроде “CR” (возврат каретки) and “LF” (перенос строки), чтобы отобразить разрыв строки, тогда как двоичный режим позволяет вам считывать данные в сыром виде — где данные хранятся как есть безо всякой дальнейшей интерпретации.

Как только вы открыли файл, методopen()вернёт вам файловый объект. Эти файловые объекты содержат методы наподобиеread(), readline(), write(), tell()и seek(). Хотя сами они (или подобные объекты) имеют больше методов, чем указано здесь, эти встречаются наиболее часто. Не каждый файловый объект нуждаются в имплементации всех методов работы с файлами.

Примеры

В этой статье мы будем объяснять, как читать файлы с помощью Python, через примеры. Некоторые из них включают построчное чтение, кусками (определённое количество строк за раз) и чтение в один заход. Также мы вам покажем способ прочесть отдельную строку из файла без поиска по всему файлу.

Построчное чтение файла

Первый пример вдохновлён двумя языками программирования: C и C++. Он довольно простой: открываем файл с помощью метода open(), читаем файл построчно, используя метод readline(), и выводим строку сразу же после чтения. Здесь применяется цикл while, который непрерывно читает из файла, пока метод readline() продолжает возвращать данные. В случае конца файла (EOF) цикл while останавливается, и файловый объект закрывается, освобождая ресурсы для других программ.

# определим имя файла, который читаем

filename = "test.txt"



# открываем файл для чтения

filehandle = open(filename, 'r')  

while True:  

    # читаем одну строку

    line = filehandle.readline()

    if not line:

        break

    print(line)



# закрываем указатель на этот файл

filehandle.close()  

Листинг 1

Как вы могли заметить, в листинге 1 мы явно открыли и закрыли файл (строки 5 и 14 соответственно). Хотя интерпретатор Python закрывает открытые файлы автоматически в конце исполнения программы, явно закрывать файл через close() есть хороший стиль программирования, и это не следует забывать.

В качестве улучшения в языке Python 2.3 был введён удобный протокол итераторов. Это позволяет вам упростить цикл readline следующим образом:

# определим имя файла, который читаем

filename = "test.txt"



for line in open(filename, 'r'):  

    print(line)

Листинг 2

Здесть применяется цикл for в комбинации с итератором in. Файл открывается в строке 4 листинга 2. Текущая строка определяется с помощью итератора in, считывается из файла, и её содержимое подаётся на выход stdout в строке 5. Python скрывает открытие и закрытие файла, когда закончится исполнение. Хотя такой способ неэффективен, он позволяет вам больше не иметь дело с файловым потоком.

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

# определим имя файла, который читаем

filename = "test.txt"



with open(filename, 'r') as filehandle:  

    for line in filehandle:

        print(line)

Листинг 3

Комбинация оператора with и команды open() открывает файл только один раз (строка 4). Если цикл for завершён успешно, содержимое файла напечатано в поток stdout (строки 5 и 6).

Более того, использование выражения with даёт побочный эффект. Внутри интерпретатора Python создаётся блок tryfinally, чтобы инкапсулировать чтение из файла. Листинг 4 показывает, что неизбежно случается внутри в Python с помощью блоков кодаwith:

try:  

    filehandle = open(filename, 'r')

    # что-то делаем

finally:  

    filehandle.close()

Листинг 4

Чтение файла как набора строк

До сих пор мы обрабатывали файл построчно. Это довольно медленный способ для огромных файлов, но его можно улучшить, если читать несколько строк одновременно. Чтобы этого добиться, в дело вступает метод islice() из модуля itertools. Также он работает как итератор и возвращает набор данных, состоящий из n строк. В конце файла результат может быть короче, и завершающий вызов вернёт пустой список.

from itertools import islice



# определим имя файла, который читаем

filename = "test.txt"



# определим, сколько строк нужно прочесть

number_of_lines = 5



with open(filename, 'r') as input_file:  

    lines_cache = islice(input_file, number_of_lines)



    for current_line in lines_cache:

        print (current_line)

Листинг 5

Чтение определённой строки из файла

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

# определим имя файла, который читаем

filename = "test.txt"



# определим номер строки

line_number = 3



print ("line %i of %s is: " % (line_number, filename))



with open(filename, 'r') as filehandle:  

current_line = 1  

    for line in filehandle:

        if current_line == line_number:

            print(line)

            break

        current_line += 1

Листинг 6

Листинг 6 достаточно просто понять, но он немного больше, чем предыдущие примеры. Его можно сократить, используя модуль linecacheЛистинг 7 поазывает, как упростить код с помощью метода getline(). Если запрашиваемая строка выпадает из ряда валидных строк в файле, то метод getline() взамен возвращает пустую строку.

# импортируем модуль linecache

import linecache



# определим имя файла, который читаем

filename = "test.txt"



# определим номер строки

line_number = 3



# получим определённую строку

line = linecache.getline(filename, line_number)  

print ("line %i of %s:" % (line_number, filename))  

print (line)  

Листинг 7

Чтение из всего файла целиком

В заключение мы посмотрим на совершенно другой случай по сравнению с предыдущим примером — прочитаем файл за один раз. Имейте в виду, что во многих случаях вам нужно достаточно места на компьютере, чтобы прочесть файл в память целиком. Листинг 8 использует комбинацию выражения with и метода read(). В этом случае мы применим read(), чтобы загрузить содержимое файла как поток данных.

# определим имя файла, который читаем

filename = "test.txt"



with open(filename, 'r') as filehandle:  

    filecontent = filehandle.read()

    print (filecontent)

Листинг 8

Python также предлагает метод readlines(), похожий на метод readline() из первого примера. В отличие от read(), содержимое файла сохраняется в список, где каждый элемент есть строка содержимого. Листинг 9 показывает, как получить доступ к этим данным:

# определим имя файла, который читаем

filename = "test.txt"



with open(filename, 'r') as filehandle:  

    filecontent = filehandle.readlines()

    for line in filecontent:

        print (line)

Листинг 9

Хотя readlines() будет читать содержимое из файла, пока не наткнётся на EOF (конец файла), не забывайте, что вы также можете ограничить количество прочитанного параметром sizehint, — числом байт, которое нужно прочесть.

Вывод

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

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


2018-04-12T12:00:10
Программирование

Генератор (собиратор) описаний функций PHP

Читать

Метаклассы и метапрограммирование в Python



























4.5/5 — (2 голоса)

Представьте себе, что у вас есть компьютерные программы, которые пишут код за вас. Это возможно, но машины не напишут весь ваш код!

Эта методика, именуемая метапрограммированием, популярна среди разработчиков фреймворков. Так вы получаете генерацию кода и умные возможности во многих распространённых фреймворках и библиотеках, таких как Ruby On Rails или TensorFlow.

Языки фукционального программирования, такие как Elixir, Clojure и Ruby, известны своими возможностями метапрограммирования. В этом руководстве мы покажем, как вы можете использовать мощь метапрограммирования в Python.

Примеры кода написаны для Python 3, но будут работать на Python 2 с некоторыми исправлениями.

Что такое метакласс в Python?

Python – это объектно-оринтированный язык, который облегчает работу с классами.

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

Википедия довольно хорошо описывает метаклассы:

Метакласс (англ. Metaclass) — в объектно-ориентированном программировании это класс, экземпляры которого в свою очередь являются классами.

Когда мы определяем класс, его объекты создаются, используя класс как пример. Но как насчёт самого класса? Что есть этот самый пример для класса?

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

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

# hello_metaclass.py

# Простой метакласс

# Этот метакласс добавляет метод 'hello' к классам, использующим его значение,

# те классы получают метод 'hello' без лишних усилий

# метакласс заботится о генерации кода для нас

class HelloMeta(type):  

    # Метод hello

    def hello(cls):

        print("greetings from %s, a HelloMeta type class" % (type(cls())))



    # Вызываем метакласс

    def __call__(self, *args, **kwargs):

        # создаём новый класс как обычно

        cls = type.__call__(self, *args)



        # определяем новый метод hello для каждого из этих классов

        setattr(cls, "hello", self.hello)



        # возвращаем класс

        return cls



# Проверяем метакласс

class TryHello(object, metaclass=HelloMeta):  

    def greet(self):

        self.hello()



# Создаём экземпляр метакласса. Он должен автоматически содержать метод hello

# хотя он не объявлен в классе вручную

# иными словами, он объявлен за нас метаклассом

greeter = TryHello()  

greeter.greet()

В результате запуска этого кода новый класс TryHello способен напечатать приветствие:

greetings from <class '__main__.TryHello'>, a HelloMeta type class

Метод, ответственный за этот вывод, не объявлен в декларации класса. Вместо этого метакласс, в данном случае HelloMeta, порождает код во время запуска, что сразу связывает этот метод с классом.

Чтобы увидеть это в действии, смело копируйте код в консоль Python. Также прочтите комментарии, чтобы лучше понимать, что мы сделали в каждой части кода. У нас есть новый объект по имени greeter, который является сущностью класса TryHello. Впрочем, мы можем вызвать метод self.hello класса TryHello, хотя этот метод не определён в объявлении класса TryHello.

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

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

Это пример метапрограммирования. Метапрограммирование — это просто написание кода, который работает с метаклассами и схожие методики изменения кода на заднем плане.

Прекрасно в метапрограммировании то, что вместо вывода исходного кода, оно даёт нам лишь исполнение этого кода. Пользователь нашей программы не знает о “магии”, происходящей за кадром.

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

Помимо языка Python, другие библиотеки вроде Ruby On Rails(Ruby) и Boost(C++) служат примерами того, как метапрограммирование используется авторами фреймворков, чтобы неявно порождать код и заботиться обо всем. В результате получаем упрощённые пользовательские API, которые автоматизируют много работы за программиста, пишущего код фреймворка.

Обеспечить, чтобы простота работала за кадром – как раз то, для чего метапрограммирование встроено в исходный код фреймворков.

Немного теории: разберёмся, как работают метаклассы

Чтобы понять, как работают метаклассы в Python, Вам нужно очень хорошо понимать нотацию типов. Тип – это просто номенклатура данных или объекта в Python.

Найти тип объекта

Используя Python REPL (командный интерпретатор), давайте создадим простой строковый объект и проверим его тип:

>>> day = "Sunday"

>>> print("The type of variable day is %s" % (type(day)))

The type of variable day is <type 'str'>  

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

Найти тип класса

Так, строка вроде "Sunday" или "hello" имеет тип str, но как насчёт самого str ? Какой тип у класса str ?

Опять введём в консоль Python:

>>> type(str)

<type 'type'>  

В этот раз мы получаем вывод: str принадлежит типу type.

Тип и тип его типа

Что скажем про сам type? Каков тип type?

>>> type(type)

<type 'type'>  

В результате снова получаем “type”. Так мы находим, что type есть не только метакласс для типов наподобие int, но и свой собственный метакласс!

Специальные методы, которые используются метаклассами

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

Тип type, как метакласс по умолчанию в Python, определяет особые методы, которые новый метакласс может переписать, чтобы создать поведение уникального кода. Вот краткий обзор этих “волшебных” методов, существующих в метаклассе:

  • __new__: этот метод вызывается в метаклассе до того, как создаётся сущность класса, на нём основанного
  • __init__: этот метод вызывается, чтобы установить переменные после создания сущности/объекта
  • __prepare__: определяет пространство имён класса в отображении, сохраняющием атрибуты
  • __call__: этот метод вызывается, когда конструктор нового класса нужно использовать для создания оъекта

Вот методы, переопределение которых в вашем метаклассе даст вашим классам поведение, отличное от типа typeметакласса по умолчанию.

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

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

Декоратор есть функция, которая изменяет исполнение функции. Иными словами, он принимает функцию на вход и возвращает другую функцию.

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

# decorators.py



from functools import wraps



# Создаём новый декоратор по имени notifyfunc

def notifyfunc(fn):  

    """печатает имя функции перед её исполнением"""

    @wraps(fn)

    def composite(*args, **kwargs):

        print("Executing '%s'" % fn.__name__)

        # Запускаем исходную функцию и возвращаем результат

        rt = fn(*args, **kwargs)

        return rt

    # Возвращаем нашу сложную функцию

    return composite



# Применяем наш декоратор к обычной функции, которая печатает произведение своих аргументов

@notifyfunc

def multiply(a, b):  

    product = a * b

    return product

Вы можете скопировать и вставить этот код в Python REPL. Главное в использовании декораторов – то, что исполняется составная функция вместо входной. В результате исполнения следующего кода функция умножения объявляет о своём запуске перед началом вычислений:

>>> multiply(5, 6)

Executing 'multiply'  

30  

>>>

>>> multiply(89, 5)

Executing 'multiply'  

445  

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

Практика метапрограммирования 2: использование метаклассов как функций-декораторов

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

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

# metaclassdecorator.py

import types



# Функция, которая возвращает имя поступающей на вход функции и возвращает новую функцию

# инкапсулирующию поведение исходной функции

def notify(fn, *args, **kwargs):



    def fncomposite(*args, **kwargs):

        # Обычная функциональность notify

        print("running %s" % fn.__name__)

        rt = fn(*args, **kwargs)

        return rt

    # Возвращаем сложную функцию

    return fncomposite



# Метакласс, меняющий поведение своих классов

# на новые методы, 'дополненные' поведением преобразователя сложной функции

class Notifies(type):



    def __new__(cls, name, bases, attr):

        # Заменим каждую функцию на выражение, которое печатает имя функции

        # перед запуском вычисления с предоставленными args и возвращает его результат

        for name, value in attr.items():

            if type(value) is types.FunctionType or type(value) is types.MethodType:

                attr[name] = notify(value)



        return super(Notifies, cls).__new__(cls, name, bases, attr)



# Проверим метакласс

class Math(metaclass=Notifies):  

    def multiply(a, b):

        product = a * b

        print(product)

        return product



Math.multiply(5, 6)



# Запуск multiply():

# 30





class Shouter(metaclass=Notifies):  

    def intro(self):

        print("I shout!")



s = Shouter()  

s.intro()



# Запуск intro():

# I shout!

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

Пример метаклассов 1: объявление класса, от которого нельзя унаследоваться

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

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

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

# final.py



# финальный метакласс. Унаследоваться от класса, имеющего свои метаклассом Final, не получится

class Final(type):  

    def __new__(cls, name, bases, attr):

        # от финального класса нельзя унаследоваться

        # проверим, что класс Final не выступает в качестве базового

        # если так, укажем об ошибке, иначе создадим новый класс с атрибутами Final

        type_arr = [type(x) for x in bases]

        for i in type_arr:

            if i is Final:

                raise RuntimeError("You cannot subclass a Final class")

        return super(Final, cls).__new__(cls, name, bases, attr)





# Тест: применим метакласс, чтобы создать финальный класс Cop



class Cop(metaclass=Final):  

    def exit():

        print("Exiting...")

        quit()



# Попытка создать класс Cop, в идеале следует возбудить исключение!

class FakeCop(Cop):  

    def scam():

        print("This is a hold up!")



cop1 = Cop()  

fakecop1 = FakeCop()



# Больше тестов, другой класс Final

class Goat(metaclass=Final):  

    location = "Goatland"



# Унаследоваться от финального класса не получится

class BillyGoat(Goat):  

    location = "Billyland"

В этом коде мы ввели объявления классов, чтобы попробовать унаследоваться от класса Final. Эти попытки провалились, в результате было выброшено исключение. Использование метаклассов, что ограничивают наследование, позволяет нам объявить финальные классы в нашей кодовой базе.

Пример метаклассов 2: создание класса, отслеживающего время исполнения

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

Мы можем использовать метакласс, чтобы следить за временем исполнения кода. Наш пример кода не совсем профилировщик, но он показывает, как можно метапрограммированием решать подобные задачи.

# timermetaclass.py	 	 

import types	 	 

# Класс функций времени	 	 

import time	 	 

class Timer: 	 	 

 def __init__(self, func=time.perf_counter):	 	 

 self.elapsed = 0.0	 	 

 self._func = func	 	 

 self._start = None	 	 

 def start(self):	 	 

 if self._start is not None:	 	 

 raise RuntimeError('Already started')	 	 

 self._start = self._func()	 	 

 def stop(self):	 	 

 if self._start is None:	 	 

 raise RuntimeError('Not started')	 	 

 end = self._func()	 	 

 self.elapsed += end - self._start	 	 

 self._start = None	 	 

 def reset(self):	 	 

 self.elapsed = 0.0	 	 

 @property	 	 

 def running(self):	 	 

 return self._start is not None	 	 

 def __enter__(self):	 	 

 self.start()	 	 

 return self	 	 

 def __exit__(self, *args):	 	 

 self.stop()	 	 

# Далее мы создаём метакласс Timed, который считает время работы своих методов	 	 

# вместе с функциями установки, которые переписывают методы классa	 	 

# времена создания классов	 	 

# Функция, засекающая время исполнения встроенной функции, возвращает новую,	 	 

# инкапсулируя поведение исходной функции	 	 

def timefunc(fn, *args, **kwargs):	 	 

 def fncomposite(*args, **kwargs):	 	 

 timer = Timer()	 	 

 timer.start()	 	 

 rt = fn(*args, **kwargs)	 	 

 timer.stop()	 	 

 print("Executing %s took %s seconds." % (fn.__name__, timer.elapsed))	 	 

 return rt	 	 

 # возвращает сложную функцию	 	 

 return fncomposite



# Метакласс 'Timed', который заменяет методы своих классов	

# с новым методами 'timed' на поведение сложной функции-преобразователя	 	 

class Timed(type):	 	 

 def __new__(cls, name, bases, attr):	 	 

 # меняет каждую функцию на новую, время которой замеряется	 	 

 # запускает вычисление с заданными args и возвращает результат	 	 

 for name, value in attr.items():	 	 

 if type(value) is types.FunctionType or type(value) is types.MethodType:	 	 

 attr[name] = timefunc(value)	 	 

 return super(Timed, cls).__new__(cls, name, bases, attr)	 	 

# Следующий пример кода проверяет метакласс	 	 

# Классы, применяющие метакласс Timed, следует замерить за нас автоматически	 	 

# проверьте результат в REPL	 	 

class Math(metaclass=Timed):	 	 

 def multiply(a, b):	 	 

 product = a * b	 	 

 print(product)	 	 

 return product	 	 

Math.multiply(5, 6)	 	 

class Shouter(metaclass=Timed):	 	 

 def intro(self):	 	 

 print("I shout!")	 	 

s = Shouter() 	 	 

s.intro()	 	 

def divide(a, b): 	 	 

 result = a / b	 	 

 print(result)	 	 

 return result	 	 

div = timefunc(divide) 	 	 

div(9, 3)

Выводы

Как можно видеть, мы смогли создать метакласс Timed, котоый переписывает сови классы на лету. Когда бы ни объявлялся новый класс, использующийTimed, его методы переписываются, чтобы наш класс замерял время его работы. Когда бы мы ни запускали вычисления с помощью класса Timed , замеры делаются за нас сами, без нужды делать что-либо ещё.

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


2018-03-14T12:43:04
Программирование

BASH. (Пере) подключение к WIFI

Мой небольшой нетбук на Linux работает 24/7. Он выполняет сразу несколько периодических скриптов. Так уж получилось, что его обязанность нельзя перенести на VPS или какой-нибудь хостинг. Поэтому крайне необходимо, чтобы этот нетбук непрерывно функционировал. Но наличие устойчивой ОС не гарантирует стабильность работы. Читать

Linux. Запуск скрипта каждые N секунд

Некоторые программы требуют периодического запуска. Например, каждый 5 минут. Для этого можно воспользоваться планировщиком CRON. Но прописывать cron правило будет слишком хлопотным занятием, если надо проводить запуск только в ближайший час-два. Для этого лучше воспользоваться командой watch.

Читать