Extract assigned variable name
-
24-01-2021 - |
题
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.