"indexed properties"...

Gabriel Genellina gagsl-py2 at yahoo.com.ar
Fri May 16 23:27:31 EDT 2008


(warning: it's a rather long message)

En Fri, 16 May 2008 12:58:46 -0300, David C. Ullrich  
<dullrich at sprynet.com> escribió:
> 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ó:
>>
>>> 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.

I suppose those people were talking about this usage:

   def getfoo(self): return self._foo
   def setfoo(self, value): self._foo = value
   foo = property(getfoo, setfoo)

Just use a plain foo attribute instead. Almost nobody will notice the  
difference, and it's faster, easier to maintain, easier to test, less  
magic... But if you *do* have more to do besides handling the _foo  
attribute, using a property is OK, at least for me. And from your other  
comments, I can see it's fine for you too.

>>> 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".

Oh, sure. I missed them a lot until they became available in Python. And I  
even wrote a fake property mixin class for Python 2.1 (horrible and slow!).
Now I understand better what you're saying.

>   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.

Sure, that looks like a good candidate for using a property.

>> 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.)

No, I don't necesarily expect it, but I try to maintain that behavior as  
long as I can; I try to keep things simple and intuitive. This is a rather  
used idiom:

   items = foo.items = []
   for x in someiterator:
     if somecondition:
       ...
       items.append(x)
       ...

That works fine if foo.items is items - but if items were a property that  
holds its own internal list, the code above would be broken. In that case,  
replacing a simple attribute with a property would *not* be equivalent;  
and I try to follow the "principle of least astonishment" and avoid such  
situations as long as possible.

(I think that in OP the equivalent of m.row[0] = [1,2,3] isn't very usual  
- mostly because there isn't a list literal, I presume)

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

Unfortunately that doesn't work. Properties get their "magic" when  
searched in the class namespace - a property set as an instance attribute  
doesn't behave as a property at all, it's just an attribute as any other.

>> 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.)

The above class works fine in the simple cases I tested it - including the  
bug involving two AClass instances. Adding "row" and "col" views is rather  
simple:

class AClass(object):
   def __init__(self):
     self.cell = Matrix2x2()
     self.row = ViewAsRow(self.cell)
     self.col = ViewAsCol(self.cell)

class ViewAsRow(object):
   def __init__(self, matrix):
     self.cells = matrix.cells

   def __getitem__(self, index):
     return self.cells[index][:]

   def __setitem__(self, index, row):
     self.cells[index][:] = row

class ViewAsCol(object):
   def __init__(self, matrix):
     self.cells = matrix.cells

   def __getitem__(self, index):
     return [row[index] for row in self.cells]

   def __setitem__(self, index, col):
     for row, value in zip(self.cells, col):
       row[index] = value

There is no need for the ViewAsCol nor ViewAsRow classes to know they're  
being used inside an AClass instance. (A better design would avoid to use  
matrix.cells - but I was constrained by the original Matrix2x2 design that  
I didn't want to modify.)

> (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.)

But you may have many references to the *same* data and view it in  
different ways - you don't have to walk thru the intermediate AClass  
"container" because AClass doesn't "store" data; the data is "stored" in  
the list itself, AClass instances only contain the name "cell" bound to  
the Matrix2x2 object.

In OP terms, all objects in Python act like those inheriting from TObject,  
not like basic types (numbers, char, string...). An important difference  
is that Python takes care of automatic object deletion when they're no  
longer used, but in OP you have to manage the object's lifetime yourself.  
So in OP it is important *who* owns some object; and sharing a TList  
instance -by example- isn't so frequent because when the list is freed all  
its references become invalid (so one needs some kind of notification...)
In Python, on the other side, many objects may have a reference to the  
same other one, like the Matrix above - it won't disappear until nobody  
else references it. This sharing behavior may be a Good Thing or a Bad  
Thing, depending on the circumstances. By example, the ViewAsRow class  
above *could* return an actual list from the Matrix2x2 instance, but I  
choose not to, to emphasize that it's a *view* of the underlying data  
(else, modifications to the returned list would alter the data itself).

> 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.

I don't understand what you call "data-passing protocol"; as far as I can  
tell, there is no data-passing, only names refering to the same and unique  
object instance.

-- 
Gabriel Genellina




More information about the Python-list mailing list