Search code examples
pythonpython-descriptors

How to get a variable name from within the function it call for assignment?


I need to get the name of a variable from within a function it call during assignment. For example if we take the code:


class CharField:
    
    def __init__(self):
        self.field_name = ???

class Person:
    name = CharField() # this is a field
    zodiac = CharField() # this is a field

How do i get the the field names (name and zodiac) from within the init method of CharField?

Please don't just comment that it is not possible since in one way or another the sqlalchemy library (and other python ORMs) all does it some how, we define models and fields with a dataclass just like above with Person and somehow sqlalchemy use it to generate a SQL statement containing the name of the field, so there must be a way..

I tried looking into python call stack for the name of the variable and technically it is there in the form of a string containing the entire line of code filepath.. name = CharField(). I could always extract it from this string but i have been told it is a messy way to do it that should not be used.


Solution

  • This can be done by making CharField a descriptor so you can obtain the attribute name that an instance of the descriptor is assigned to with the __set_name__ method.

    The descriptor should be defined with __get__ and __set__ methods to allow the value of attribute to be accessed and updated.

    To prevent the name of the instance attribute from shadowing the name of the descriptor, you should name the instance attribute differently, typically by prefixing the descriptor name with an underscore:

    class CharField:
        def __set_name__(self, owner, name):
            self.field_name = name
            self.private_name = '_' + name
    
        def __get__(self, obj, objtype=None):
            value = getattr(obj, self.private_name)
            print(f'Accessing field {self.field_name!r} giving {value!r}')
            return value
    
        def __set__(self, obj, value):
            print(f'Updating field {self.field_name!r} to {value!r}')
            setattr(obj, self.private_name, value)
    

    so that:

    class Person:
        name = CharField()
        zodiac = CharField()
    
    person = Person()
    person.name = 'Foo'
    print(person.name)
    

    would output:

    Updating field 'name' to Foo
    Accessing field 'name' giving Foo
    Foo