请参阅下面的更新
我甚至不知道如何为我的问题做一个简短的标题。
在课堂上,我有一些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
模块?
我看到Django这样做了:
每个字段类型,但ForeignKey,ManyToManyField和 OneToOneField采用可选的第一个位置参数 - a 详细的名字。如果没有给出详细的名称,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)
答案 0 :(得分: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
的{{1}}属性设置为.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
对象。实际执行init并不难,所以我改了它。
答案 1 :(得分: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
class argument * 而不是__metaclass__
property:
class Container(object, metaclass=MetaContainer):
first_name = Field()
foo = Field('Foobar')
通过直接使用元类创建容器的中间基类,而不是metaclass
参数或__metaclass__
属性,可以编写与metaclasses in in Python 2 and 3一起使用的版本:
ContainerBase = MetaContainer('ContainerBase', (object,), {})
class Container(ContainerBase):
first_name = Field()
foo = Field('Foobar')
* 有关更改的原因,请参阅PEP 3115: Metaclasses in Python 3000。