Извлечь назначенное имя переменной

См. обновление ниже

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

В классе у меня есть некоторые атрибуты класса StringField класса:

class Authors(Table):
    # id field is already present 
    first_name = StringField(maxLength=100)
    last_name = StringField(maxLength=100)

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

Можно ли извлечь имя переменной, которой будет назначен созданный экземпляр? Думаю, мне нужно использовать модуль inspect?

Я вижу, что Джанго делает это:

Каждый тип поля, кроме ForeignKey, ManyToManyField и OneToOneField, принимает необязательный первый позиционный аргумент — подробное имя. Если подробное имя не указано, Django автоматически создаст его, используя имя атрибута поля, преобразуя символы подчеркивания в пробелы.

В этом примере подробное имя — «имя человека»:

first_name = models.CharField("person's first name", max_length=30)

В этом примере подробное имя — «имя»:

first_name = models.CharField(max_length=30)

Но я не нахожу в исходном коде Django 1.3.1 часть, которая делает то, что мне нужно.

ОБНОВЛЕНИЕ:

Для упрощения:

class Field():
    def __init__(self, field_name=None):
        if not field_name:
            field_name = ??? # some magic here to determine the name
        print(field_name)

class Table():
    first_name = Field()
    last_name = Field()

Запуск этого должен напечатать first_name и last_name

РЕШЕНИЕ:

class Field():
    def __init__(self, name=None):
        self._name = name

class Table():
    first_name = Field()
    last_name = Field()



for attrName, attr in Table.__dict__.items():
    if isinstance(attr, Field):
        if attr._name is None:
            attr._name = attrName

print(Table.first_name._name)
print(Table.last_name._name)

person warvariuc    schedule 21.09.2011    source источник
comment
Распространяется ли StringField на django.db.models.Field? Не могли бы вы опубликовать источник StringField? Или, если вы его не писали, расскажите нам, откуда вы берете StringField, потому что он не является частью Django.   -  person Derek Kwok    schedule 21.09.2011
comment
Не усложняете ли вы это больше, чем необходимо? Не могли бы вы пролить свет на причины, по которым вы используете StringField?   -  person LaundroMat    schedule 21.09.2011
comment
Я процитировал документы Django, потому что вижу, что Django использует это, чтобы объяснить, чего я хочу, и я не смог найти соответствующий код. StringField — мой собственный класс. Я просто хочу реализовать поведение, подобное Django.   -  person warvariuc    schedule 21.09.2011
comment
В вашем обновлении вот что произойдет. Python будет вызывать Field() без аргументов, создавая экземпляр класса Field. Затем он привяжет полученный аргумент к имени first_name. До того, как произойдет это связывание, Field.__init__() не может знать имя, с которым оно будет связано в будущем, так что предложенная вами магия действительно является магией.   -  person steveha    schedule 21.09.2011


Ответы (2)


Я не знаю, как Джанго это делает. Но вы можете сделать это следующим образом:

class WantFixup(object):
    def new_instance(self, name, derived_name):
        cls = type(self)
        if name is None:
            name = derived_name.replace('_', ' ')
        return cls(name)

class Container(WantFixup):
    def __init__(self, name=None):
        self.name = name
    def __repr__(self):
        return "Container('%s')" % str(self.name)

class WillFixup(object):
    def __init__(self):
        cls = type(self)
        for name in cls.__dict__:
            o = cls.__dict__[name] # look up object from name
            if not isinstance(o, WantFixup):
                continue
            print("calling %s.new_instance('%s', '%s')" % (o, o.name, name))
            self.__dict__[name] = o.new_instance(o.name, name)

class Name(WillFixup):
    first_name = Container("given name")
    last_name = Container()

Вот пример приведенного выше кода в действии:

>>> import auto_name
>>> n = auto_name.Name()
calling Container('None').new_instance('None', 'last_name')
calling Container('given name').new_instance('given name', 'first_name')
>>> print(n.__dict__)
{'first_name': Container('given name'), 'last_name': Container('last name')}
>>> print(auto_name.Name.__dict__)
{'__module__': 'auto_name', 'last_name': Container('None'), 'first_name': Container('given name'), '__doc__': None}
>>> 

Класс WantFixup служит двум целям. Во-первых, все классы, которые унаследованы от него, могут быть обнаружены с помощью isinstance(); если наш экземпляр объекта называется o, мы можем протестировать его как isinstance(o, WantFixup). Во-вторых, он предоставлял функцию метода .new_instance() любому классу, который наследуется от него.

Класс Container — это пример контейнера, который может нуждаться в исправлении. Обратите внимание, что он наследуется от WantFixup.

Класс WillFixup содержит метод .__init__(), выполняющий исправление всех классов, которые унаследованы от него. Это просто перебирает все в словаре класса и вызывает функцию метода .new_instance() для каждого из них, передавая имя.

Наконец, класс Name наследуется от WillFixup и содержит два экземпляра Container. Поскольку он наследуется от WillFixup, вызывается метод WillFixup.__init__(). Как видно из примера, атрибут first_name имеет атрибут .name, установленный на 'given name', но last_name не был установлен, поэтому он исправлен, чтобы атрибут .name был установлен на 'last name'.

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

Запутанная часть здесь заключается в том, что для экземпляра first_name установлено значение экземпляра Container, имя которого исправлено, и фактически он будет использоваться для хранения вещей. Но класс Name содержит экземпляр Container, который просто используется для хранения имени класса и в качестве маркера для поиска методом .__init__().

Хорошая часть заключается в том, что магия скрыта в базовых классах. Классы Container и Name просто должны наследоваться от них, но сами по себе они не загромождены вещами.

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

http://www.ibm.com/developerworks/linux/library/l-pymeta/index.html

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

РЕДАКТИРОВАТЬ: это измененная версия кода. Исходный код должен был показать общую идею, но на самом деле он не инициализировал объект Name. На самом деле выполнить инициализацию несложно, поэтому я изменил ее.

person steveha    schedule 21.09.2011
comment
Код изменен и улучшен. Возможно, теперь это будет иметь больше смысла. - person steveha; 21.09.2011
comment
Я не полностью понял ваш код, но я думаю, что у меня есть идея. Добавлю решение вопроса. - person warvariuc; 21.09.2011

person    schedule
comment
Спасибо! Теперь я, наконец, пришел и к метаклассам. Но я думаю, что видел способ сделать то, что мне нужно, с помощью модуля проверки, хотя сейчас мне это не нужно. - person warvariuc; 22.09.2011