1

I'm looking for a way performing some custom escaping whenever certain properties are set, and corresponding unescaping code when those properties are retrieved from one of my objects. I've looked at __setattr__, __getattr__ and __getattribute__ but can't see how they relate to properties.

For example I have a class such as:

class Bundle(object):
    def __init__(self):
        self.storage = Storage()

    @property
    def users_name(self):
        """
        Get the user's name
        """
        return self.storage.users_name

    @users_name.setter
    def users_name(self, users_name):
        """
        Set the user's name
        """
        # only permit letters and spaces. 
        if not re.match('^[A-Za-z ]+$', users_name):
            msg = "'%s' is not a valid users_name. It must only contain letters and spaces" % users_name
            log.fatal(msg)
            raise ConfigurationError(msg)

        self.storage.users_name = users_name
    ...

There are several such properties on the object.

What I want is a method that will only affect some of my properties (such as users_name, but not 'storage'), and will escape/unescape the value when set/retrieved.

  1. __getattr__ doesn't seem right because this code should be called for new and existing properties.
  2. __setattr__ might work but I don't understand how it fits in with my @property methods. For example, will __setattr__ always be called instead of my custom methods? How can I call my custom methods from within __setattr__? Is there an easy way of only affecting my @property properties without storing a list of properties to work on?
  3. __getattribute__ I tried using this but ended up in infinite loops. That aside, my questions are roughly the same as for __setattr__ regarding whether it's possible to delegate to the methods declared with @property (and how to do that?), and how best to say "only work for these properties and not just any property of this class.

What's the best way of approaching this?

-- update --

To elaborate: This particular 'Bundle' class acts as a proxy, storing config values in different backends - in this case a FileConfigBackend that wraps ConfigParser. ConfigParser uses '%' signs for interpolation. However, some of the values I want to set can legitimately contain percent signs so I need to escape them. However, instead of escaping them explicitly for every property, I want to have some sort of magic method that will be called every time a property is set. It will then run replace('%', '%%') on the string. Similarly, I want a method that every time a property is retrieved will run replace('%%', '%') on the value before returning it.

Also, since the majority of the properties in 'Bundle' are simply proxied to the backend storage, it'd be nice if there was a way to say 'if the property is in this list, just call self.storage.PROPERTY_NAME = VALUE'. Sometimes though I want to be able to override that assignment, for example to run a regex on the value to set.

So really, I'm looking for a way of saying 'when a property is set, always call this method. Then if a concrete @property.setter exists, call that instead of doing self.storage.key = value'.

(doh! I meant __getattr__ & __setattr__ not __get__ and __set__ - updated my question to reflect that)

Jakub Wasilewski
  • 2,916
  • 22
  • 27
user1491250
  • 1,831
  • 4
  • 18
  • 21
  • 2
    You almost never want to use `__getattribute__`. Use `__getattr__` instead. The difference is that `__getattribute__` is *always* called, while `__getattr__` is called only for missing attributes, and you don't end up in infinite loops. Anyway I really don't understand your question. Can you provide an example of how you want to use the class and how it fails as you are doing now? – Bakuriu Jun 26 '13 at 12:48
  • Who downvoted this question to "-1" ? It is a legitimate doubt. – jsbueno Jun 26 '13 at 12:51
  • My previous comment was wrong, `__setattr__` is always called. In this case, there might be a solution here, but it would probably be more hacky than the additional decorator. – Jakub Wasilewski Jun 26 '13 at 13:23
  • OK, perhaps that's another question. Thanks – user1491250 Jun 26 '13 at 13:26
  • Just added a different answer, which does this with magic methods. It looks more convenient and is shorter, but is potentially more dangerous also. – Jakub Wasilewski Jun 26 '13 at 13:40

3 Answers3

2

The @property decorator creates an object following the descriptor protocol, which is where the __get__, __set__ methods live.

The best way I can think of to add additional behavior to some properties would be creating your own decorator. This decorator would follow the same protocol, wrap the property originally created by Python, and add your desired escaping/unescaping behavior. This would allow you to mark the 'special' escaped properties as such:

@escaped
@property
def users_name(self):
  ...

Only those properties would get the special treatment. Here is a quick example of how this might be implemented:

class escaped:
  def __init__(self, property_to_wrap):
    # we wrap the property object created by the other decorator
    self.property = property_to_wrap

  def __get__(self, instance, objtype=None):
    # delegate to the original property
    original_value = self.property_to_wrap.__get__(instance, objtype)
    # ... change the data however you like
    return frotz(original_value)

  def __set__(self, instance, new_value):
    actual_value = frob(new_value)
    self.property.__set__(instance, actual_value)

  ...

The same should be repeated for all the descriptor methods. You will also have to delegate the getter, setter, deleter methods of property itself (to allow you to use syntax like @users_name.setter with your wrapped property. You can look at the descriptor guide for help.

Some details can be found here too: How do Python properties work?.

Community
  • 1
  • 1
Jakub Wasilewski
  • 2,916
  • 22
  • 27
0

Read what Uncle Google suggests in this area and by trial-end-error all become very clear. In your case you probably do not need __getatribute__ because it is called on access instead of looking into obect's __dict__.

I did not used to decorators as using property() built-in is more clear to me:

http://docs.python.org/2/library/functions.html#property


Methods __set__() and __get__() are for descriptor classes, it means you need to have class defined for your property/-ies. Good example:

http://docs.python.org/2/howto/descriptor.html#descriptor-example


What you may actually looking for is the attribute access available by overriding __setattr__() and __getattr()__ methods in your class (available if you derive from object in 2.7.x):

http://docs.python.org/2/reference/datamodel.html#customizing-attribute-access

Michał Fita
  • 1,183
  • 1
  • 7
  • 24
0

I provided a different answer using a decorator, but if you really prefer to go with the magic methods, there is a way:

class Magic(object):
    @staticmethod
    def should_be_escaped(property_name):
        return not "__" in property_name

    def __getattribute__(self, property):
        value = object.__getattribute__(self, property)
        if Magic.should_be_escaped(property):
            value = value.replace("%%", "%")
        return value

    def __setattr__(self, property, value):
        if Magic.should_be_escaped(property):
            value = value.replace("%", "%%")
        object.__setattr__(self, property, value)

Calling object.__getattribute__ and object.__setattr__ allows you to reach the standard behavior from your magic methods, which should include resolving the properties. Using those is a way to avoid the infinite loops you mentioned.

The reason this is the worse solution lies in should_be_escaped, where you have to decide whether a property should be escaped or not based on the name. This is difficult to do correctly, and you have to care about special names, your storage variable, etc.

For example, the implementation of should_be_escaped above is so incomplete that it will try to call replace on your object's methods, so a check should be added for the property type. There is a lot of those corner-cases. This is why I suggested the decorator solution as cleaner - it explicitly marks who receives the special behavior and has no nasty unexpected consequences down the road.

Jakub Wasilewski
  • 2,916
  • 22
  • 27