Извлечь присвоенное имя переменной - PullRequest
2 голосов
/ 21 сентября 2011

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

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

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

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 модуль?

Я вижу, Django делает это:

Каждый тип поля, кроме 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)

Ответы [ 2 ]

2 голосов
/ 21 сентября 2011

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

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.На самом деле не сложно выполнить инициализацию, поэтому я изменил ее.

1 голос
/ 22 сентября 2011

Чтобы магия произошла, как в примере, Python должен был быть контекстно-зависимым языком (который, насколько я знаю, не так уж и далеко). Django использует метакласс ModelBase для (среди прочих задач) установки подробных имен для полей. По сути, метакласс __new__ перебирает атрибуты класса, чтобы получить имена атрибутов, добавляя их к опциям . Вы можете быть немного более прямым и изменять поля напрямую. Вот пример Python 2:

class Field(object):
    def __init__(self, name=None):
        self.name = name
    def __str__(self):
        if self.name:
            return self.name
        return type(self).__name__
    def __repr__(self):
        return '%s(%s)' % (type(self).__name__, repr(self.name))

class MetaContainer(type):
    @classmethod
    def dub(metacls, name):
        return name.replace('_', ' ').capitalize()

    def __new__(cls, name, bases, attrs):
        for attr in attrs:
            if issubclass(type(attrs[attr]), Field) and attrs[attr].name is None:
                attrs[attr].name = MetaContainer.dub(attr)
        return super(MetaContainer, cls).__new__(cls, name, bases, attrs)

class Container(object):
    __metaclass__ = MetaContainer
    first_name = Field()
    foo = Field('Foobar')

cntr = Container()
cntr.first_name

Python 3 почти такой же, но вы используете metaclass аргумент класса * вместо __metaclass__ свойство :

class Container(object, metaclass=MetaContainer):
    first_name = Field()
    foo = Field('Foobar')

Вы можете написать версию, которая работает с метаклассами в Python 2 и 3 , создав промежуточный базовый класс для контейнера, используя непосредственно метакласс, а не аргумент metaclass или свойство __metaclass__ :

ContainerBase = MetaContainer('ContainerBase', (object,), {})

class Container(ContainerBase):
    first_name = Field()
    foo = Field('Foobar')
<Ч />

* Об изменении см. PEP 3115: Метаклассы в Python 3000 .

...