В следующей программе у меня есть три функции get_dataset1
, get_dataset2
и get_dataset3
, которые очень похожи. Они отличаются только тем, когда вызывают len(dataset)
и os.path.join = tmp
.
Функции get_dataset1
и get_dataset3
ведут себя так, как задумано; они загружают набор данных, и его длина больше 0. Однако в случае get_dataset2
набор данных имеет длину 0. Почему?
import copy
import os
import time
from pinecone_datasets import load_dataset
datasetName = "langchain-python-docs-text-embedding-ada-002"
def get_dataset1():
os.path.join = lambda *s: "/".join(s) # pinecone bug workaround
dataset = load_dataset(datasetName)
print("Dataset loaded:", len(dataset) != 0) # dataset has length greater than 0
def get_dataset2():
os.path.join = lambda *s: "/".join(s) # pinecone bug workaround
dataset = load_dataset(datasetName)
os.path.join = tmp
print("Dataset loaded:", len(dataset) != 0) # dataset has length 0
def get_dataset3():
os.path.join = lambda *s: "/".join(s) # pinecone bug workaround
dataset = load_dataset(datasetName)
print("Dataset loaded:", len(dataset) != 0) # dataset has length greater than 0
os.path.join = tmp
print("Dataset loaded:", len(dataset) != 0) # dataset has length greater than 0
def main():
get_dataset1()
get_dataset2()
get_dataset3()
if __name__ == "__main__":
tmp = copy.deepcopy(os.path.join)
main()
В итоге мне пришлось просмотреть исходный код pinecone-datasets, но ответ, по сути, сводится к ленивым вычислениям.
Когда вы инициализируете Dataset
, ему на самом деле не присвоено никаких полезных значений. Все вычисления, включая выяснение того, на какие данные он должен ссылаться, происходят, когда вы пытаетесь сделать что-то интересное, например, определить его длину.
В этот момент он наконец использует os.path.join
, чтобы выяснить, какие данные он должен хранить, и если в os.path.join
были внесены какие-либо изменения между инициализацией и этим моментом, он будет использовать самое последнее значение.
Чтобы добиться ожидаемого поведения, убедитесь, что os.path.join
определен правильно, когда вы делаете что-то интересное с Dataset
, а не только когда вы его создаете.
Когда вы пишете строку:
dataset = load_dataset(datasetName)
В конечном итоге это приводит к вызову конструктора Dataset
. Большая часть конструктора не имеет значения, но есть пара строк, которые, похоже, влияют на ваш вариант использования:
self._dataset_path = dataset_path
self._documents = None
Набор данных хранит информацию о том, как он может получить данные, но откладывает доступ к ним на более позднее время. Таким образом, к тому времени, когда вы попытаетесь найти len(dataset)
, свойство dataset._documents
может все еще быть None
, а может и не быть. Чтобы обработать оба случая, len(dataset)
обращается к свойству documents
(а не _documents
), используя этот метод:
@property
def documents(self) -> pd.DataFrame:
if self._documents is None:
self._documents = self._safe_read_from_path("documents")
return self._documents
Это часть класса, посвященная ленивым вычислениям. Если _documents
уже существует, он просто вернет свое значение. Но если _documents
по-прежнему равно None
, он вычислит его значение с помощью _safe_read_from_path
, затем навсегда сохранит это значение и вернет его.
В свою очередь, _safe_read_from_path
содержит следующую строку:
read_path_str = os.path.join(self._dataset_path, data_type, "*.parquet")
Таким образом, функциональность метода будет зависеть от того, как вы в последний раз определили os.path.join
.
Если сложить все вместе, это означает, что Dataset
не сохраняет многого, пока вы не вызовете len
, после чего он использует текущее значение os.path.join
, чтобы присвоить полезное значение своему свойству _document
и продолжает использовать это значение во всех будущих вызовах.
Учитывая это, мы можем выполнить каждую из ваших функций:
get_dataset1
Функция выполняет следующие шаги:
os.path.join
как свою лямбду._documents
None.os.path.join
, чтобы наконец присвоить значение _documents
.Это довольно ванильно, что объясняет, почему все работает так, как ожидалось.
get_dataset2
Этот имеет аналогичную структуру, но отличается важным образом:
os.path.join
как лямбду._documents
None.os.path.join
как temp
.temp
, хранящееся в os.path.join
, чтобы присвоить значение _documents
.В результате вы получите значение _documents
, рассчитанное с использованием неправильной реализации os.path.join
, что объясняет, почему вы получаете Dataset
нулевой длины.
get_dataset3
Именно здесь действительно вступает в игру ленивая оценка. Функция выполняет следующие шаги:
get_dataset1
os.path.join
_documents
уже известно из последнего вызова len
, он использует это старое значение вместо пересчета на основе нового значения os.path.join
.Таким образом, даже если вы переопределили os.path.join
перед вторым len
вызовом, Dataset
использует то же самое значение _documents
, которое вы вычислили в get_dataset1
, и ни в малейшей степени не заботится о новом значении os.path.join
.
Что-то происходит с вашими побочными эффектами. Поскольку
get_dataset3
работает, аget_dataset2
нет, я думаю, что вызовlen(dataset)
имеет побочный эффект (может быть, это значение кэшируется?)