22

Suppose I have a class like this:

class Alphabet(object):
     __init__(self):
         self.__dict = {'a': 1, 'b': 2, ... 'z': 26}

     @property
     def a(self):
         return self.__dict['a']

     @property
     def b(self):
         return self.__dict['b']

     ...

     @property
     def z(self)
         return self.__dict['z']

This would be a long and cumbersome task to define and it seems highly redundant. Is there a way to dynamically create these properties? I know you can dynamically create attributes with the built-in setattr, but I want to have control over read/write/delete access (for this reason I want to use a property). Thanks!

Michael
  • 2,031
  • 6
  • 21
  • 27
  • 2
    doing `@property\ndef something()` is equivalent to `something = property(something)`. Which is equivalent to doing (in this case) `self.setattr('a', property(lambda self: self.__dict['a']))` – Joel Cornett Jun 10 '12 at 09:29
  • 2
    @JoelCornett: The second statement in your comment is incorrect, you need to set it on the class, not the instance: https://paste.aeum.net/show/106/. The reason for this is because of [how descriptors work](http://docs.python.org/reference/datamodel.html#implementing-descriptors): *The following methods only apply when an instance of the class containing the method (a so-called descriptor class) appears in an owner class (the descriptor must be in either the owner’s class dictionary or in the class dictionary for one of its parents).* – ThiefMaster Jun 10 '12 at 09:35
  • @ThiefMaster: I stand corrected :) – Joel Cornett Jun 10 '12 at 09:38

2 Answers2

42

Don't use properties but implement the following methods:

  • __getattr__(self, name)
  • __setattr__(self, name, value)
  • __delattr__(self, name)

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

Your __getattr__ method could look like this:

def __getattr__(self, name):
    try:
        return self.__dict[name]
    except KeyError:
        msg = "'{0}' object has no attribute '{1}'"
        raise AttributeError(msg.format(type(self).__name__, name))
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
-2

Don't do that. Just let the consumers of the class at the __dict directly and trust them not to screw it up. Remember, we're all consenting adults here!

Ned Batchelder explains better than I can:


Keep data out of your variable names

The question reminded me of others I've seen on Stack Overflow or in the #python IRC channel:

  • How do I see if a variable exists?
  • How do I use a variable as the name of another variable?
  • How do I use a variable as part of a SQL table name?

The thing all these have in common is trying to bridge the gap between two domains: the data in your program, and the names of data in your program. Any time this happens, it's a clear sign that you need to move up a level in your data modeling. Instead of 26 lists, you need one dictionary. Instead of N tables, you should have one table, with one more column in it.

Katriel
  • 120,462
  • 19
  • 136
  • 170
  • 3
    Ned has a good point, but there is a goal here of encapsulation. That's not just important for preventing mistakes (which consenting adults can make, too), but as a matter of convenience. The internal code of the class probably has a good reason to treat the dictionary as a dictionary while client code treats it as a set of attributes. – Karl Knechtel Jun 10 '12 at 10:46
  • 4
    A problem with accessing `__dict` directly is it's ugly, especially with the name mangling that will be involved. A little syntactic sugar for clients goes a long way sometimes, not to mention the level of indirection it provides will facilitate possible future changes. – martineau Jun 10 '12 at 11:04
  • in regards to @martineau's comment, the name mangling would make this solution verry ugly, this is because the `dict` is prefixed with a double underscore. Also it is an unspoken rule that in python anything prefixed with an underscore should not be touched. Also if you use a property to define a class attribute then you don't want it do be modified. – error 1044 May 22 '23 at 14:19