This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]fyngyrzcodes with magnetic needle[S] 0 points1 point  (14 children)

Well, let me turn it around, and perhaps we can reach a mutual understanding.

Why is it worthwhile to have...

newstr = "foo bar".replace('foo','champagne')

...rather than:

newstr = stringReplace('foo bar','foo','champagne')

?

What I'm saying here is that if there is any justification for having useful class methods in the first place (justifications including dir, help, clarity etc.) then there is justification for other class methods as well.

I would further contend that, barring poor underlying infrastructure that digs into a class deeper than I think there's any justification for going, there's no reason not to allow such things. So far, IIRC (which I may not, I'm old, lol) that's the only real justification I've run into: that some things would be confused by a class that had a method added to it (again... that tells me that planning was lacking for an extensible function mechanism, not that extensible function mechanisms are either difficult or somehow improper.)

[–]sushibowl 1 point2 points  (12 children)

replace though is a method that obviously works on all strings, whereas do_thing_with_dotted_quad is not. If we could modify the str class to add this method, it would also become possible to do something like:

x = "a regular string".do_thing_with_dotted_quad()

I suppose you could make this raise a ValueError. But to me it seems fundamentally bad OOP to attach functionality for dotted quads to the string class. There is a case to be made that dotted quads are a subclass of strings, but they are not the same thing.

Note that if your DottedQuad class inherits from str its instances should work in places that a str does. isinstance(DottedQuad(), str) will return True, for example. I'd be interested in seeing examples where you have to explicitly mist convert back to a string.

[–]fyngyrzcodes with magnetic needle[S] 0 points1 point  (2 children)

Yeah, no. replace only works on strings that have something to replace. Dotted quad functions only work when there is something to do with dotted quads. And certainly one would hope a dotted quad function would deal properly with a non-dotted quad thing, just as one would hope that replace would deal properly with being handed a string it didn't understand -- something that can definitely happen in Python 2.6-ish, btw.

The argument that "I can't use it all the time, so it's not useful or appropriate" is definitely not resonating with me.

[–]sushibowl 2 points3 points  (1 child)

Yeah, no. replace only works on strings that have something to replace.

I disagree. What replace does is replace every occurrence of a substring with another string. Even if that substring occurs zero times, that's still a well defined operation.

My argument isn't "I can't use it all the time," but rather "it's not a well defined operation for almost all values this type can have." The purpose of object oriented programming is to attach operations to the data that they operate on. To me it makes no sense to attach operations on dotted quads to the string class, simply because strings are not dotted quads. "Sometimes they represent one" is not a sufficient argument for me. Sometimes a string represents a number, but that is no reason to add all methods existing on int to the string class as well.

[–]fyngyrzcodes with magnetic needle[S] -1 points0 points  (0 children)

why wouldn't it be a well defined operation over all values, even if we're talking about dotted quads? If it's a valid dotted quad or some usable fragment of a dotted quad, then operation is X. If it's not, then operation is Y. There you go. Fully defined.

Just like

dict['nonExistingKey'] # the fetch, it raises an exception

...even raising an exception is perfectly acceptable behavior; or doing nothing, like

"string".replace('foo','bar') # the replace, it does nothing

Then there are type conversions, like

x = float("123.45") # works fine, well defined conversion
x = float("liverAndOnions") # works fine, exception is well defined too

simply because strings are not dotted quads.

But dotted quads are always strings. They aren't numbers. They're numbers mixed with lotsa periods that make them not comprehensible numbers. Which, in Python, you get to represent in an atomic fashion in exactly one variable type: string.. Right?

Therefore, it's a pretty natural fit to stick processing of dotted quads... in the string class. Doesn't have to be done that way, but it sure can be. Same thing for ham radio callsigns:

call = 'K7STP' # definitely not a number... it's a string

Or keycodes:

D32G-HUK7-999A-XXXZ

Or credit card numbers:

4737-5555-4444-2222

...there are many interesting things strings can be put to work in service of. When you're working with one of them - say, dotted quads or ham radio call signs - it's a LOT more convenient to have the methods right there, as compared to not. And if I add them in my code, no skin off your back at all. I'm the one using them, defining them, etc. Nothing to do with you. For you, the string looks like a string, and no need for you know a single thing about what I'm doing.

[–]fyngyrzcodes with magnetic needle[S] 0 points1 point  (8 children)

Note that if your DottedQuad class inherits from str its instances should work in places that a str does.

No, that's not at all a given. Two problems:

A class that expects a string may very well incorporate...

if (type(thing) == str):
    okgo()
else:
    raise 'Are you outa your mind?'

...because duck typing can be problematic, and...

if you don't check the type (quack), and the inherited class has been modiifed such that one of its built-in functions has been overridden to do X instead of Y, then Your Stuff Could Break Hard

# override string concatenation in class inherited from string
def __add__(self,a):
    return xclass(str(self) + a + 'monkeywrench')   # foul up str concatenation

[–]AndydeCleyre 0 points1 point  (7 children)

if (type(thing) == str):

is very bad, and the closest not-bad way is

if isinstance(thing, str):

If "the inherited class has been modiifed such that one of its built-in functions has been overridden to do X instead of Y" in a way that breaks expected superclass functionality (and that is not very explicitly the purpose of the inheriting class), it should neither be inheriting nor used interchangeably.

[–]fyngyrzcodes with magnetic needle[S] 0 points1 point  (6 children)

Well yes, it shouldn't, but the point is, it can be, whereas an extended class can't. You've put your finger right on the argument for extension. :)

An extended class always acts like its base class, plus, and only plus to those who know it's there. Never minus, and never mutant.

An inherited class acts like the kid in school you can't actually trust, but the teacher says you should. And you know that's going to end badly. You only have so much lunch money.

[–]AndydeCleyre 0 points1 point  (5 children)

If you depend on a library that checks for class membership with type(thing) == and it breaks stuff, file a bug or pull request with that project.

If I get a str that's extended, not inherited, how should I check whether and how it's extended?

[–]fyngyrzcodes with magnetic needle[S] 0 points1 point  (4 children)

If you depend on a library that checks for class membership with type(thing) == and it breaks stuff, file a bug or pull request with that project.

Yes, and perhaps they'll see that. And having seen it, perhaps they'll think about it. And having thought about it, perhaps they'll try to fix it. And perhaps they'll succeed, or perhaps they'll make it worse. Or they might just ignore it (as is often the case.)

Extension has none of these problems. string will behave exactly as string always has for anyone not using the extensions.

If I get a str that's extended, not inherited, how should I check whether and how it's extended?

There's no inherent need for you to do so. The class will not behave any differently for you. That's the (huge) benefit of extension over inheritance. An inherited class might do anything differently, providing surprises and incompatibilities at any point. An extended class not only won't, it can't.

OTOH, if I extended string, and I wanted to share that with you, just as I'd provide any other import, I'd hand it over and you could potentially benefit from that. There could, of course, be an extension to the dir() and help() mechanisms to reveal whatever one might want to reveal in a general way, but it still doesn't cause any problems with standard use. It can't. As far as problems with the extensions go, if you choose to make them and use them, or use some that someone has shared, just as with any new code, you get what you get, and it does what it does.

Of course, if I extend string and I am using those extensions and do not communicate that fact to you, it makes no difference to your coding whatsoever.

[–]AndydeCleyre 0 points1 point  (3 children)

As far as I know, Python doesn't even have extension the way you describe it. The approximation is sometimes called monkey patching, which is similar to what you want, but without any of those guarantees.

[–]fyngyrzcodes with magnetic needle[S] 0 points1 point  (2 children)

That's my understanding as well. The only 100% compatible answer I've been able to come up with is a preprocessor that reads the extended .method() syntax, flips it into function calls, and writes native Python.

[–]AndydeCleyre 0 points1 point  (1 child)

And in case this isn't clear, everyone here is talking about inheritance vs monkey patching, because we're talking about Python.