return an object of a different class

S

Steven D'Aprano

My point stands.

Your point is wrong. Ints and longs weren't unified because it is
"cryptonic" (cryptic?), but to avoid the user needing to care about the
difference between ints and longs. I've been coding in Python since
version 1.5, and I can tell you I hated the need to write code like this:


def make_int(obj):
try:
return int(obj)
except ValueError:
return long(int)

To say nothing of:

def add(x, y):
try:
return x+y
except OverflowError:
return long(x)+long(y)


Having the int class automatically promote instances to long as needed
was a HUGE win for simplicity and readability, while still saving memory
and performance for small ints.

Still not convinced that it's allowed to return objects of a different
type? From Python 3.1:
reversed((1,2,3))
reversed("abcd")
reversed([1,2,3])
reversed(range(10))
<range_iterator object at 0xb7cd8a88>
 
W

Westley Martínez

My point stands.

Your point is wrong. Ints and longs weren't unified because it is
"cryptonic" (cryptic?), but to avoid the user needing to care about the
difference between ints and longs. I've been coding in Python since
version 1.5, and I can tell you I hated the need to write code like this:


def make_int(obj):
try:
return int(obj)
except ValueError:
return long(int)

To say nothing of:

def add(x, y):
try:
return x+y
except OverflowError:
return long(x)+long(y)


Having the int class automatically promote instances to long as needed
was a HUGE win for simplicity and readability, while still saving memory
and performance for small ints.

Still not convinced that it's allowed to return objects of a different
type? From Python 3.1:
reversed((1,2,3))
reversed("abcd")
reversed([1,2,3])
reversed(range(10))
<range_iterator object at 0xb7cd8a88>
But again, these types all have an identical interface.
 
P

Piet van Oostrum

Richard Thomas said:
If you don't want to use a factory function I believe you can do this:

class MyNumber(object):
def __new__(cls, n):
if n <= 100:
cls = SmallNumbers
else:
cls = BigNumbers
return object.__new__(cls, n)

But you have to be aware that the initializers are not called in this way.
 
J

Jean-Michel Pichavant

alex23 said:
I don't think they were intended to be inconsistent types, but
subclasses of the same type. Returning different subclasses is exactly
what a factory is for. And given Python's propensity for duck typing,
they don't even really need to be subclassed for the same object, they
just need the interface that is required.



What if they have different implementations rather than methods?
The answer is quite simple, from a OOP POV, the factory class/function
returns a MyNumber instance, not a SmallNumber nor BigNumber one. This
is what the documentation should state. Note that SmallNumber and
BigNumber instances are both MyNumber instances. But from a user POV,
there's no need to know about Small and Big implementations. All you
need to know is the base classe MyNumber methods.

Regarding duck typing, it is all about implementing an interface that is
not explicit. That way, factory functions return consistent types if
they return instances with a common interface. That's perfectly legit.

What is not legit, is to return different objects for which the caller
has to test the type to know what attributes he can use.

JM
 
S

Steven D'Aprano

What is not legit, is to return different objects for which the caller
has to test the type to know what attributes he can use.

Well, I don't know... I'm of two minds.

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

The problem is that re.search and re.match return None instead of a match-
object when they don't find anything. Perhaps they should return an empty
match object?


But on the other hand, there is a well known failure mode caused by doing
this:

# Strip everything after a comment symbol.
offset = text.find("#")
text = text[:eek:ffset]

See the bug? If there's no # in the string, it drops the last character.
The most insidious part of the bug is that you might not notice, if your
strings end with whitespace. If str.find() returned None, at least you
would get a nice TypeError as soon as you tried to use it as a integer
offset.

So, it's not clear to me whether under *these* circumstances it's better
to return a completely different type, which cannot possibly be mistaken
for success, or a magic value of the same type.

(The third alternative is to raise an exception, and the unspeakably
awful alternative is to have a single global error flag for the operation
which must be checked.)
 
J

Jean-Michel Pichavant

Steven said:
What is not legit, is to return different objects for which the caller
has to test the type to know what attributes he can use.

Well, I don't know... I'm of two minds.

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'group'

The problem is that re.search and re.match return None instead of a match-
object when they don't find anything. Perhaps they should return an empty
match object?


But on the other hand, there is a well known failure mode caused by doing
this:

# Strip everything after a comment symbol.
offset = text.find("#")
text = text[:eek:ffset]

See the bug? If there's no # in the string, it drops the last character.
The most insidious part of the bug is that you might not notice, if your
strings end with whitespace. If str.find() returned None, at least you
would get a nice TypeError as soon as you tried to use it as a integer
offset.

So, it's not clear to me whether under *these* circumstances it's better
to return a completely different type, which cannot possibly be mistaken
for success, or a magic value of the same type.

(The third alternative is to raise an exception, and the unspeakably
awful alternative is to have a single global error flag for the operation
which must be checked.)
A rule cannot be 100% effective in any situation. At least that's the
case of this one. First of all, I think the rule is incomplete as I
stated it, None is an acceptable inconsistent type and can be returned
in some cases (if documented). Thanks to how logical operators work, you
can sometimes nicely handle 'None' values:

myMatch = re.search(pattern, text)
print (myMatch and myMatch.group()) or 'No text found'


Your second point with the find method illustrates another problem,
handled by a very similar rule:
"Return values must be consistent in their meaning".
This is my personal preferences but I quite dislike this type of
inconsistent meaning, find should have raised an exception or returned
the None value if nothing is found. The -1 value meaning 'not found' is
very 80's old C style coding.

JM
 
D

Dennis Lee Bieber

Your second point with the find method illustrates another problem,
handled by a very similar rule:
"Return values must be consistent in their meaning".
This is my personal preferences but I quite dislike this type of
inconsistent meaning, find should have raised an exception or returned

We do have .index() to raise the exception when not found.
 

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

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,905
Latest member
Kristy_Poole

Latest Threads

Top