See the update below

I even don't know how to make a short title for my problem.

In a class I have some class attributes of StringField class:

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

StringField constructor may receive an argument called name. If it's not given, i want it to be equal to class attribute's name (first_name, last_name in the example above).

Is it possible to extract the name of the variable the created instance is going to be assigned to? I guess i have to use inspect module?

I see Django does this:

Each field type, except for ForeignKey, ManyToManyField and OneToOneField, takes an optional first positional argument -- a verbose name. If the verbose name isn't given, Django will automatically create it using the field's attribute name, converting underscores to spaces.

In this example, the verbose name is "person's first name":

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

In this example, the verbose name is "first name":

first_name = models.CharField(max_length=30)

But i don't find in Django 1.3.1 source code the part which is doing what i need.

UPDATE:

To simplify:

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()

Running this should print first_name and last_name

SOLUTION:

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)
有帮助吗?

解决方案

I don't know how Django does it. But you could do it this way:

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()

Here is an example of the above code in action:

>>> 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}
>>> 

The class WantFixup serves two purposes. First, all classes that inherit from it can be detected using isinstance(); if our object instance is named o, we can test it like isinstance(o, WantFixup). Second, it provided the .new_instance() method function to any class that inherits from it.

The class Container is an example of a container that might need fixup. Note that it inherits from WantFixup.

The class WillFixup contains a .__init__() method that performs fixup on all classes that inherit from it. This simply loops over everything in the class dictionary, and calls the .new_instance() method function for each one, passing in the name.

Finally, class Name inherits from WillFixup and contains two instances of Container. Because it inherits from WillFixup, the method WillFixup.__init__() is called. As you can see from the example, first_name has a .name attribute set to 'given name' but last_name wasn't set, so it is patched to have its .name attribute set to 'last name'.

The .__init__() function is supposed to set up the new class instance. As long as all the special WantFixup class instances are in the parent class, the .__init__() method will automatically loop over them and set them up.

The confusing part here is that the instance has first_name set to an instance of Container that has the name patched, and will actually be used to store stuff. But the class Name contains an instance of Container that is just used to store the name of the class, and as a marker for the .__init__() method to find.

The good part is that the magic is hidden away in the base classes. The Container and Name classes just need to inherit from them, but are not themselves cluttered with stuff.

There might be a slicker way to solve the problem using metaprogramming.

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

This solution isn't metaclass programming, but it is tested, working code.

EDIT: This is a changed version of the code. The original code was intended to show the general idea, but didn't actually init the Name object. It's not hard to actually do the init, so I changed it.

其他提示

In order for the magic to happen as in the sample, Python would need to be a context-sensitive language (which is isn't, as far as I know, which isn't that far). Django uses the ModelBase meta-class to (among other tasks) set verbose names for the fields. Basically, the metaclass's __new__ loops over the class attributes to get the attribute names, adding them to the options. You can be a little more direct and alter the fields directly. Here's a Python 2 example:

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 is almost the same, but you use the metaclass class argument* rather than the __metaclass__ property:

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

You can write a version that works with metaclasses in in Python 2 and 3 by creating an intermediate base class for the container using the metaclass directly, rather than the metaclass argument or __metaclass__ property:

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

class Container(ContainerBase):
    first_name = Field()
    foo = Field('Foobar')

* For the reason for the change, see PEP 3115: Metaclasses in Python 3000.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top