Newbie: Why doesn't this work

C

ct60

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 ([email protected])
 
G

Gabriel Genellina

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

Tim Chase

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:
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
 
C

ct60

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 ([email protected])
 
G

Gabriel Genellina

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!
 
P

petr.jakes.tpc

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
 
G

Gabriel Genellina

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

petr.jakes.tpc

My question is: is it possible to set the "property" for any attribute
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
 
S

Steven D'Aprano

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'
 
P

petr.jakes.tpc

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

Gabriel Genellina

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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top