"indexed properties"...

David C. Ullrich dullrich at sprynet.com
Fri May 16 11:58:46 EDT 2008


On Thu, 15 May 2008 10:59:41 -0300, "Gabriel Genellina"
<gagsl-py2 at yahoo.com.ar> wrote:

>En Wed, 14 May 2008 18:15:41 -0300, David C. Ullrich <dullrich at sprynet.com> escribió:
>
>> Having a hard time phrasing this in the form
>> of a question...
>>
>> The other day I saw a thread where someone asked
>> about overrideable properties and nobody offered
>> the advice that properties are Bad. So maybe we've
>> got over that. I suppose properties could have
>> Bad consequences if a user doesn't know they exist
>> and think that a certain property of an object is
>> just an ordinary attribute. But that applies to
>> almost any aspect of any language.
>
>Which "bad consequences" are you thinking of? 

Didn't have anything specific in mind - there I was
just playing devil's advocate. I've seen a lot of people
here say things like "properties should only be used
when refactoring" without any explanation that I could
see of why properties were bad things. I was referring
to whatever problems they had in mind.

>Apart from the user not being aware of the complexity of certain operations, or a getter/setter very time-consuming (in that case I'd use public getXXX/setXXX functions instead, to emphasize that "something" is happening, not just an attribute lookup)
>
>> If a person comes from, say, Object Pascal (Delphi)
>> then properties are hard to live without. The
>
>You should read the article "Python is not Java" if you haven't done it yet.
>http://dirtsimple.org/2004/12/python-is-not-java.html

I suspect you misunderstood my point there. I actually read that
article years ago. Been using Python for years. I'd never confuse
it with OP, I promise (luckily I don't know any Java and don't
intend to). I really didn't express what I meant very well - what
I should have said was more like "If you're accustomed to properties
from a language like OP they seem like a very useful addition to
Python".

Looking at the article it seems like two points are relevant.
(i) I'm well aware that chains of attribute accesses don't
get compiled away in Python the way they do in OP.

(ii) Regarding the bit about how getters and setters are
evil, and it's also bad to wrap things in properties when
simple attributes would do. Yes. Thanks for pointing
that out, but I wouldn't do that - using a property where
a simple attribute would do would be stupid. But they
_can_ be much nicer than having to use explicit getters
and setters, in cases where a simple attribute won't do.

I got started on this thinking about making myself a
nicer interface (nicer-seeming to me) to the wxPython
grid object. Being able to say

  grid[row, col] = value

seems much nicer and more natural than

  grid.SetCellValue(row, col, value)

(yes, _that_ one is just __get(set)item__,
no property needed), just as 

  window.pos = (x,y)

seems more natural than

  window.SetPos(x,y);

in these cases the work involved in changing the cell
value or the window position is going to make the
extra overhead of the property interface irrelevant.

>> other day I decided I wanted what OP calls an
>> "indexed property" or "array property". Couldn't
>> figure out how to make a _property_ behave that way.
>> So I read a little bit about descriptors, and a
>> few minutes later I had an indexedproperty thing
>> that works just like property, except it gives
>> an indexed property! This is just too cool.
>>
>> Why? For example, a Matrix should have a row[n]
>> property allowing things like
>>
>> m.row[0] = m.row[1] + m.row[2]
>>
>> Ok, you could do _that_ by just making row
>> an ordinary list of Row objects. But then
>> you'd have to say
>>
>> m.row[0] = Row([1,2,3])
>>
>> where I want to be able to say
>>
>> m.row[0] = [1,2,3]
>>
>> and have the Row created automatically.
>
>One could make *row* an object with a custom __getitem__/__setitem__ in this case. 

But then [see below]

>But I prefer to have as little magic as possible on my objects: if it says m.row[0] = [1,2,3] I 
>expect m.row[0] to actually *be* that list (whenever possible), and not any other object initialized 
>from the [1,2,3] arguments. (Maybe this is some overreaction against C++ "magic" constructors and such horrible things...)

Whatever - the idea here is that m.row[0] is going to walk and quack 
exactly like that list would, but also do other things that the liist
can't do, like you can add two of them.

(When you say anobject.aproperty = 2 you also expect aproperty to
be 2? But if aproperty is a property that's not necessarily so - this
seems like as much an argument against properties in general as
against my version. Or perversion, if you like.)

>> _Also_ with these indexed properties my Matrix
>> can have m.row[j] and m.col[k] that look exactly
>> the same to a client - we don't want to store a
>> list of rows internally and also store the same
>> data in a list of columns. Too cool.
>
>The same can be achieved having m.row and m.col be custom objects like I said above.
>
>> Hmm, none of that's a valid excuse for a post here.
>> Um, right, here we go: Anyone see problems or
>> possible improvements with the implementation
>> of indexedproperty below?
>
>Note that the property object (or your indexedproperty) is a *class* attribute. That is, shared among all instances.
>
>> class indexedproperty(object):
>>   def __init__(self, getitem=None, setitem=None):
>>     self.getitem = getitem
>>     self.setitem = setitem
>>
>>   def __get__(self, obj, owner):
>>     self.obj = obj
>>     return self
>
>Here is the problem. You can't store "obj" in "self" because it is shared among all instances. Your examples don't show any problem because all property accesses are immediately followed by a getitem access using the same object. Try this:
>
>x = AClass()
>y = AClass()
>x.cell[0,0] = 1
>print x.cell[1,1]   # output: 0
>y.cell[x.cell[0,0], x.cell[0,0]] = 2
>print y.cell[1,1]   # should be 2, still 0
>print x.cell[1,1]   # should still be 0, but changed, now 2

Eewps. I did realize that the "indexedproperty" object was going to be
shared, but I thought this sort of problem would be taken care of by
the fact that self.obj was reset each time __get__ was called. I
guess not.

_A_ workaround would be to simply not have indexedproperties
be class attributes, instead saying self.whatever =
indexedproprty(whatever) in __init__.

>A workaround would be to return another object in __get__ instead of self, which remembers the "obj" instance, or a closure, or...
>But I don't really see the point, it's a lot easier to use another object for .cell (it's more clear, doesn't break encapsulation, divides responsabilities...)
>
>class Matrix2x2(object):
>   def __init__(self):
>     self.cells = [[0,0], [0,0]]
>
>   def __setitem__(self, (row, col), value):
>     self.cells[row][col] = value
>
>   def __getitem__(self, (row, col)):
>     return self.cells[row][col]
>
>class AClass(object):
>   def __init__(self):
>     self.cell = Matrix2x2()

Well, something like this is where I was before coming up with
the indexedproperty thing. Unless I'm missing something, which
has happened before, what you write above can't work, because
self.cell has to know what Matrix2x2 created it so it knows
what AClass instance to modify when it's modified. (No, we
can't just leave the state information in self.cells; think of the
case of a Matrix with a row[] property and also a col[]
property - they have to be modifying the data stored in
the owning matrix.)

So it would have to be more something like (untested,
I don't have Python on this machine right now)

class Matrix2x2(object):
   def __init__(self, owner):
     self.owner = owner

   def __setitem__(self, (row, col), value):
     self.owner.data[row][col] = value

   def __getitem__(self, (row, col)):
     return self.owner.data[row][col]

class AClass(object):
   def __init__(self):
     self.cell = Matrix2x2(self)
     self.data = [[0, 0], [0, 0]]

(again, it doesn't have to be like that in this case,
but it does in the case where various "properties"
like cells are giving different sorts of access to the
same data, which data then has to be stored in AClass.)

And then that's the point to the indexedproperty;
the code passing data back and forth doesn't need
to be rewritten with each property, it's all done
inside the implementation of the indexedproperty
class. I realized that I could just use object attributes
instead, but it seems to me that indexedproperty
encapsulates the data-passing protcol once and for
all instead of requiring it to be redone each time.

Thanks for the comments, especially for pointing out
the bug. Before doing this I reproduced a Python
implementation of "property". Gonna have to look
that up instead of figuring it out to make sure I got
it right, then look at it to see why property
doesn't have the same problem... 
David C. Ullrich



More information about the Python-list mailing list