Set and Get @property method in Python by string variable

dbmikus picture dbmikus · Jul 26, 2013 · Viewed 17.1k times · Source

Currently I have a generalized function where you can pass in an attribute name and a class (it would also work with specific object instances, but I am using classes), and the function will look up and operate on that attribute by calling

getattr(model_class, model_attribute)

and it will modify the attribute by calling (on an object instance this time)

settattr(model_obj, key, value)

However, I have a class where we have an @property method defined instead of a simple attribute, and setattr does not work. How do I dynamically get the @property based on a string name for that property method?

Perhaps I could use __dict__ but that seems dirty and not as safe.

Edit: example code

The generalized function

def process_general(mapping, map_keys, model_class, filter_fn, op_mode=op_modes.UPDATE):
    """
    Creates or updates a general table object based on a config dictionary.

    `mapping`: a configuration dictionary, specifying info about the table row value
    `map_keys`: keys in the mapping that we use for the ORM object
    `model_class`: the ORM model class we use the config data in
    `op_mode`: the kind of operation we want to perform (delete, update, add, etc.)

    Note that relationships between model objects must be defined and connected
    outside of this function.
    """
    # We construct a dictionary containing the values we need to set
    arg_dict = make_keyword_args(map_keys, mapping)

    # if we are updating, then we must first check if the item exists
    # already
    if (op_mode == op_modes.UPDATE):
        # Find all rows that match by the unique token.
        # It should only be one, but we will process all of them if it is the
        # case that we didn't stick to the uniqueness requirement.
        matches = filter_fn()

        # Keep track of the length of the iterator so we know if we need to add
        # a new row
        num_results = 0
        for match in matches:
            # and we set all of the object attributes based on the dictionary
            set_attrs_from_dict(match, arg_dict)
            model_obj = match
            num_results += 1
        # We have found no matches, so just add a new row
        if (num_results < 1):
            model_obj = model_class(**arg_dict)

        return model_obj

    # TODO add support for other modes. This here defaults to add
    else:
        return model_class(**arg_dict)

An example class passed in:

class Dataset(db.Model, UserContribMixin):
    # A list of filters for the dataset. It can be built into the dataset filter form dict
    # in get_filter_form. It's also useful for searching.
    filters     = db.relationship('DatasetFilter', backref='dataset')

    # private, and retrieved from the @property = select
    _fact_select = db.relationship('DatasetFactSelect', order_by='DatasetFactSelect.order')

    @property
    def fact_select(self):
        """
        FIXME: What is this used for?

        Appears to be a list of strings used to select (something) from the
        fact model in the star dataset interface.

        :return: List of strings used to select from the fact model
        :rtype: list
        """

        # these should be in proper order from the relationship order_by clause
        sels = [sel.fact_select for sel in self._fact_select]
        return sels

Answer

FastTurtle picture FastTurtle · Jul 26, 2013

Calling getattr(model_class, model_attribute) will return the property object that model_attribute refers to. I'm assuming you already know this and are trying to access the value of the property object.

class A(object):

    def __init__(self):
        self._myprop = "Hello"

    @property
    def myprop(self):
        return self._myprop

    @myprop.setter
    def myprop(self, v):
        self._myprop = v

prop = getattr(A, "myprop")

print prop
# <property object at 0x7fe1b595a2b8>

Now that we have obtained the property object from the class we want to access its value. Properties have three methods fget, fset, and fdel that provide access to the getter, settter, and deleter methods defined for that property.

Since myprop is an instance method, we'll have to create an instance so we can call it.

print prop.fget
# <function myprop at 0x7fe1b595d5f0>

print prop.fset
# <function myprop at 0x7fe1b595d668>

print prop.fdel  # We never defined a deleter method
# None

a = A()
print prop.fget(a)
#  Hello