Оглавление

Объект

Интерфейс

Логика и реализация

Публичные методы

Приватные методы

Парадигма ООП

Наследование

Как задавать публичные и приватные методы

Как инкапсулировать поля

Как создавать @classmethod

Как выглядит пример полиморфной реализации

Dataclass

Создание датаклассов по словарям

Создание marshmallow схемы на основе dataclass

ValidationError

Meta

Обработка ValidationError

Кастомизация полей marshmallow

Объект

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

Интерфейс

для Python это набор публичных методов объекта или класса, через которые внешний мир может взаимодействовать и как-то влиять на внутреннее состояние объекта

Логика и реализация

логика подразумевает взаимодействие между различными объектами по определенным интерфейсам, а реализация — непосредственное взаимодействие с потоком, источником или хранилищем данных. **self** — ссылка на экземпляр класса. **cls** — ссылка на сам класс экземпляра (доступно только под декоратором @classmethod).

Публичные методы

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

Приватные методы

методы, при помощи которых реализовано взаимодействие внутри экземпляра.

Парадигма ООП

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

Наследование

расширение или сокращение функционала уже существующего класса.

Как задавать публичные и приватные методы


                    
class SomeClass:

  def public_method(self):
    self._private_method()

  def _private_method(self):
    pass
                

Как инкапсулировать поля


                    
class SomeClass:

  def public_method(self):
# Cокрытие взаимодействия с внутренними состояниями через публичные методы
    self._private_method()
		self._private_method2()

  def _private_method(self):
    pass

	def _private_method2(self):
    self._private_method3()

	def _private_method3(self):
    pass
                

Как создавать @classmethod


                    
class SomeClass:
	value = 100500

	def __call__(self):
		return self._get_value()
		--------- или так ------------
		return SomeClass._get_value()
	
	@classmethod
	def _get_value(cls):
		return cls.value

                

Как выглядит пример полиморфной реализации


                    
class Human:
	def eat(self):
		pass

class Cat:
	def eat(self):
		pass

class Reptiloid:
	def eat(self):
		pass


class FeedSomeone:
	def __init__(self, someone):
		self.someone = someone

	def feed(self):
		self.someone.eat()

for creature in (Human(), Cat(), Reptiloid()):
		FeedSomeone(creature).feed()
                

Dataclass

Для описаний различных структур данных в коде удобно использовать dataclass. Большинству Python-разработчиков приходится регулярно писать такие классы:


                    
class Person:
    def __init__(self, first_name: str, last_name: str, age: int):
        self.first_name = first_name
        self.last_name = last_name
        self.age = age
                

Уже на этом примере видна избыточность. Переменные first_name, last_name и age повторяются несколько раз. Реальный класс, скорее всего, еще будет содержать функции для сравнения (__eq__), корректного вывода на экран (__repr__) и так далее.

Модуль dataclasses содержит декоратор @dataclass, используя его, код можно переписать следующим образом:


                    
from dataclasses import dataclass, field

@dataclass
class Person:
    first_name: str
    last_name: str
    age: int
                

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


                    
Person("ivan", "ivanov", "30")
# или
Person(first_name="ivan", last_name="ivanov", age="30")
                

Для описание полей, которые имеют значения по умолчанию, нужно:


                    
@dataclass
class Person:
    first_name: str
    last_name: str = ''
    age: int = 10
                

Теперь класс Person можно создавать следующим образом: Person("ivan")

Замечание Если вы определили переменную, которая имеет значение по умолчанию, а потом после нее определите переменную без значения по умолчанию, то в таком случае будет вызвано исключение TypeError: non-default argument 'age' follows default argument.

Для указания значений по умолчанию для изменяемых типов (список, словарь, множество) нужно использовать field.


                    
from dataclasses import dataclass, field

@dataclass
class Person:
    first_name: str
    last_name: str = ''
    age: int = 10

@dataclass
class Adult(Person):
  children: list = field(default_factory=list)

Adult("alex")
# Adult(first_name='alex', last_name='', age=10, children=[])
                

Обратите внимание, что в children находится не тип list, а объект списка. Примеры изменяемых полей других типов:


                    
children: dict = field(default_factory=dict)
children: set = field(default_factory=set)
                

Создание датаклассов по словарям

Для создания датаклассов по словарям нужно использовать библиотеку [marshmallow-dataclass](https://pypi.org/project/marshmallow-dataclass/). Эта библиотека связывает библиотеку для проверки корректности данных [marshmallow](https://marshmallow.readthedocs.io/en/stable/) с датаклассами. Перед началом использования библиотеки marshmallow_dataclass ее нужно установить: `pip install marshmallow_dataclass`

Создание marshmallow схемы на основе dataclass

Для создания marshmallow схемы на основе dataclass нужно:


                    
from dataclasses import dataclass

import marshmallow
import marshmallow_dataclass

@dataclass
class Person:
    name: str
    age: int

    class Meta:
        unknown = marshmallow.EXCLUDE


PersonSchema = marshmallow_dataclass.class_schema(Person)
PersonSchema().load({"name": "alex", "age": "100", "ds": 123})
# Person(name='alex', age=100)
                

В результате выполнения PersonSchema().load() будет возвращен объект Person или вызвано исключение ValidationError.

ValidationError

может возникать, если:


                    
🎯 Переданы данные неправильного формата. Например, в поле, которое ожидает число, передается строка.

🎯 Передано недостаточно данных. Например, в передаваемом словаре не существует поле, которое является обязательным (не помечено аннотацией Optional).

🎯 Переданы излишние поля и не указан режим для marshmallow unknown = marshmallow.EXCLUDE. Ниже приведен пример такой ситуации.
                

Meta

С помощью класса Meta можно указывать параметры работы библиотеки marshmallow по умолчанию. Если будут переданы лишние данные, то при вызове метода load будет вызвано исключение ValidationError. Например:


                    
from dataclasses import dataclass

import marshmallow
import marshmallow_dataclass

@dataclass
class Person:
    name: str
    age: int


PersonSchema = marshmallow_dataclass.class_schema(Person)

PersonSchema().load({"name": "name", "age": 1, "some_extra": ""})

# Traceback (most recent call last):
# marshmallow.exceptions.ValidationError: {'some_extra': ['Unknown field.']}
                

Обработка ValidationError

Обработать ValidationError можно следующим образом:


                    
try:
    PersonSchema().load({"name": "name", "age": 1, "some": ""})
except marshmallow.exceptions.ValidationError:
    # todo handle
    pass
                

Кастомизация полей marshmallow

Чтобы соблюдать стилистику Python (PEP 8), нужно называть переменные в стиле snake case. То есть:


                    
from dataclasses import dataclass, field

@dataclass
class Person:
    first_name: str
    last_name: str
    age: int
                

Но часто название полей в данных из внешних источников не соответствуют нужному стилю в Python. Например:


                    
{
    "firstName": "",
    "lastName": "",
    "age": 0
}
                

Если попытаться загрузить такой словарь в датакласс Person, то получим ошибку ValidationError, так как в словаре нет обязательных полей (first_name и last_name). Для решения этой проблемы нужно настроить работу marshmallow. Конкретно для этой проблемы используем настройку [data_key](https://marshmallow.readthedocs.io/en/stable/quickstart.html#specifying-serialization-deserialization-keys). Она позволяет указать имя, которое применяется в загружаемых (исходных) данных. Но мы не описываем marshmallow схему, она генерируется на основе нашего датакласса. Поэтому для определения этих настроек нужно установить поле metadata при описании датакласса.

Оглавление

Пространство имен

Глобальное пространство

Объемлющее пространство

Магические методы

Реализация init

Реализация new

Реализация del

Пространство имен

область видимости, в которой происходит поиск переменной по ее имени.

Глобальное пространство

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

Объемлющее пространство

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

Магические методы

встроенные в язык Python методы для добавления классам особой функциональности или изменения уже существующей методы. Основной отличительный признак такого метода — начинается и заканчивается символами “__”.

Реализация init


                            
class SomeClass:
	def __init__(self, a, b, c):
		self.a = a
		self.b = b
		self.c = c
	...
                        

Реализация new


                            
class LimitedInstances(object):
    _instances = []  # ссылки на объекты
    limit = 5 

    def __new__(cls, *args, **kwargs):
        if not len(cls._instances) <= cls.limit:
            raise RuntimeError, "Count not create instance. Limit %s reached" % cls.limit    
        instance = object.__new__(cls, *args, **kwargs)
        cls._instances.append(instance)
        return instance
    
    def __del__(self):
        # Remove instance from _instances 
        self._instance.remove(self)
                        

Реализация del


                            
class SomeClass:
	...
	def __del__(self):
		self.file_descriptor.close()
                        

Оглавление

Антипаттерны

Рефакторинг

Запахи плохого кода Длинный метод

Длинный список параметров

Комментарии

Дублирование кода

Большой класс

Бесполезный код

Посредник

Завистливые функции

Неуместная близость

Цепочка вызовов

Антипаттерны

Программирование копипастом

Спагетти-код

Магические числа

Hard code

Soft code

Ненужная сложность

Изобретение квадратного колеса

Программирование перебором

Слепая вера

God Object

Антипаттерны

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

Рефакторинг

процесс исправления ошибок или устранения узких мест в коде.

Запахи плохого кода Длинный метод

метод длиннее 10 строк опасен: сложность поддержки кода возрастает, нарушается уровень контекста

Длинный список параметров

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

Комментарии

комментарии внутри кода отвечают на вопрос «как?», а не «почему?»

Дублирование кода

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

Большой класс

класс, в котором зацепление недостаточно высоко (его можно повысить, разбив класс на более мелкие классы) опасен: класс с нечеткой зоной ответственности значительно усложнит поддержку кода, а также внедрение дополнительного функционала

Бесполезный код

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

Посредник

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

Завистливые функции

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

Неуместная близость

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

Цепочка вызовов

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

Антипаттерны

Программирование копипастом

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

Спагетти-код

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

Магические числа

константа с каким-либо значением безо всяких указаний, что бы это могло быть опасен: никто не знает, что это за число, почему это число, за что оно отвечает и что случится, если его изменить. Темные пятна в коде никого еще не доводили до добра исправить: выяснить у автора кода, что это за магические числа, вынести эти числа в конфиг или прокомментировать эту переменную, указав, ПОЧЕМУ используется эта константа и почему был выбран этот алгоритм.

Hard code

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

Soft code

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

Ненужная сложность

избыточные проверки в коде опасен: лишнее усложнение кода, усложнение читаемости, незначительное (но бесполезное) увеличение времени работы приложения исправить: удалить избыточные проверки.

Изобретение квадратного колеса

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

Программирование перебором

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

Слепая вера

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

God Object

у вас ОДИН класс. Может, их несколько, но 95% кода находится в ОДНОМ классе опасен: этот класс невозможно поддерживать и развивать исправить: разбить класс на классы с более узкой функциональностью.

Оглавление

Пространство имен

Области видимости

Магические методы

Пространство имен

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

Области видимости


                        
Порядок поиска полей и методов:

- Локальная область видимости.

- Локальные области видимости объемлющих функций.

- Глобальная область видимости.

- Встроенная область видимости.
                    

Магические методы


                        

                    

                        
__init__ — ничего не возвращает, отвечает только за инициализацию экземпляра после его создания.
                    

                        
__del__ — обнуляет все ссылки на объект (но не освобождает выделенную память).
                    

                        
__eq__ — равенство двух объектов между собой.
                    

                        
__ne__ — неравенство двух объектов между собой.
                    

                        
`__gt__` — аналог > (больше).
                    

                        
__ge__ — аналог ≥ (больше или равно).
                    

                        
__lt__ — аналог < (меньше).
                    

                        
__le__ — аналог ≤ (меньше или равно).
                    

Оглавление

Singleton

Factory

Adapter

Fasad

Iterator

Decorator

Singleton


                        
import time
import datetime

class Cache(object):
  # Тут объеке кэша который будет создан или возвратиться при попытке создать
  # Экземпляр
  _instance = None
  # хранилище кэша
  vault: dict = {}
  # __new__ вызывается перед __init__ поэтому здесь есть смысл подменять инстанс
  def __new__(cls, *args, **kwargs):
    # если объект еще не создан:
    if not cls._instance:
      cls._instance = object.__new__(cls, *args, **kwargs)
    return cls._instance

  def set_value(self, key, value):
    """ Установить значение по ключу """
    self.vault[key] = value     

  def get_value(self, key):
    """ Получить значение по ключу """
    return self.vault[key]

  def check(self, key):
    """ Проверить есть ли такой ключ """
    return key in self.vault

class Source:
  def get_something(self, key):
    """ Метод заглушка """
    time.sleep(5)
    return 'result' 


class App:
  def __init__(self) -> None:
    self.cache = Cache() # Получаем экземпляр класса Cache
    self.source = Source() # Получаем экземпляр класса Source

  def process(self, key):
    start_time = datetime.datetime.now() # Время начала
    if self.cache.check(key): # Проверяем есть ли ключ в кэше
      result = self.cache.get_value(key) # Возвращаем значение
    else:
      result = self.source.get_something(key) # Получаем значение из источника
      self.cache.set_value(key, result) # Устанавливаем значение в кэш
    print(datetime.datetime.now() - start_time) # Время выполнения
    return result  

app = App()
app2 = App()

print('Являются ли объекты одинаковыми', id(app) == id(app2))
print('Являются ли их кэши одинаковыми', id(app.cache) == id(app2.cache))

app.process(1)
print('При повторном выполнении данные будут получаться уже из кэша, быстро')

app.process(1)
print('Данные для другого источника попрежнему будут получаться долго')

app.process(2)
print('Поскольку у нас кэш один на два разных объекта то 2 объект не будет обращаться к источнику данных а возмет из кэша')

app2.process(1)
print('Если мы вызовем этот блок кода повторно то данные будут получены моментально')

                    

Factory


                        
from abc import ABC, abstractmethod

class AbstractCar(ABC):
  @abstractmethod
  def deliver_by_land(self):
    pass

class AbstractShip(ABC):
  @abstractmethod
  def deliver_by_sea(self):
    pass

class AbstractTransportFacrory(ABC):
  @abstractmethod
  def create_car(self) -> AbstractCar:
    pass

  @abstractmethod
  def create_ship(self) -> AbstractShip:
    pass  

class TeslaX(AbstractCar):
  
  def deliver_by_land(self):
    print("Доставим быстро") 

class HeavyTrack(AbstractCar):
  
  def deliver_by_land(self):
    print("Медленно но верно") 

class RubberBoat(AbstractShip):
  
  def deliver_by_sea(self):
    print("Плывет по морю") 

class CargoShip(AbstractShip):
 
  def deliver_by_sea(self):
    print("Идет по морю")         

class UsualTransportFactory(AbstractTransportFacrory):
  def create_car(self) -> AbstractCar:
    return TeslaX()    

  def create_ship(self) -> AbstractShip:
    return RubberBoat()      

class CargoTransportFactory(AbstractTransportFacrory):
  def create_car(self) -> AbstractCar:
    return HeavyTrack()    

  def create_ship(self) -> AbstractShip:
    return CargoShip()

class Warehouse:
  factory: AbstractTransportFacrory
  def __init__(self, factory: AbstractTransportFacrory) -> None:
    self.factory = factory

  def deliver_with_ship(self):
    ship = self.factory.create_ship()
    ship.deliver_by_sea()

  def deliver_with_car(self):
    car = self.factory.create_car()
    car.deliver_by_land()

warehouse = Warehouse(factory=CargoTransportFactory())
warehouse.deliver_with_car()
warehouse.deliver_with_ship()

warehouse = Warehouse(factory=UsualTransportFactory())
warehouse.deliver_with_car()
warehouse.deliver_with_ship()





                    

Adapter


                        
from abc import ABC, abstractmethod
import json

CSV = "Название;Цвет;Вес\nКот;Редкий окрас (скумбрия на снегу);3\nПёс;Черный;15"
JSON = '{"items": [{"Название": "Медведь", "Цвет": "Бурый", "Вес": "150"}, {"Название": "Лиса", "Цвет": "Рыжий", "Вес": "15"} ]}'

class Adapter(ABC):
  @abstractmethod
  def get_data(self, source) -> dict:
    pass

class CSVReader(Adapter):
  def get_data(self, source) -> dict:
    lines = self._split_lines(source)
    headers, data = self._split_headers_with_data(lines)
    return self._format_result(headers, data)    

  def _split_lines(self, source):
    return source.split('\n')

  def _split_headers_with_data(self, lines):
    return lines[0].split(';'), [x.split(';') for x in lines[1:]]

  def _format_result(self, headers, data) -> list:
    items = []
    for line in data:
      items.append(dict(zip(headers, line)))

    return items

class JSONReader(Adapter):
  def get_data(self, source) -> dict:
    return json.loads(source)['items']      

class Printer:
  def __init__(self, adapter: Adapter) -> None:
    self.adapter = adapter

  def _get_data(self, source):
    self.data = self.adapter.get_data(source)

  def print(self, source):
    self._get_data(source)
    for line in self.data:
      print(f"В наличии: {line['Название']}. Окрас: {line['Цвет']}. Масса: {line['Вес']}")

csv_printer = Printer(adapter=CSVReader())
json_printer = Printer(adapter=JSONReader())

csv_printer.print(CSV)
json_printer.print(JSON)


                    

Fasad


                        
from abc import ABC, abstractmethod
import json

CSV = "Название;Цвет;Вес\nКот;Редкий окрас (скумбрия на снегу);3\nПёс;Черный;15"
CSV1 = "Название;Цвет;Вес\nКот;Редкий окрас (скумбрия на снегу);3\nПёс;Черный;15"
JSON = '{"items": [{"Название": "Медведь", "Цвет": "Бурый", "Вес": "150"}, {"Название": "Лиса", "Цвет": "Рыжий", "Вес": "15"} ]}'
JSON1 = '{"items": [{"Название": "Медведь", "Цвет": "Бурый", "Вес": "150"}, {"Название": "Лиса", "Цвет": "Рыжий", "Вес": "15"} ]}'

class Adapter(ABC):
  @abstractmethod
  def get_data(self, source) -> dict:
    pass

class CSVReader(Adapter):
  def get_data(self, source) -> dict:
    lines = self._split_lines(source)
    headers, data = self._split_headers_with_data(lines)
    return self._format_result(headers, data)    

  def _split_lines(self, source):
    return source.split('\n')

  def _split_headers_with_data(self, lines):
    return lines[0].split(';'), [x.split(';') for x in lines[1:]]

  def _format_result(self, headers, data) -> list:
    items = []
    for line in data:
      items.append(dict(zip(headers, line)))

    return items

class JSONReader(Adapter):
  def get_data(self, source) -> dict:
    return json.loads(source)['items']      

class Printer:
  def __init__(self, adapter: Adapter) -> None:
    self.adapter = adapter

  def _get_data(self, source):
    self.data = self.adapter.get_data(source)

  def print(self, source):
    self._get_data(source)
    for line in self.data:
      print(f"В наличии: {line['Название']}. Окрас: {line['Цвет']}. Масса: {line['Вес']}")

class Fasad:
  def __init__(self) -> None:
    self.csv_reader = Printer(CSVReader())
    self.json_reader = Printer(JSONReader())      
    self.csv_files = []
    self.json_files = []

  def fill_with_csv(self, *args):
    """Наполняем данными csv"""
    self.csv_files = args  

  def fill_with_json(self, *args):
    """Наполняем данными csv"""
    self.json_files = args

  def _csv_print(self):
    """Выводим на печать содержимое файлов csv"""
    i = 1
    print("Вывод csv содержимого")
    for csv in self.csv_files:
      print(f"Печать файла № {i}")
      self.csv_reader.print(csv)
      i += 1

  def _json_print(self):
    """Выводим на печать содержимое файлов json"""
    i = 1
    print("Вывод json содержимого")
    for json in self.json_files:
      print(f"Печать файла № {i}")
      self.json_reader.print(json)
      i += 1    

  def print(self):
    self._csv_print()
    self._json_print()    

fasad = Fasad()
fasad.fill_with_csv(CSV, CSV1)
fasad.fill_with_json(JSON, JSON1)
fasad.print()



                    

Iterator


                        
class Letters:
  def __init__(self) -> None:
    self.letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    self.i = 0

  def __next__(self):
    try:
      val = self.letters[self.i]
      self.i += 1
      return val  
    except IndexError:
      raise StopIteration  

  def __iter__(self):
    return self
    
for letter in Letters():
  print(letter)

                    

Decorator


                        
from pprint import pprint
import datetime
import json

def log_func(func):
  def wrapper(*args, **kwargs):
    start_time = datetime.datetime.now()
    result = func(*args, **kwargs)
    length = len(json.dumps(result))
    delta_time = datetime.datetime.now() - start_time
    result['length'] = length
    result['duration'] = delta_time
    return result

  return wrapper

@log_func
def parse_json(string):
  return json.loads(string)

JSON1 = '{"items": [{"Название": "Медведь", "Цвет": "Бурый", "Вес": "150"}, {"Название": "Лиса", "Цвет": "Рыжий", "Вес": "15"} ]}'

pprint(parse_json(JSON1))
                    
© 2023 Все права защищены