[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

Newbie: Why doesn't this work

Chris

12/31/2007 4:56:00 PM

Hi Python Community:

Despite my new-ness to Python I have alreadhy been able to do some (I
think) amazing things. It is a truly elegant and smart language.

Yet, I can not seem to get a handle on something simple.

I would like to make a class which has private varaiables fName and
lName. It should have a property "name" which can get or set a name.
Something like as follows:

class Person:
def __init__(self, fName="", lName=""):
self.__fName = fName
self.__lName = lName

def __getattr__(self, attr):
if attr == "name":
return self.__fName + " " + self.__lName

def __setattr__(self, attr, value):
# this assumes that value is a tuple of first and last name
if attr == "name":
self.__fName, self.__lName = value


P = Person()

P.name = ("Joe", "Smith")

print P.name

This fails with the following note:

>>>
Traceback (most recent call last):
File "C:\Python\testObject.py", line 20, in <module>
print P.name
File "C:\Python\testObject.py", line 8, in __getattr__
return self.__fName + " " + self.__lName
TypeError: unsupported operand type(s) for +: 'NoneType' and 'str'

I don't understand why this fails. I thought perhaps I need to make
the __getattr__ function like this

def __getattr__(self, attr):
if attr == "name":
return self.__fName + " " + self.__lName
elif attr == "__fName":
return self.__fName
elif attr == "__lName":
return self.__lName

But that still fails.

Can someone please tell me what I am doing wrong?

Thansk in advance,

Chris (ct60@aol.com)
10 Answers

Gabriel Genellina

12/31/2007 5:37:00 PM

0

En Mon, 31 Dec 2007 14:56:02 -0200, <ct60@aol.com> escribi�:

> Hi Python Community:
>
> Despite my new-ness to Python I have alreadhy been able to do some (I
> think) amazing things. It is a truly elegant and smart language.
>
> Yet, I can not seem to get a handle on something simple.
>
> I would like to make a class which has private varaiables fName and
> lName. It should have a property "name" which can get or set a name.
> Something like as follows:
>
> class Person:
> def __init__(self, fName="", lName=""):
> self.__fName = fName
> self.__lName = lName
>
> def __getattr__(self, attr):
> if attr == "name":
> return self.__fName + " " + self.__lName
>
> def __setattr__(self, attr, value):
> # this assumes that value is a tuple of first and last name
> if attr == "name":
> self.__fName, self.__lName = value

__getattr__ gets called when an attribute lookup fails in the "standard"
places. Your version, when attr is not "name", does nothing - effectively
returning None for *any* other attribute.
You should raise an AttributeError instead: add this line after the if:
raise AttributeError, attr
__setattr__, on the other hand, is called with *any* attempt to set an
attribute. Your version effectively makes Person "read only"; the only
attribute you can set is "name". We could fix that, but there are better
ways. Forget about __getattr__ and __setattr__ and use a property:

class Person(object):
def __init__(self, fName="", lName=""):
self._fName = fName
self._lName = lName

def getName(self):
return "%s %s" % (self._fName, self._lName)

def setName(self, value):
self._fName, self._lName = value

name = property(getName, setName)

Note that:
- I've inherited Person from object. Person is then a "new-style" class,
and only "new-style" classes support properties (and other goodies).

- I've used _fName instead of __fName. The single _ is an indication to
the outside "warning, this is an implementation detail, don't play with
it". (Two underscores are (rarely) used when you want to minimize name
collisions with subclasses.)

- name is now a property, associated to the two functions getName and
setName. It works the way you want:

py> p = Person("John", "Smith")
py> p.name
'John Smith'
py> p.name = "Tom", "Sawyer"
py> p.name
'Tom Sawyer'

Something I don't like in this design, is that you can't assign the
property to itself; p.name = p.name fails because the "set" expects a
tuple and the "get" returns a single string.

--
Gabriel Genellina

Tim Chase

12/31/2007 5:39:00 PM

0

A couple items of note:

> class Person:

This should be "class Person(object)" to take advantage of some
of the features that new-style classes offer...particularly in
this case.

> def __init__(self, fName="", lName=""):
> self.__fName = fName
> self.__lName = lName
>
> def __getattr__(self, attr):
> if attr == "name":
> return self.__fName + " " + self.__lName
>
> def __setattr__(self, attr, value):
> # this assumes that value is a tuple of first and last name
> if attr == "name":
> self.__fName, self.__lName = value

if the attr isn't "name", no default behavior gets called here.
The common way, with new-style classes is to add

else:
parent(Person, self).__setattr__(attr, value)

Do be aware that this has some odd behaviors when what you put in
and what you get out are different types:

>>> p1.name = ("Joe", "Smith")
>>> p2.name = p1.name
Traceback (most recent call last):
File "x.py", line 22, in ?
p2.name = P.name
File "x.py", line 13, in __setattr__
self.__fName, self.__lName = value
ValueError: too many values to unpack

(slightly munged traceback as it actually came from the test
input file rather than the interactive prompt)

-tim



Chris

12/31/2007 6:02:00 PM

0

Thanks you Gabriel and Timm for your thoughtful responses. I am very
appreciative.

I had heard about the properties function, but wanted to understand
the old syntax first before I tried that. Thanks to your responses, I
was able to see what the problem was.

Here is a solution I came up with:

class Person():
def __init__(self, fName="", lName=""):
self.__fName = fName
self.__lName = lName

def __getattr__(self, attr):
if attr == "name":
return self.__fName + " " + self.__lName
else:
return self.__dict__[attr]

def __setattr__(self, attr, value):
# this assumes that value is a tuple of first and last name
if attr == "name":
self.__fName, self.__lName = value
else:
self.__dict__[attr] = value


P = Person("Joe", "Smith")

print P.name

P.name = ("Jane", "Doe")

print P.name

This works as expected printing "Joe Smith" and then "Jane Doe".

To be honest, I think the above old syle (I guess) method is pretty
cool and elegant.

Thanks again and have a GREAT NEW YEAR!!

Chris (ct60@aol.com)

Gabriel Genellina

12/31/2007 7:05:00 PM

0

En Mon, 31 Dec 2007 16:01:38 -0200, <ct60@aol.com> escribi�:

> Thanks you Gabriel and Timm for your thoughtful responses. I am very
> appreciative.
>
> I had heard about the properties function, but wanted to understand
> the old syntax first before I tried that. Thanks to your responses, I
> was able to see what the problem was.
>
> Here is a solution I came up with:
>
> class Person():
> def __init__(self, fName="", lName=""):
> self.__fName = fName
> self.__lName = lName
>
> def __getattr__(self, attr):
> if attr == "name":
> return self.__fName + " " + self.__lName
> else:
> return self.__dict__[attr]
>
> def __setattr__(self, attr, value):
> # this assumes that value is a tuple of first and last name
> if attr == "name":
> self.__fName, self.__lName = value
> else:
> self.__dict__[attr] = value
>

Almost. __getattr__ is called *after* searching the name in the standard
places, including self.__dict__, so there is no point in looking there
again as it will fail certainly (with a KeyError instead of the right
AttributeError).

def __getattr__(self, attr):
if attr == "name":
return self.__fName + " " + self.__lName
raise AttributeError, attr

(__getattr__ and __setattr__ despite their names, are not very symmetrical)

> To be honest, I think the above old syle (I guess) method is pretty
> cool and elegant.

If you have one or two special attributes, may be fine. But when you want
to define many properties, it becomes unmanageable.
And notice that the mere existence of those methods slows down A LOT *all*
attribute accesses, not just the ones related to your special names.

> Thanks again and have a GREAT NEW YEAR!!

You too!

--
Gabriel Genellina

petr.jakes.tpc

1/1/2008 6:58:00 PM

0

Hi all,

I am trying to understand new-style classes in Python and I have found
your postings here.

Gabriel, if I understand it properly, it is necessary to define get/
set/del/doc methods for each attribute for which I want to set the
"property" data descriptor (which triggers get/set/del/doc function
calls upon access to defined attribute).

My question is: is it possible to set the "property" for any attribute
when I do not know what will be the name of the attribute in the
future?

Thanks for your reply

Petr Jakes

Gabriel Genellina

1/1/2008 7:37:00 PM

0

En Tue, 01 Jan 2008 16:57:41 -0200, <petr.jakes.tpc@gmail.com> escribi�:

> Gabriel, if I understand it properly, it is necessary to define get/
> set/del/doc methods for each attribute for which I want to set the
> "property" data descriptor (which triggers get/set/del/doc function
> calls upon access to defined attribute).

Yes. Not all of them, just what you need (by example, a read-only property
doesn't require a set method; I think the get() docstring is used if no
explicit doc is provided; most of the time I don't care to define del...)
You don't "have" to define anything you don't need; moreover, you don't
have to define a property if you don't actually need it. The very first
example in the builtin functions documentation for "property" is a bad
example: if all your property does is to wrap a stored attribute, forget
about the property and use the attribute directly.

> My question is: is it possible to set the "property" for any attribute
> when I do not know what will be the name of the attribute in the
> future?

Uhm... I don't understand the question. Perhaps if you think of a concrete
case...?

--
Gabriel Genellina

petr.jakes.tpc

1/1/2008 9:01:00 PM

0

> > My question is: is it possible to set the "property" for any attribute
> > when I do not know what will be the name of the attribute in the
> > future?
>
> Uhm... I don't understand the question. Perhaps if you think of a concrete
> case...?

Thanks for reply,

few minutes after i posted my question, I have realized my posting is
probably a "nonsense".

If I understand it properly, it is necessary, in the respect of
encapsulation, to define attributes inside the class (even it is
possible to add a new attribute to the existing object outside class
definition).

The meaning of my question was:
Is it possible to define some general sort of set/get/del/doc rules
for the attributes which are defined in the code AFTER the
instantiation of an object.

I am sending this question even I feel such a "on the fly" creation of
the attribute is probably not a good trick.

Petr Jakes

Steven D'Aprano

1/1/2008 9:54:00 PM

0

On Tue, 01 Jan 2008 13:01:24 -0800, petr.jakes.tpc wrote:

>> > My question is: is it possible to set the "property" for any
>> > attribute when I do not know what will be the name of the attribute
>> > in the future?
>>
>> Uhm... I don't understand the question. Perhaps if you think of a
>> concrete case...?
>
> Thanks for reply,
>
> few minutes after i posted my question, I have realized my posting is
> probably a "nonsense".
>
> If I understand it properly, it is necessary, in the respect of
> encapsulation, to define attributes inside the class (even it is
> possible to add a new attribute to the existing object outside class
> definition).
>
> The meaning of my question was:
> Is it possible to define some general sort of set/get/del/doc rules for
> the attributes which are defined in the code AFTER the instantiation of
> an object.
>
> I am sending this question even I feel such a "on the fly" creation of
> the attribute is probably not a good trick.

Like all dynamic modification of classes, it is liable to abuse, but
Python allows such things and trusts the programmer not to be foolish.

class Parrot(object):
pass


def set_property(cls, propertyname, defaultvalue=None, docstring=''):
"""Make a readable, writable but not deletable property."""
privatename = '_' + propertyname
setattr(cls, privatename, defaultvalue)
def getter(self):
return getattr(self, privatename)
def setter(self, value):
setattr(self, privatename, value)
setattr(cls, propertyname, property(getter, setter, None, docstring))


set_property(Parrot, 'colour', 'red',
"""Parrots have beautiful coloured plumage.""")



Now that you know how to do it, please don't. Except for the lack of
docstring, the above is much better written as:


class Parrot(object):
colour = 'red'





--
Steven

petr.jakes.tpc

1/2/2008 4:11:00 AM

0

Steven,
thanks for a nice explanation.

I am trying to experiment a little bit with new-style class and I am
confused why following example always returns 0 (zero). I was
expecting <generator object> will return values from 0 to 9 and finaly
an Exception.

class GenExample(object):

def getInfo(self):
for no in range(10):
yield no

myNo=property(getInfo)

gen=GenExample()
print gen.myNo.next()
print gen.myNo.next()
.
.
print gen.myNo.next()

--
Petr Jakes

Gabriel Genellina

1/2/2008 5:14:00 AM

0

En Wed, 02 Jan 2008 01:11:12 -0300, <petr.jakes.tpc@gmail.com> escribió:

> I am trying to experiment a little bit with new-style class and I am
> confused why following example always returns 0 (zero). I was
> expecting <generator object> will return values from 0 to 9 and finaly
> an Exception.
>
> class GenExample(object):
>
> def getInfo(self):
> for no in range(10):
> yield no
>
> myNo=property(getInfo)
>
> gen=GenExample()
> print gen.myNo.next()
> print gen.myNo.next()
> .
> .
> print gen.myNo.next()

Doing it this way, works as intended:

py> gen=GenExample()
py> myNo = gen.myNo
py> print myNo.next()
0
py> print myNo.next()
1
....
py> print myNo.next()
9
py> print myNo.next()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration

Why? I've seen you have read about the descriptor protocol. Methods are
implemented as non-data descriptors. When you retrieve the "myNo"
attribute from the "gen" instance, a bound method is created, combining
the original getInfo function (stored into the class) with the gen
instance. This is done *each* time the attribute is retrieved, and each
time you get a different method object. That's why in your original
example you always got 0: all methods are newly created ones, starting the
iteration. (After the call finishes, as nobody holds any additional
reference to it, the method object becomes a candidate to be garbage
collected and eventually is deleted)

Now it should be clear why my example above does work: I'm using always
the *same* method object, previously saved into the "myNo" variable.

This also explains a popular optimization technique: move attribute
lookups out of a critical loop. That is, transforming this:

for item in container:
out.writerecord(item)

into this:

writerecord = out.writerecord
for item in container:
writerecord(item)

Of course this should NOT be done blindly.

--
Gabriel Genellina