Set and Get @property method in Python by string variable
Asked Answered
V

2

13

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
Vasquez answered 26/7, 2013 at 20:48 Comment(5)
Could you give an example of a class and a call to your function, and explain how this doesn't give what you want? getattr handles properties as transparently as the . syntax, so I'm not sure what your problem is.Muskeg
Actually, before you go through any pains to answer this, let me snoop around in my code more. I thought the issue was that getattr did not work for properties, but if it should work, then I'm sure that the error lies somewhere else. I can clear more stuff up and maybe even find the solution.Vasquez
hasattr,setattr and getattr works in my code. but i declared it as request = property(get_request, set_request, del_request)Orton
So, I wrote part of the question wrong. setattr does not work for the property. In this specific case, we are trying to set the property to None.Vasquez
If you are trying to set the property, why not add a setter method?Sandbag
S
17

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
Sandbag answered 26/7, 2013 at 21:17 Comment(2)
getattr(A(), 'myprop') returns you value of the property immediately. So, setattr allows to invoke setter method of the propertyMorello
This doesn't work as advertized in the case where the setter defines the return value, b/c getattr(A, "myprop") will see an AttributeError. I've added a different/probably better answer.Guarantee
G
3

For the most general case follow this example:

class Foo(object):

    @property
    def bar(self):
        return self._spam

    @bar.setter
    def bar(self, v):
        self._spam = v

foo = Foo()
# prop = foo.bar.fset('Aaaah')  # will raise an error
# if you wanna access the setter do:
type(foo).bar.fset(foo, 'Aaaah')
print(foo.bar)
Guarantee answered 30/8, 2018 at 20:16 Comment(1)
I find this to be the most concise and clear answer.Seato

© 2022 - 2024 — McMap. All rights reserved.