Написание пользовательских полей шаблона

Введение

Справочная документация по моделям объясняет, как использовать стандартные поля Django классов: CharField и DateField т. Д. В большинстве случаев этих занятий достаточно. Однако могут быть случаи, когда предложение Django не соответствует всем вашим требованиям или вы хотите использовать совершенно другое поле, чем те, которые предоставляет Django.

Встроенные типы полей Django не поддерживают все возможные типы столбцов базы данных, только самые распространенные, такие как VARCHAR и INTEGER . Для менее распространенных типов столбцов, таких как географические многоугольники или даже определяемые пользователем типы в стиле пользовательских типов PostgreSQL , вы можете определить свои собственные подклассы Field .

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

Наш пример объекта

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

class Hand:
    """A hand of cards (bridge style)"""

    def __init__(self, north, east, south, west):
        # Input parameters are lists of cards ('Ah', '9s', etc.)
        self.north = north
        self.east = east
        self.south = south
        self.west = west

    # ... (other possibly useful methods omitted) ...

Это обычный класс Python без специфики Django. Мы хотели бы иметь возможность делать подобные вещи в наших моделях (при условии, что атрибут hand модели является экземпляром Hand ):

example = MyModel.objects.get(pk=1)
print(example.hand.north)

new_hand = Hand(north, east, south, west)
example.hand = new_hand
example.save()

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

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

Заметка

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

Теоретические основы

Хранилище базы данных

Начнем с полей шаблона. Вкратце, поле модели - это способ обработать обычный объект Python (строка, логическое значение, один datetime или несколько сложных объектов, например Hand ) и преобразовать его в формат, подходящий для использования с базой данных. data (этот формат также подходит для сериализации, но позже мы увидим, что это задумано вполне естественно, когда будет освоена часть базы данных).

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

Обычно вы либо пишете поле Django, которое будет соответствовать определенному типу столбца базы данных, либо вам нужно найти способ преобразовать ваши данные, например, в строку.

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

Что делает полевой класс?

Все поля Django (и когда мы говорим о полях в этом документе, мы по-прежнему говорим о полях шаблона , а не о полях форм ) являются подклассами django.db.models.Field . Большая часть информации, которую Django хранит о поле, является общей для всех полей (имя, текст справки, уникальность и т. Д.). Хранением всей этой информации управляет Field . Мы увидим более точно, что можно сделать Field чуть позже; а пока давайте просто узнаем, что все наследует Field и персонализирует ключевые элементы поведения класса.

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

Помните об этом при создании собственных настраиваемых полей. Подкласс Field Django, который вы пишете, предоставляет механизмы для преобразования ваших экземпляров Python в значения базы данных или сериализации различными способами (например, существуют различия между сохранением значения и его использованием в запросе. ). Если все это звучит немного сложно, не волнуйтесь, это станет понятнее в приведенных ниже примерах. Просто помните, что вы часто будете создавать два класса, когда хотите создать настраиваемое поле:

  • Первый класс - это объект Python, которым будут управлять ваши пользователи. Они будут использовать его как атрибут модели, будут читать его значения для отображения и так далее. Это класс Hand в нашем примере.
  • Второй класс - это подкласс Field . Это класс, который знает, как преобразовать ваш первый класс из его полезной формы для хранения в форму Python и наоборот.

Написание подкласса поля

Планируя свой подкласс Field , сначала подумайте о том, какой из Field существующих классов ваше новое поле выглядит больше всего. Можно ли наследовать существующее поле Django и сохранить некоторую работу? Если нет, вам придется наследовать от класса, Field который является родительским для всех остальных.

Инициализация новой области состоит в выделении какого - либо параметра , который является специфическим для своей области, от стандартных параметров, и передавая последний метод __init__() из Field (или другого родительского класса).

В нашем примере мы будем называть наше поле HandField (желательно называть ваш подкласс Field модели, <QuelqueChose>Field чтобы мы могли быстро определить, что это подкласс Field ). Наше поле не похоже ни на одно другое существующее поле, поэтому мы наследуем напрямую от Field :

from django.db import models

class HandField(models.Field):

    description = "A hand of cards (bridge style)"

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super().__init__(*args, **kwargs)

Поле HandField принимает большинство стандартных параметров поля (см. Список ниже), но мы хотим убедиться, что его длина фиксирована, поскольку в нем должны храниться только значения 52 карт плюс их масть; Всего 104 символа.

Заметка

Многие поля модели Django принимают параметры, которые не используются. Например, вы можете передать как editable и auto_now к , django.db.models.DateField и он будет игнорировать параметр editable (определение» auto_now означает , что „ редактируемые = False“). В этом случае ошибки не возникает.

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

Метод Field.__init__() принимает следующие параметры:

Все параметры без объяснения в приведенном выше списке имеют то же значение, что и для обычных полей Django. См. Полевую документацию для примеров и подробностей.

Деконструкция поля

Аналогом записи метода является запись метода . Это используется во время : docs: `миграции модели </ themes / migrations>`, чтобы сообщить Django, как взять экземпляр нового поля, чтобы сжать его до сериализованного формата; в частности, выбор параметров, которые нужно передать для его воссоздания.__init __() deconstruct() __init __()

Если вы не добавили никаких дополнительных параметров в унаследованное вами поле, нет необходимости писать новый метод deconstruct() . Если, однако, вы измените параметры, переданные в __init__() (как мы это сделали с HandField ), необходимо будет завершить переданные значения.

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

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

Например, в нашем классе HandField мы всегда принудительно используем max_length in __init__() . Метод deconstruct() базового класса Field увидит его и попытается вернуть его в названных параметрах; таким образом, мы можем удалить его из именованных параметров для лучшей читаемости:

from django.db import models

class HandField(models.Field):

    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = 104
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        del kwargs["max_length"]
        return name, path, args, kwargs

Если вы добавляете новый именованный параметр, вы должны сами написать код, deconstruct() который помещает его значение в kwargs . Вам также следует опустить значение, kwargs когда нет необходимости восстанавливать состояние поля, например, когда используется значение по умолчанию.

from django.db import models

class CommaSepField(models.Field):
    "Implements comma-separated storage of lists"

    def __init__(self, separator=",", *args, **kwargs):
        self.separator = separator
        super().__init__(*args, **kwargs)

    def deconstruct(self):
        name, path, args, kwargs = super().deconstruct()
        # Only include kwarg if it's not the default
        if self.separator != ",":
            kwargs['separator'] = self.separator
        return name, path, args, kwargs

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

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

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

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

name, path, args, kwargs = my_field_instance.deconstruct()
new_instance = MyField(*args, **kwargs)
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)

Изменение базового класса настраиваемого поля

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

class CustomCharField(models.CharField):
    ...

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

class CustomCharField(models.TextField):
    ...

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

class CustomCharField(models.CharField):
    ...

class CustomTextField(models.TextField):
    ...

Как обсуждалось в разделе « Удаление полей» , вам следует сохранять CustomCharField исходный класс, пока вы сохраняете любые миграции, которые на него ссылаются.

Документация по настраиваемым полям

Как всегда, рекомендуется задокументировать тип поля, чтобы пользователи знали, что это такое. Помимо предоставления ему полезной строки «docstring» для разработчиков, вы также можете разрешить пользователям административного приложения просматривать краткое описание типа поля через приложение django.contrib.admindocs . Для этого укажите описательный текст в атрибуте class description настраиваемого поля. В приведенном выше примере описанием, отображаемым приложением admindocs для a, HandField будет «Карточная рука (стиль моста)».

На дисплее django.contrib.admindocs описание поля интерполируется с помощью field.__dict__ , что позволяет описанию содержать параметры поля. Например, описание CharField :

description = _("String (up to %(max_length)s)")

Полезные методы

После создания подкласса Field может оказаться полезным переопределить некоторые стандартные методы в зависимости от поведения поля. Список методов ниже более или менее расположен в порядке убывания важности, поэтому вам нужно начать с первых.

Пользовательские типы баз данных

Допустим, вы создали собственный тип под названием PostgreSQL mytype . Вы можете получить Field и реализовать метод db_type() следующим образом:

from django.db import models

class MytypeField(models.Field):
    def db_type(self, connection):
        return 'mytype'

После создания MytypeField его можно использовать в любой модели, как и любой другой тип Field :

class Person(models.Model):
    name = models.CharField(max_length=80)
    something_else = MytypeField()

Если ваша цель - создать приложение, не зависящее от выбора базы данных, вы должны знать о различиях в типах столбцов базы данных. Например, в PostgreSQL вызывается столбец типа дата / время, timestamp а в MySQL - тот же столбец datetime . Вы можете справиться с этим в методе, db_type() основанном на атрибуте connection.settings_dict['ENGINE'] .

Например :

class MyDateField(models.Field):
    def db_type(self, connection):
        if connection.settings_dict['ENGINE'] == 'django.db.backends.mysql':
            return 'datetime'
        else:
            return 'timestamp'

db_type() И методы rel_db_type() называются Джанго , когда система строит инструкции для вашего приложения - то есть, когда таблицы сначала создается. Они также вызываются при создании предложения, которое включает поле шаблона, то есть когда вы извлекаете данные с помощью методов QuerySet, таких как , или, а поле шаблона - найдено в параметре. Они не вызываются в другое время, поэтому они могут позволить себе запускать несколько сложный код, как проверка в приведенном выше примере.CREATE TABLE WHERE get() filter() exclude() connection.settings_dict

Некоторые типы столбцов базы данных принимают такие параметры, как CHAR(25) , где параметр 25 - это максимальный размер столбца. В таких случаях более гибко определять параметр в модели, а не жестко закодировать его в методе db_type() . Например, было бы неразумно иметь такой тип CharMaxlength25Field :

# This is a silly example of hard-coded parameters.
class CharMaxlength25Field(models.Field):
    def db_type(self, connection):
        return 'char(25)'

# In the model:
class MyModel(models.Model):
    # ...
    my_field = CharMaxlength25Field()

Лучший способ решить эту проблему - разрешить установку параметра во время выполнения, то есть, когда создается экземпляр класса. Для этого реализуйте метод Field.__init__() следующим образом:

# This is a much more flexible example.
class BetterCharField(models.Field):
    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super().__init__(*args, **kwargs)

    def db_type(self, connection):
        return 'char(%s)' % self.max_length

# In the model:
class MyModel(models.Model):
    # ...
    my_field = BetterCharField(25)

Наконец, если ваш столбец требует действительно сложной настройки SQL, вернитесь None из db_type() . Это приведет к тому, что код создания SQL Django пропустит это поле. Затем вы отвечаете за создание столбца в правой таблице каким-либо другим способом, но это дает вам способ сказать Django, чтобы он не мешал.

Метод rel_db_type() вызывается такими полями, как ForeignKey и, OneToOneField которые указывают на другое поле для определения их типа столбца базы данных. Например, если он у вас есть UnsignedAutoField , вам также понадобятся внешние ключи, указывающие на это поле, чтобы использовать тот же тип данных:

# MySQL unsigned integer (range 0 to 4294967295).
class UnsignedAutoField(models.AutoField):
    def db_type(self, connection):
        return 'integer UNSIGNED AUTO_INCREMENT'

    def rel_db_type(self, connection):
        return 'integer UNSIGNED'

Преобразование значений в объекты Python

Если ваш Field настраиваемый класс обрабатывает более сложные структуры данных, чем строки, даты, целые числа или числа с плавающей запятой, вам может потребоваться переопределить from_db_value() и to_python() .

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

to_python() вызывается десериализацией и как часть метода, clean() используемого из форм.

Как правило, to_python() должен элегантно обрабатывать все следующие параметры:

  • Экземпляр правильного типа (например, Hand в модельном случае этого документа).
  • цепь
  • None (если поле позволяет null=True )

В нашем классе HandField мы храним данные с полем VARCHAR в базе данных, поэтому нам нужно иметь возможность обрабатывать строки и значение None в from_db_value() . В to_python () также необходимо управлять экземплярами Hand :

import re

from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

def parse_hand(hand_string):
    """Takes a string of cards and splits into a full hand."""
    p1 = re.compile('.{26}')
    p2 = re.compile('..')
    args = [p2.findall(x) for x in p1.findall(hand_string)]
    if len(args) != 4:
        raise ValidationError(_("Invalid input for a Hand instance"))
    return Hand(*args)

class HandField(models.Field):
    # ...

    def from_db_value(self, value, expression, connection):
        if value is None:
            return value
        return parse_hand(value)

    def to_python(self, value):
        if isinstance(value, Hand):
            return value

        if value is None:
            return value

        return parse_hand(value)

Обратите внимание, что эти методы всегда возвращают экземпляр Hand . Это тип объекта Python, который мы хотим сохранить в атрибуте модели.

Ведь to_python() если что-то пойдет не так во время преобразования значения, вы должны создать исключение типа ValidationError .

Преобразование объектов Python в значения запроса

Поскольку использование базы данных требует двустороннего преобразования, при перегрузке from_db_value() вам также потребуется перегрузка get_prep_value() для преобразования объектов Python в значения запроса.

Например :

class HandField(models.Field):
    # ...

    def get_prep_value(self, value):
        return ''.join([''.join(l) for l in (value.north,
                value.east, value.south, value.west)])

Предупреждение

Если ваше настраиваемое поле использует типы MySQL или CHAR , вы должны убедиться, что метод всегда возвращает строковый тип. MySQL выполняет гибкие, но непредсказуемые совпадения, когда запросы к этим типам выполняются с целым числом, что может привести к тому, что результаты будут содержать неожиданные объекты. Эта проблема не может возникнуть, если вы постоянно возвращаете строки в .VARCHAR TEXT get_prep_value() get_prep_value()

Преобразование значений запроса в значения базы данных

Определенные типы данных (например, даты) должны быть в определенном формате, прежде чем они могут быть использованы ядром базы данных. get_db_prep_value() это метод, в котором должны выполняться эти преобразования. Конкретное соединение, которое будет использоваться для запроса, передается в качестве параметра connection . Это позволяет при необходимости использовать логику преобразования конкретного движка.

Например, Django использует для своего поля следующий метод BinaryField :

def get_db_prep_value(self, value, connection, prepared=False):
    value = super().get_db_prep_value(value, connection, prepared)
    if value is not None:
        return connection.Database.Binary(value)
    return value

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

Предварительная обработка значений перед сохранением

Если вы хотите предварительно обработать значение непосредственно перед сохранением, вы можете использовать pre_save() . Например, поле DateTimeField Django использует этот метод для правильной установки атрибута в случае auto_now или auto_now_add .

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

Выбор поля формы для поля шаблона

Чтобы настроить используемое поле формы ModelForm , вы можете переопределить formfield() .

Класс поля формы может быть определен через form_class и параметры choices_form_class  ; последний используется, если поле содержит список вариантов, в противном случае он form_class учитывается. Если эти параметры отсутствуют, они используются CharField или TypedChoiceField будут использоваться.

Все содержимое словаря kwargs передается непосредственно в метод __init__() поля формы. Обычно все, что вам нужно сделать, это установить подходящее значение по умолчанию для параметра form_class (ou choices_form_class ), а затем делегировать остальное родительскому классу. Возможно, вам потребуется написать настраиваемое поле формы (и даже компонент формы). См. Дополнительную информацию по этой теме в документации по формам .

Продолжая наш текущий пример, мы можем написать метод formfield() следующим образом:

class HandField(models.Field):
    # ...

    def formfield(self, **kwargs):
        # This is a fairly standard way to set up some defaults
        # while letting the caller override them.
        defaults = {'form_class': MyFormField}
        defaults.update(kwargs)
        return super().formfield(**defaults)

Мы предполагаем, что класс поля MyFormField был импортирован (по умолчанию у него есть собственный компонент). В этом документе подробно не рассматривается написание настраиваемых полей формы.

Эмуляция встроенных типов полей

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

Например :

class HandField(models.Field):
    # ...

    def get_internal_type(self):
        return 'CharField'

Независимо от используемого механизма базы данных это означает, что migrate и другие команды SQL будут создавать столбец правильного типа для хранения строки.

Если get_internal_type() возвращает строку, которая не известна Django для используемого движка базы данных - то есть, она не отображается в django.db.backends.<db_name>.base.DatabaseWrapper.data_types - строка все равно будет использоваться сериализатором, но метод по умолчанию db_type() вернется None . Обратитесь к документации, db_type() чтобы узнать, почему это может быть полезно. Помещение описательной строки в тип поля для сериализации - полезная идея, если вам когда-либо понадобится использовать вывод сериализации где-то еще, за пределами Django.

Преобразование данных поля для сериализации

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

class HandField(models.Field):
    # ...

    def value_to_string(self, obj):
        value = self.value_from_object(obj)
        return self.get_prep_value(value)

Несколько общих советов

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

  1. Посмотрите на существующие поля Django (in django/db/models/fields/__init__.py ) для вдохновения. Найдите поле, которое выглядит как то, что вы хотите сделать, и заполните то, что вам не хватает, вместо того, чтобы создавать новое поле с нуля.
  2. Напишите метод __str__() для класса поля, который вы пишете. Во многих случаях код поля по умолчанию вызывает str() значение (в примерах в этом документе value это будет экземпляр Hand , а не экземпляр HandField ). Итак, если ваш метод __str__() автоматически преобразует объект Python в текстовую форму, вы сэкономите много работы.

Написание подкласса

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

Django предоставляет класс, который File используется в качестве посредника и отвечает за содержание файлов и их операции. Он может быть основой подклассов, которые могут настраивать доступ к файлам и доступные методы. Он расположен в, django.db.models.fields.files и его поведение по умолчанию объясняется в документации к файлу .

Когда подкласс File создан, это включает указание новому подклассу того, FileField что он должен его использовать. Для этого определите новый подкласс File в специальном атрибуте attr_class подкласса FileField .

Некоторые предложения

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

  1. Исходный код ImageField поля Django (in django/db/models/fields/files.py ) является отличным примером того, как подкласс FileField может поддерживать определенный тип файла, поскольку он учитывает все методы, описанные выше.
  2. Если возможно, кешируйте атрибуты файла. Поскольку файлы могут храниться в удаленных системах, доступ к ним может потребовать временных или даже финансовых затрат, что не всегда необходимо. Как только файл извлекается для получения определенной информации о его содержимом, кэшируйте как можно больше этих данных, чтобы уменьшить количество совпадений файлов, которые могут произойти при возможных будущих запросах той же информации.

Copyright ©2021 All rights reserved