Как работает автоматическая полная квалификация имен классов в Python? [относится к травлению объекта] - PullRequest
4 голосов
/ 15 мая 2011

(Можно непосредственно перейти к вопросу, далее вниз, и пропустить введение.)

Существует общая проблема с выбором объектов Python из пользовательских классов:

# This is program dumper.py
import pickle

class C(object):
    pass

with open('obj.pickle', 'wb') as f:
    pickle.dump(C(), f)

Фактически, попытка вернуть объект из другой программы loader.py с помощью

# This is program loader.py
with open('obj.pickle', 'rb') as f:
    obj = pickle.load(f)

приводит к

AttributeError: 'module' object has no attribute 'C'

Фактически, класс выбирается по имени ("C"), и программа loader.py ничего не знает о C.Общее решение состоит в импорте с

from dumper import C  # Objects of class C can be imported

with open('obj.pickle', 'rb') as f:
    obj = pickle.load(f)

. Однако у этого решения есть несколько недостатков, включая тот факт, что все классы, на которые ссылаются протравленные объекты, должны быть импортированы (их может быть много);кроме того, локальное пространство имен загрязняется именами из программы dumper.py.

Теперь решение этой проблемы состоит в том, чтобы полностью выполнить квалификацию объектов перед засолкой:

# New dumper.py program:
import pickle
import dumper  # This is this very program!

class C(object):
    pass

with open('obj.pickle', 'wb') as f:
    pickle.dump(dumper.C(), f)  # Fully qualified class

Отбор с оригиналомПрограмма loader.py, описанная выше, теперь работает напрямую (нет необходимости делать from dumper import C).

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

import pickle
import dumper  # This is this very program!

class D(object):  # New class!
    pass

class C(object):
    def __init__(self):
        self.d = D()  # *NOT* fully qualified

with open('obj.pickle', 'wb') as f:
    pickle.dump(dumper.C(), f)  # Fully qualified pickle class

Теперь также работает отладка с оригинальной программой loader.py (не нужно делать from dumper import C);print obj.d дает полностью квалифицированный класс, что я нахожу удивительным:

<dumper.D object at 0x122e130>

Это поведение очень удобно, поскольку только верхний, протравленный объект должен быть полностью квалифицирован с помощью модуляимя (dumper.C()).Но является ли это поведение надежным и задокументированным?почему классы подвергаются травлению по имени ("D"), но при отмене выбора решается, что атрибут self.d относится к классу dumper.D (а не к некоторому локальному D классу)?

PS: уточненный вопрос : Я только что заметил несколько интересных деталей, которые могут указывать на ответ на этот вопрос:

В программе травления dumper.py, print self.d отпечатки <__main__.D object at 0x2af450>,с первой dumper.py программой (без import dumper).С другой стороны, выполнение import dumper и создание объекта с dumper.C() в dumper.py приводит к print self.d print <dumper.D object at 0x2af450>: атрибут self.d автоматически определяется Python!Таким образом, похоже, что модуль pickle не играет никакой роли в хорошем поведении расслоения, описанном выше.

Таким образом, на самом деле возникает вопрос: почему Python преобразует D() в полностью квалифицированный dumper.D ввторой случай?это где-то задокументировано?

Ответы [ 2 ]

3 голосов
/ 15 мая 2011

Когда ваши классы определены в вашем главном модуле, вот где pickle ожидает найти их, когда они не выбраны. В вашем первом случае классы были определены в основном модуле, поэтому при запуске загрузчика loader является основным модулем, и pickle не может найти классы. Если вы посмотрите на содержимое obj.pickle, то увидите, что имя __main__ экспортируется как пространство имен ваших классов C и D.

Во втором случае dumper.py импортирует сам себя. Теперь у вас фактически есть два отдельных набора классов C и D: один набор в __main__ пространстве имен и один набор в dumper пространстве имен. Вы сериализуете один в пространстве имен dumper (посмотрите в obj.pickle, чтобы проверить).

pickle попытается динамически импортировать пространство имен, если оно не найдено, поэтому, когда loader.py запускает себе, pickle импортирует dumper.py и классы dumper.C и dumper.D.

Поскольку у вас есть два отдельных скрипта, dumper.py и loader.py, имеет смысл только определить классы, которые они разделяют в общем модуле импорта:

common.py

class D(object):
    pass

class C(object):
    def __init__(self):
        self.d = D()

loader.py

import pickle

with open('obj.pickle','rb') as f:
    obj = pickle.load(f)

print obj

dumper.py

import pickle
from common import C

with open('obj.pickle','wb') as f:
    pickle.dump(C(),f)

Обратите внимание, что хотя dumper.py создает дамп C(), в этом случае pickle знает, что это объект common.C (см. obj.pickle). При запуске loader.py он динамически импортирует common.py и успешно загружает объект.

2 голосов
/ 15 мая 2011

Вот что происходит: при импорте dumper (или from dumper import C) из dumper.py, вся программа снова анализируется (это можно увидеть, вставив отпечаток в модуль ). Такое поведение ожидается, поскольку dumper не является модулем, который уже был загружен (однако __main__ считается загруженным) - его нет в sys.modules.

Как показано в ответе Марка, импорт модуля естественно квалифицирует все имена, определенные в модуле, так что self.d = D() интерпретируется как принадлежащий к классу dumper.D при повторной оценке файла dumper.py (это эквивалентно синтаксическому анализу common.py, в ответе Марка).

Таким образом, объяснен трюк import dumper (или from dumper import C), и травление полностью квалифицирует не только класс C, но и класс D. Это облегчает расслоение внешней программой!

Это также показывает, что import dumper, выполненное в dumper.py, заставляет интерпретатор Python дважды анализировать программу, что не является ни эффективным, ни элегантным. Выборка классов в программе и их выборка в , другая , поэтому, вероятно, лучше всего сделать это с помощью подхода, изложенного в ответе Марка: маринованные классы должны находиться в отдельном модуле.

...