style query: function attributes for return codes?

G

george young

[python 2.3.3, x86 linux]
I recently found myself writing something like:

def get_connection():
if tcp_conn():
if server_allows_conn():
return 'good_conn'
else:
return 'bad_auth'
else:
return 'no_server'


cn = get_connection()
if cn == 'good_con': ...


This is obviously just evil, since a misspelling in the string
return is treacherous. I'm considering function attributes:

def get_connection():
if tcp_conn():
if server_allows_conn():
return get_connection.GOOD
else:
return get_connection.BAD_AUTH
else:
return get_connection.NO_SERVER
get_connection.GOOD = 1
get_connection.BAD_AUTH = 2
get_connection.NO_SERVER = 3


If I put this function in it's own module, the solution is obvious:

GOOD_CONN = 1
def get_connection():
...
return GOOD_CONN


But if this is a small utility function that belongs inside a
larger module/class, I would like to have it's return values
closely associated with the function, not just another value in
the parent class.

Is anybody using function attributes like this?
Is this good python style?


-- George Young
 
S

Steven Bethard

george said:
This is obviously just evil, since a misspelling in the string
return is treacherous. I'm considering function attributes:

def get_connection():
if tcp_conn():
if server_allows_conn():
return get_connection.GOOD
else:
return get_connection.BAD_AUTH
else:
return get_connection.NO_SERVER
get_connection.GOOD = 1
get_connection.BAD_AUTH = 2
get_connection.NO_SERVER = 3

Although in most cases this is probably okay, you're not guaranteed that
the name of your function will stay the same, so there are some hazards
in this style:
.... return f.y * x
....Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 2, in f
AttributeError: 'NoneType' object has no attribute 'y'

One option is to turn your function into a class:

class get_connection(object):
GOOD = 1
BAD_AUTH = 2
NO_SERVER = 3
def __new__(cls):
if tcp_conn():
if server_allows_conn():
return cls.GOOD
else:
return cls.BAD_AUTH
else:
return cls.NO_SERVER

This is a little sneaky here -- because you only need shared state
between all instances of get_connection, you don't actually ever need to
create an instance. So I've overridden __new__ to return the result of
the function instead. This allows you to call the function just like
you would have before. I haven't tested the code above, but here's a
simpler example that works:
.... X = 42
.... def __new__(cls):
.... return cls.X
....42

If you need to do this with a function that takes more parameters, you
can just add them to the __new__ declaration:
.... X = 42
.... def __new__(cls, s):
.... return cls.X / float(len(s))
....1.0

Despite the fact that this particular use seems a little sneaky to me, I
do usually end up turning most functions that seem to need attributes
into classes (usually with a __call__ method defined).

Steve
 
S

Steven Bethard

george said:
[python 2.3.3, x86 linux]
I recently found myself writing something like:

def get_connection():
if tcp_conn():
if server_allows_conn():
return 'good_conn'
else:
return 'bad_auth'
else:
return 'no_server'


cn = get_connection()
if cn == 'good_con': ...


This is obviously just evil, since a misspelling in the string
return is treacherous. I'm considering function attributes:

def get_connection():
if tcp_conn():
if server_allows_conn():
return get_connection.GOOD
else:
return get_connection.BAD_AUTH
else:
return get_connection.NO_SERVER
get_connection.GOOD = 1
get_connection.BAD_AUTH = 2
get_connection.NO_SERVER = 3


If I put this function in it's own module, the solution is obvious:

GOOD_CONN = 1
def get_connection():
...
return GOOD_CONN


But if this is a small utility function that belongs inside a
larger module/class, I would like to have it's return values
closely associated with the function, not just another value in
the parent class.

Sorry, I also meant to add that the other obvious way of dealing with
this kind of thing is to make the results keyword parameters:

def get_connection(GOOD=1, BAD_AUTH=2, NO_SERVER=3):
if tcp_conn():
if server_allows_conn():
return GOOD
else:
return BAD_AUTH
else:
return NO_SERVER

This has the benefit that if your user wants different return values
they can specify them, but the disadvantage that someone improperly
calling the function with more than 0 parameters will get, instead of an
error message, a strange return value.

Steve
 
R

Robert Kern

Steven said:
Sorry, I also meant to add that the other obvious way of dealing with
this kind of thing is to make the results keyword parameters:

def get_connection(GOOD=1, BAD_AUTH=2, NO_SERVER=3):
if tcp_conn():
if server_allows_conn():
return GOOD
else:
return BAD_AUTH
else:
return NO_SERVER

This has the benefit that if your user wants different return values
they can specify them, but the disadvantage that someone improperly
calling the function with more than 0 parameters will get, instead of an
error message, a strange return value.

Another disadvantage is that one must compare the return value by value
and not by name. That is, I cannot do something like this:

code = get_connection()
if code == NO_SERVER:
...

--
Robert Kern
(e-mail address removed)

"In the fields of hell where the grass grows high
Are the graves of dreams allowed to die."
-- Richard Harter
 
S

Steven Bethard

Robert said:
Another disadvantage is that one must compare the return value by value
and not by name. That is, I cannot do something like this:

code = get_connection()
if code == NO_SERVER:
...

Good point. The class-type implementation does allow you to do this:
.... GOOD = 1
.... BAD_AUTH = 2
.... NO_SERVER = 3
.... def __new__(cls):
.... if tcp_conn():
.... if server_allows_conn():
.... return cls.GOOD
.... else:
.... return cls.BAD_AUTH
.... else:
.... return cls.NO_SERVER
....3

Steve
 
H

holger krekel

Hi George,

[george young Fri, Dec 10, 2004 at 10:45:47AM -0500]
[python 2.3.3, x86 linux]
I recently found myself writing something like:

def get_connection():
if tcp_conn():
if server_allows_conn():
return 'good_conn'
else:
return 'bad_auth'
else:
return 'no_server'

cn = get_connection()
if cn == 'good_con': ...


This is obviously just evil, since a misspelling in the string
return is treacherous.

Right.

I usually like to look at such problems from the angle of
what is most convenient for the *caller* side?

And having to adress function attributes does
not seem convenient. I'd probably like to do from
the caller side something like:

conn = get_connection()
if conn.good:
...
elif conn.badauth:
...
elif conn.noserver:
...

which allows you to freely choose and hide your actual
implementation at the "called" side. Example:

class Connection(object):
def __init__(self, **kw):
for name in kw:
assert name in ('good', 'badauth', 'noserver'), name
setattr(self, name, kw[name])

def get_connection():
if tcp_conn():
if server_allows_conn():
return Connection(good=True)
else:
return Connection(badauth=True)
else:
return Connection(noserver=True)

And btw, the view of "what do i want at the caller side?"
is natural if you do test-driven development and actually
first write your tests. Another reason why testing is
a good thing :)

cheers & HTH,

holger
 
G

george young

Although in most cases this is probably okay, you're not guaranteed that
the name of your function will stay the same, so there are some hazards
in this style:

... return f.y * x
...
Traceback (most recent call last):
File "<interactive input>", line 1, in ?
File "<interactive input>", line 2, in f
AttributeError: 'NoneType' object has no attribute 'y'

Yes, I was worried about this.
One option is to turn your function into a class:

class get_connection(object):
GOOD = 1
BAD_AUTH = 2
NO_SERVER = 3
def __new__(cls):
if tcp_conn():
if server_allows_conn():
return cls.GOOD
else:
return cls.BAD_AUTH
else:
return cls.NO_SERVER

This is a little sneaky here -- because you only need shared state
between all instances of get_connection, you don't actually ever need to
create an instance. So I've overridden __new__ to return the result of
the function instead. This allows you to call the function just like
you would have before. I haven't tested the code above, but here's a
simpler example that works:

... X = 42
... def __new__(cls):
... return cls.X
...
42

Hmm, this is quite clever, and indeed does what I want.

I hesitate to adopt it, though, because it *looks* sneaky.
For readable and maintainable code, I think it may be a bit
too hackish... The original impetus for my post was to find
a clear, maintainable form.

I wonder about returning an object that tests True if all is
ok, and has boolean attributes to query if not True...:

def get_connection():
class Ret:
def __init__(self, badauth=False, noserver=False):
self.badauth = badauth
self.noserver = noserver
def __nonzero__(self):
return not(self.badauth and self.noserver)
if tcp_conn():
if server_allows_conn():
return Ret()
else:
return Ret(badauth=True)
else:
return Ret(noserver=True)

ret = get_connection()
if not ret:
if ret.badauth:
...


still seems a bit cumbersome in definition,
though the use is not bad...


-- George
 
R

Reinhold Birkenfeld

holger said:
Hi George,

[george young Fri, Dec 10, 2004 at 10:45:47AM -0500]
[python 2.3.3, x86 linux]
I recently found myself writing something like:

def get_connection():
if tcp_conn():
if server_allows_conn():
return 'good_conn'
else:
return 'bad_auth'
else:
return 'no_server'

cn = get_connection()
if cn == 'good_con': ...


This is obviously just evil, since a misspelling in the string
return is treacherous.

Right.

I usually like to look at such problems from the angle of
what is most convenient for the *caller* side?

And having to adress function attributes does
not seem convenient. I'd probably like to do from
the caller side something like:

conn = get_connection()
if conn.good:
...
elif conn.badauth:
...
elif conn.noserver:
...

which allows you to freely choose and hide your actual
implementation at the "called" side. Example:

class Connection(object):
def __init__(self, **kw):
for name in kw:
assert name in ('good', 'badauth', 'noserver'), name
setattr(self, name, kw[name])

def get_connection():
if tcp_conn():
if server_allows_conn():
return Connection(good=True)
else:
return Connection(badauth=True)
else:
return Connection(noserver=True)

That's evil, because "if conn.good" raises an AttributeError instead of
evaluating to False if the connection is not good. You would have to
inizialize all three attributes in every construction of a Connection
object.

Reinhold
 
H

holger krekel

[Reinhold Birkenfeld Fri, Dec 10, 2004 at 08:42:10PM +0100]
holger said:
class Connection(object):
def __init__(self, **kw):
for name in kw:
assert name in ('good', 'badauth', 'noserver'), name
setattr(self, name, kw[name])

def get_connection():
if tcp_conn():
if server_allows_conn():
return Connection(good=True)
else:
return Connection(badauth=True)
else:
return Connection(noserver=True)

That's evil, because "if conn.good" raises an AttributeError instead of
evaluating to False if the connection is not good. You would have to
inizialize all three attributes in every construction of a Connection
object.

Ups, you are right of course. I somehow managed to delete the line ...

class Connection(object):
good = badauth = noserver = False

thanks for pointing it out.

holger
 

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
474,213
Messages
2,571,109
Members
47,701
Latest member
LeoraRober

Latest Threads

Top