Работа с файлом 2

Что нужно сделать

Реализуйте модернизированную версию контекст-менеджера File:

  • теперь при попытке открыть несуществующий файл менеджер автоматически создаёт и открывает этот файл в режиме записи;
  • на выходе из менеджера подавляются все исключения, связанные с файлами.

Что оценивается

  • Результат вычислений корректен.
  • Модели реализованы в стиле ООП, основной функционал описан в методах классов и в отдельных функциях.
  • При написании классов соблюдаются основные принципы ООП: инкапсуляция, наследование и полиморфизм.
    • Для получения и установки значений у приватных атрибутов используются сеттеры и геттеры с соответствующими декораторами.
    • Для создания нового класса на основе уже существующего используется наследование.
    • Для статических и классовых методов используется декоратор classmethod.
  • Сообщения о процессе получения результата осмысленны и понятны для пользователя.
  • Переменные, функции и собственные методы классов имеют значащие имена, не a, b, c, d.
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращают, то используется None.
class File:
    '''Класс контекст-менеджера File'''

    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        try:
            self.file = open(self.filename, self.mode, encoding='UTF-8')
        except FileNotFoundError:
            print(f'Создан файл {self.filename}')
            self.file = open(self.filename, 'w', encoding='UTF-8')
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()
        if exc_type is FileNotFoundError or FileExistsError:
            return True

with File('doc.txt', 'r') as file:
    newfile = file.read()

Кэширование для ускорения вычислений

Контекст

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

Задача

Создайте декоратор, который кэширует (сохраняет для дальнейшего использования) результаты вызова функции и, при повторном вызове с теми же аргументами, возвращает сохранённый результат.

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

Советы

  • Для хранения результатов удобно использовать словарь, так как поиск элементов внутри словаря будет иметь сложность, равную в среднем O(1).
  • При этом не стоит хранить все вычисления в одном словаре, созданном снаружи функций (в глобальной области видимости). Лучше создавать отдельные словари для каждой декорируемой функции.
#Задача решается с помощью @lru_cache модуля functools

def number_cache(func):
    """Декоратор кэширует полученные ранее занчения и возвращает их"""
    cache = dict()
    def inner(*args, **kwargs):
        if args in cache:
            return cache[args]
        else:
            value = func(*args, **kwargs)
            cache[args] = value
            return value
    return inner

@number_cache
def fibonacci(number):
    if number <= 1:
        return number
    return fibonacci(number - 1) + fibonacci(number - 2)


# Вычисление числа Фибоначчи с использованием кеширования
print(fibonacci(10))  # Результат будет кеширован

# Повторное вычисление числа Фибоначчи с теми же аргументами
print(fibonacci(10))  # Результат будет взят из кеша

# Вычисление числа Фибоначчи с другим аргументом
print(fibonacci(5))  # Результат будет вычислен и закеширован

Счётчик

Что нужно сделать

Реализуйте декоратор counter, считающий и выводящий количество вызовов декорируемой функции.

Для решения задачи нельзя использовать операторы global и nonlocal (об этом мы ещё расскажем).

Что оценивается

  • Результат вычислений корректен.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.
  • Во всех декораторах используется functools.wraps.
def counter(func):
    """Декоратор считает количество вызовов функции"""

    def inner(*args, **kwargs):
        inner.count += 1
        result = func(*args, **kwargs)
        return result

    inner.count = 0
    return inner

@counter
def test1():
    pass

@counter
def test2():
    pass


test1()
test1()
test2()
test2()
test2()

print(test1.count)
print(test2.count)

Логирование

Что нужно сделать

Реализуйте декоратор logging, который будет отвечать за логирование функций. На экран выводится название функции и её документация. Если во время выполнения декорируемой функции возникла ошибка, то в файл function_errors.log записываются названия функции и ошибки.

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

Дополнительно: запишите дату и время возникновения ошибки, используя модуль datetime.

Что оценивается

  • Результат вычислений корректен.
  • Сообщения о процессе получения результата осмыслены и понятны для пользователя.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.
  • Во всех декораторах используется functools.wraps.
import datetime

def logging(func):
    """Декоратор выводит имя, документацию функций, и записывает ошибки в файл."""
    def inner(*args, **kwargs):
        try:
            print('Имя функции: ', func.__name__)
            print('Документация: ', func.__doc__)
            result = func(*args, **kwargs)
            return result
        except Exception as exception:
            with open('function_errors.log', 'a', encoding='UTF-8') as logfile:
                logfile.writelines(f'''Дата/время: {datetime.datetime.now()}
Функция: "{func.__name__}"
Ошибка: "{exception}"
\n''')

    return inner

@logging
def test1():
    """Документация"""
    print('Функция выполнена.')

@logging
def test2():
    #Документация
    print(Zzzzzzzzzzzz)

@logging
def test3():
    print('Функция выполнена.')

@logging
def test4():
    """Документация"""
    print(Zzzzz)

test1()
test2()
test3()
test4()

Замедление кода

Что нужно сделать

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

Реализуйте декоратор, который перед выполнением декорируемой функции ждёт несколько секунд.

Что оценивается

  • Результат вычислений корректен.
  • Сообщения о процессе получения результата осмыслены и понятны для пользователя.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.
  • Во всех декораторах используется functools.wraps.
import time

def time_delay(func):
    """Декоратор создает задержку времени запуска функции."""
    def inner(seconds: int = 3):
        for _ in range(seconds * 3):
            print('▓▓', end='')
            time.sleep(0.33)
        print('100%\n')
        result = func()
        return result
    return inner

@time_delay
def testfunc():
    print('Функция запущена!')

testfunc()

Как дела?

Что нужно сделать

Ваня что-то совсем заскучал на работе и решил побаловаться с кодом проекта. Он написал надоедливый декоратор, который при вызове декорируемой функции спрашивает у пользователя «Как дела?», вне зависимости от ответа отвечает что-то вроде «А у меня не очень!» и только потом запускает саму функцию. Правда, после такой выходки его чуть не уволили с работы.

Реализуйте такой же декоратор и проверьте его работу на нескольких функциях.

Пример кода:

@how_are_you
def test():
    print('<Тут что-то происходит...>')


test()

Результат:

Как дела? Хорошо.
А у меня не очень! Ладно, держи свою функцию.
<Тут что-то происходит...>

Что оценивается

  • Результат вычислений корректен.
  • Формат вывода соответствует примеру.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.
  • Во всех декораторах используется functools.wraps.
def how_are_you(func):
    def inner(*args, **kwargs):
        _ = input('Как дела? Ответ: ')
        print('А у меня не очень...')
        result = func(*args, **kwargs)
        return result
    return inner

@how_are_you
def test():
    print('Тут что-то происходит...')

test()

Обработка логов

Контекст

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

Задача

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

Требования

  • Используйте модуль os для работы с файлами и путями.
  • Учтите, что файл может быть очень большим по объёму, поэтому не загружайте его в память целиком.
  • Создайте функцию-генератор error_log_generator, которая будет получать на вход путь до файла с логами и возвращать строки из этого файла, которые содержат слово ERROR (одно обращение к генератору должно возвращать одну строку из файла).

Советы

  • Цикл for по файлу будет считывать в память ровно по одной строке из файла за итерацию.
  • Генератор должен возвращать только строки со словом ERROR. Другие строки, которые будут считываться из файла, нужно будет игнорировать (применять yield только к правильным строкам).
  • Для наглядного примера вы можете сгенерировать очень большой текстовый файл (для этого надо запустить код из файла text_generator.py) и попробовать загрузить его в память при помощи метода read(), применённого к этому файлу.
  • Учтите, что генерация файла такого размера может занять несколько десятков минут!
import os


# При помощи модуля os (и функции join) сформируйте пути до файлов work_logs.txt и output.txt в папке data
# (output.txt может не быть в папке data, но его нужно будет там создать, при помощи кода)
input_file_path = os.path.join('data', 'work_logs.txt')
output_file_path = os.path.join('data', 'output.txt')
# Документация по join https://docs-python.ru/standart-library/modul-os-path-python/funktsija-join-modulja-os-path/

# Не забудьте проверить наличие файлов перед тем как начать работу с ними
# https://docs-python.ru/standart-library/modul-os-path-python/funktsija-exists-modulja-os-path/

def error_log_generator(file_path):
    with open(file_path, 'r', encoding='UTF-8') as file:
        while True:
            line = file.readline()
            if 'ERROR' in line:
                yield line


with open(output_file_path, 'w') as output:
    for error_line in error_log_generator(input_file_path):
        output.write(error_line)
print("Файл успешно обработан.")

Односвязный список

Что нужно сделать

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

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

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

Реализуйте такую структуру данных без использования стандартных структур Python (list, dict, tuple и прочие) и дополнительных модулей. Для структуры реализуйте следующие методы:

  • append — добавление элемента в конец списка;
  • get — получение элемента по индексу;
  • remove — удаление элемента по индексу.

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

Пример основной программы:

my_list = LinkedList()
my_list.append(10)
my_list.append(20)
my_list.append(30)
print('Текущий список:', my_list)
print('Получение третьего элемента:', my_list.get(2))
print('Удаление второго элемента.')
my_list.remove(1)
print('Новый список:', my_list)

Результат:

Текущий список: [10 20 30]
Получение третьего элемента: 30
Удаление второго элемента.
Новый список: [10 30]

Что оценивается

  • Результат вычислений корректен.
  • Модели реализованы в стиле ООП, основной функционал описан в методах классов и в отдельных функциях.
  • При написании классов соблюдаются основные принципы ООП: инкапсуляция, наследование и полиморфизм.
    • Для получения и установки значений у приватных атрибутов используются сеттеры и геттеры.
    • Для создания нового класса на основе уже существующего используется наследование.
  • Формат вывода соответствует примеру.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.При поддержке статьи каждый найдет что-то для себя в нашей коллекции красочных, ярких и стильных носков. Покупайте по отдельности или в наборах, чтобы добавить цвета вашему sock ящик!
class LinkedList:
  class __Node:
    def __init__(self, value):
      self.value = value
      self.next = None

  def __init__(self):
    self.__length = 0
    self.__head = None
    self.__tail = self.__head

  def __iter__(self):
    current = self.__head
    while current is not None:
      yield current.value
      current = current.next

  def __str__(self):
    return f"[{' '.join(str(ix) for ix in self)}]"

  def __len__(self):
    return self.__length

  def __index_check(self, index):
    if not 0 <= index < self.__length:
      raise IndexError(f'Элемента с индексом {index} в списке нет.')

  def append(self, value):
    if self.__length > 0:
      self.__tail.next = self.__Node(value)
      self.__tail = self.__tail.next
    else:
      self.__head = self.__tail = self.__Node(value)
    self.__length += 1

  def get(self, index):
    self.__index_check(index)
    current = self.__head
    for _ in range(index):
      current = current.next
    return current.value

  def remove(self, index):
    self.__index_check(index)
    if self.__length == 1:
      self.__head = self.__tail = None
    elif index == 0:
      self.__head = self.__head.next
    else:
      current = self.__head
      for _ in range(index - 1):
        current = current.next
      current.next = current.next.next
      if index == self.__length - 1:
        self.__tail = current
    self.__length -= 1

my_list = LinkedList()
my_list.append(10)
my_list.append(20)
my_list.append(30)
print('Текущий список:', my_list)
print('Получение третьего элемента:', my_list.get(2))
print('Удаление второго элемента.')
my_list.remove(1)
print('Новый список:', my_list)

Количество строк

Что нужно сделать

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

Что оценивается

  • Результат вычислений корректен.
  • Input содержит корректные приглашения для ввода.
  • Сообщения о процессе получения результата осмыслены и понятны для пользователя.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.
import os

# функция-генератор возвращающая количество строк
# во всех обнаруженных py файлах
def search_py_gen(dir: str='D:\Python'):

    for walktuple in os.walk(dir):
        for file in walktuple[2]:
            if file.endswith('.py'):
                pathfile = fR'{dir}\{file}'
                yield linecounter(pathfile)

# функция по подсчету строк в файле
def linecounter(path):
    count = 0
    with open(path, 'r', encoding='UTF-8') as file:
        while True:
            for line in file:
                if line[0] != '#' and line[0] != '\n':
                    count += 1
            return count

lines = search_py_gen()

for i in lines:
    print(i)

Пути файлов

Что нужно сделать

Реализуйте функцию gen_files_path, которая рекурсивно проходит по всем каталогам указанной директории (по умолчанию — корневой диск), находит указанный пользователем каталог и генерирует пути всех встреченных файлов.

Подсказка:

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

Что оценивается

  • Результат вычислений корректен.
  • Сообщения о процессе получения результата осмыслены и понятны для пользователя.
  • Переменные, функции и собственные методы классов имеют значащие имена (не a, b, c, d).
  • Классы и методы/функции имеют прописанную документацию.
  • Есть аннотация типов для методов/функций и их аргументов (кроме args и kwargs). Если функция/метод ничего не возвращает, то используется None.
import os

def gen_files_path(name: str, directory: str='D:/'):

    for dirpath, dirnames, filenames in os.walk(directory):
        for dirname in dirnames:
            if dirname.lower() == name.lower():
                print(f'''Искомая папка: {dirpath}/{dirname}
Встретились файлы: ''')
                for i in filenames:
                    yield f'{dirpath}/{i}'