Вложение Defaultdictionaries в Python3 - PullRequest
       33

Вложение Defaultdictionaries в Python3

0 голосов
/ 18 февраля 2020

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

Это код, который у меня есть на данный момент:

from collections import defaultdict, Counter
from re import findall

class Class: 
    def __init__(self, n, file):
        self.counts = defaultdict(lambda: defaultdict(int))
        self.__n = n
        self.__file = file

    def function(self, starting_text, charas):
        self.__starting_text = starting_text
        self.__charas = charas

        with open(self.__file) as file:
            text = file.read().lower().replace('\n', ' ')

        ngrams = [text[i : i + self.__n] for i in range(len(text))]

        out = self.counts
        for item in ngrams:
            data = []
            for word in findall( item+".", text):
                data.append(word[-1])
            self.counts = { item : data.count(item) for item in data }
            out[item] = self.counts
        self.counts = out

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

Если я в конце запускаю print(self.counts), моя программа запускает что-то похожее на это:

defaultdict(<function Class.__init__.<locals>.<lambda> at 0x7f8c4a94bea0>, {'t': {'h': 1, ' ': 1, 'e': 1}, 'h': {'i': 1, 'o': 1}, 'i': {'s': 2}, 's': {' ': 2, 'h': 1, 'e': 1}, ' ': {'i': 1, 'a': 1, 's': 2}, 'a': {' ': 1}, 'o': {'r': 1}, 'r': {'t': 1}, 'e': {'n': 2, ' ': 1}, 'n': {'t': 1, 'c': 1}, 'c': {'e': 1}})

Что здорово! Но я бы очень хотел, чтобы эти внутренние словари тоже были по умолчанию. В частности, если я запускаю self.counts['t']['h'], я получаю 1, как и ожидалось. Однако преимущество defaultdict заключается в том, что он дает вам 0, если ключ недоступен. В настоящее время, если я запускаю self.counts['t']['x'], я получаю ключевую ошибку, но я бы дожил до 0, если бы каждый внутренний список был также по умолчанию.

Я предполагаю, что это можно сделать где-то в куске кода, начинающегося с out=self.counts, но я немного не уверен, как мне этого добиться.

Ответы [ 3 ]

2 голосов
/ 18 февраля 2020
  1. делает данные collections.Counter или defaultdict вместо списка (поскольку вам строго не важна последовательность битов, учитывается только количество их появлений)
  2. затем update ваш self.counts[item] со счетчиком вместо назначения dict
  3. вы могли бы даже выполнить обновление прямо:
    for item in ngrams:
        data = self.counts[item]
        for word in findall( item+".", text):
            data[word[-1]] += 1
    
    , и это все, это обновит релевантные подсчеты прямо в defaultdict, как первоначально определено

То, что большая часть кода ... не идеальна или нечетна

кажется излишне сложным

Вы получаете каждый код после ngram, почему бы просто не извлечь псевдонграммы n + 1 и разбить это? Что-то вроде (непроверенное, может быть немного не так):

for i in range(0, len(text)-n):
    ngram, follower = text[i:i+n], text[i+n]
    self.counts[ngram][follower] += 1

Это также позволяет избежать по меньшей мере квадратичной c сложности вашего кода (и различных постоянных сложностей), что является хорошей стороной -эффект, хотя обратите внимание, что оригинал неявно пропускает последователей \n (перевод строки / новой строки), так как re.DOTALL, . "соответствует любому символу, кроме новой строки". Поэтому, если вы хотите сохранить такое поведение, вам придется специально проверить и пропустить follower == '\n'.

Повторное использование переменных-членов в качестве локальных?

Вы по какой-то странной причине повторно используете self.counts в качестве локальной переменной, сохраняя ее в out, устанавливая ее в странные вещи, а затем перезагружая после того, как вы установили его на себя, почему бы не out внутренняя переменная?

        for item in ngrams:
            data = []
            for word in findall( item+".", text):
                data.append(word[-1])
            out = { item : data.count(item) for item in data }
            self.counts[item] = out

Не то, чтобы это было очень полезно (возможно, кроме утилиты отладки printf), вы могу присвоить self.counts[item] прямой.

Я также понятия не имею, какие утилиты __starting_text и __charas имеют

двойные префиксы подчеркивания

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

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

всегда передают кодировку в текстовый режим open

Серьезно. open(path) работает в текстовом режиме (автоматически декодирует необработанные данные на диске в str), но кодировка, которую он выбирает, - это то, что возвращает getdefaultencoding(), что, скорее всего, не является мусором. Вы не хотите использовать его для чтения файла пользователя, и вы действительно абсолютно никогда не хотите использовать его для чтения вашего собственного файла. Явно предоставьте encoding='utf-8', это позволит избежать большого горя в будущем. Если вам нужно вывести кодировку , возможно , используйте chardet.

1 голос
/ 18 февраля 2020

Ваш код не использует ни одну из функций defaultdict. Это работало бы одинаково, если бы вы использовали обычный dict. Это также очень запутанно, так как вы используете несколько имен переменных (например, item и self.__counts) для разных вещей в разных частях кода.

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

def function(self, starting_text, charas):
    self.__starting_text = starting_text
    self.__charas = charas

    with open(self.__file) as file:
        text = file.read().lower().replace('\n', ' ')

    ngrams = set(text[i : i + self.__n] for i in range(len(text)))

    for item in ngrams:
        for word in findall( item+".", text):
            self.counts[item][word[-1]] += 1

Это не совсем то же самое, что и ваш предыдущий код, так как он не идемпотентен (если вы будете вызывать его повторно, вы будете продолжать добавлять к счетам, вместо того, чтобы заменять старые счета новыми). Вы, вероятно, можете восстановить большую часть старого поведения, сначала поместив все символы в список (например, data), а затем установив значение в self.__counts только в конце. Но тогда я, вероятно, предпочел бы использовать collections.Counter вместо того, чтобы делать это вручную (и я, вероятно, построил бы Counter в l oop, вместо того, чтобы defaultdict делал это для меня).

На несвязанной заметке: в вашем коде используются двойные начальные подчеркивания для нескольких ваших атрибутов Как правило, это не рекомендуется для Python кода. Он включает искажение имен, которое преобразует имена типа self.__n в self._Class__n (если Class - это имя класса, в котором написан код, использующий __n, независимо от типа self). Обычно его не следует использовать для обозначения чего-либо как «частного», скорее, оно предназначено для предотвращения случайных конфликтов имен, когда вы не можете заранее знать, какие другие имена могут быть помещены в то же пространство имен объекта. Например, прокси или классу mixin может потребоваться разрешить пользователям доступ к любому виду имени атрибута, а разработчик класса не может знать, какими они будут (и разработчиком объектов, которые будут прокси или классов который будет смешан с может не знать о существовании класса прокси / mixin). Если вы просто хотите пометить свои атрибуты как «приватные», используйте одно подчеркивание, а не два. Конфиденциальность данных не обеспечивается в Python, и попытка использовать распределение имен для этого только вводит вас в заблуждение (внешний код, который хочет получить доступ к вашим атрибутам, все еще сможет это сделать). И искажение имени затрудняет отладку. Философия Python заключается в том, что все ее программисты являются «взрослыми по согласию», и поэтому им следует доверять, чтобы они знали, что не ведут себя неправильно с внутренностями другого кода (или справляются с последствиями, если они это делают).

1 голос
/ 18 февраля 2020

Вы должны изменить эти две строки:

self.counts = { item : data.count(item) for item in data }
out[item] = self.counts

Вам не нужна переменная out. проблема в том, что вы создаете «нормальный» дикт и назначаете его на self.count. Просто используйте:

self.counts[item].update({ item : data.count(item) for item in data })
...