2

请参阅下面的更新

我什至不知道如何为我的问题起一个简短的标题。

在一个类中,我有一些类的类属性StringField

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

StringField构造函数可能会接收一个名为name. 如果没有给出,我希望它等于类属性的名称(first_namelast_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_namelast_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)
4

2 回答 2

2

我不知道 Django 是如何做到的。但你可以这样做:

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__()方法的标记来查找。

好的部分是魔法隐藏在基类中。和类只需要从它们继承,但它们本身并不杂乱无章ContainerName

使用元编程可能有一种更巧妙的方法来解决问题。

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

该解决方案不是元类编程,而是经过测试的工作代码。

编辑:这是代码的更改版本。原始代码旨在显示总体思路,但实际上并未初始化Name对象。实际上做初始化并不难,所以我改变了它。

于 2011-09-21T09:06:37.253 回答
1

为了像示例中那样神奇地发生,Python 需要是一种上下文相关的语言(据我所知,这不是,这不是那么遥远)。Django 使用ModelBase元类(在其他任务中)为字段设置详细名称。基本上,元类__new__循环遍历类属性以获取属性名称,并将它们添加到options。您可以更直接一点,直接更改字段。这是一个 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中的元类。

于 2011-09-22T02:21:49.120 回答