basic language question

S

Stephan Diehl

Once in a while, I get bitten by the fact, that mutating list methods such
as 'append' or 'extend' return None instead of the (mutated) list itself.
Is there a compelling reason for that? I googled around, but didn't find
anything about this.
Thanks

Stephan
 
T

Terry Reedy

Stephan Diehl said:
Once in a while, I get bitten by the fact, that mutating list methods such
as 'append' or 'extend' return None instead of the (mutated) list itself.
Is there a compelling reason for that? I googled around, but didn't find
anything about this.

Some people, including GvR, think in-place mutators should not return
anything to distinguish them from methods which return a new object
and leave original untouched. One argument, I presume based on some
experience, is that if both types of method look the same, people will
forget difference, leading to obscure bugs. Others, confident in
their ability to remember and not make mistakes, want mutated object
returned so they chain methods togethers. While Python is generally a
consenting-adults language, this is one place where Guido opted for
the supposedly 'safer' choice.

'Returners' could wrap no-return mutators with functions or
derived-class methods that do return the object, but I have never seen
anyone post a complete module that does so for, say, all list
mutators.

Terry J. Reedy
 
S

Stephan Diehl

Terry said:
Stephan Diehl said:
Once in a while, I get bitten by the fact, that mutating list methods such
as 'append' or 'extend' return None instead of the (mutated) list itself.

[...]


'Returners' could wrap no-return mutators with functions or
derived-class methods that do return the object, but I have never seen
anyone post a complete module that does so for, say, all list
mutators.

That's actually a nice idea. I might just do that.
Terry J. Reedy

Stephan
 
M

Michael Peuser

Stephan Diehl said:
Once in a while, I get bitten by the fact, that mutating list methods such
as 'append' or 'extend' return None instead of the (mutated) list itself.
Is there a compelling reason for that? I googled around, but didn't find
anything about this.

There is a very old religious law saying: Thou shalt not cause side effects
by a function.
There is some wisdom in it but it is easily forgotten with languages which
do not differ between functions and routines.

Kindly
Michael P
 
J

John Roth

Michael Peuser said:
There is a very old religious law saying: Thou shalt not cause side effects
by a function.
There is some wisdom in it but it is easily forgotten with languages which
do not differ between functions and routines.

Like most "religious laws," it's a rule that has a multitude of exceptions.
You can't do I/O without having side effects, something that the designers
of functional languages have learned, sometimes the hard way.

As far as I'm concerned, this is one of those things that Ruby did
right. By convention, methods that modify the object as a side effect
have names that end with an "!" (exclamation point.) Ruby usually supplies
two methods in such cases, one that modifies the object, and one that
creates a copy and then modifies the copy. Both methods return the
modified or new object.

It obeys the Principle of Least Surprise because if you forget the
"!", you get the slow version that does a copy and leaves the original
intact.

Nice as the solution is, it's impossible to import it into Python
cleanly without introducing catastrophic backward incomaptibilities.

John Roth
 
M

Michael Peuser

John Roth said:
"Michael Peuser" <[email protected]>

Like most "religious laws," it's a rule that has a multitude of exceptions.
You can't do I/O without having side effects, something that the designers
of functional languages have learned, sometimes the hard way.

Stream I/O can never be done right in functional languages. The only
solution is to use something like memory mapped files (which I do more and
more ...)

But look at this mess:
twolines=f.readline()+f.readline()
There are solutions in some languages with _routines_ and
_call-by-reference_ but it it is generally very clumpy.

On the other there is a growing popularity of generators and iterators which
by concept are *heretic*

Kindly
Michael P
 
S

Stephan Diehl

Stephan said:
Terry said:
Stephan Diehl said:
Once in a while, I get bitten by the fact, that mutating list methods such
as 'append' or 'extend' return None instead of the (mutated) list itself.

[...]


'Returners' could wrap no-return mutators with functions or
derived-class methods that do return the object, but I have never seen
anyone post a complete module that does so for, say, all list
mutators.

That's actually a nice idea. I might just do that.

o.k., the following short code would give you a list class, that returns
'self' when invoking any of the mutating methods.
The solution involves a metaclass and I wouldn't consider this code more as
an example than an industrial strength solution (for example, at the
moment, you couldn't overload any of these methods)
------------------------------------------------------------------
def wrapedmeth(classname,meth):
def _meth(self,*argl,**argd):
getattr(super(globals()[classname],self),meth)(*argl,**argd)
return self

return _meth

class ReturnMeta(type):
def __new__(cls,classname,bases,classdict):
wrap = classdict.get('return_self_super_methods')
if wrap is not None:
for method in wrap:
classdict[method] = wrapedmeth(classname,meth)
return super(ReturnMeta,cls).__new__(cls,classname,bases,classdict)

class mylist(list):
__metaclass__ = ReturnMeta
return_self_super_methods = ['append',
'extend',
'insert',
'remove',
'reverse',
'sort']


if __name__ == '__main__':
print 'l = [1,2]'
print 'mylist: print l.append(3)'
l = mylist([1,2])
print l.append(3)
print 'list: print l.append(3)'
l = [1,2]
print l.append(3)
 
J

Jacek Generowicz

Stephan Diehl said:
o.k., the following short code would give you a list class, that returns
'self' when invoking any of the mutating methods.

OK, here's a variaton on the theme, just wraps the object in-place in
a way which makes it return self. This way you can get the desired
effect on any object you get, without pre-meditation, so you can still
fit the call chain on one line (wasn't that the point?)
The solution involves a metaclass

Mine avoids them altogether.
and I wouldn't consider this code more as an example than an
industrial strength solution

Ditto.


class returner:

def __init__(self, object):
self.object = object

def __getattr__(self, name):
def proxy(*args, **kwds):
getattr(self.object, name)(*args, **kwds)
return self.object
return proxy

lst = [1,2,3]
print lst.append(4) # Here you get None
print returner(lst).append(5) # Here you get the modified list.
 
J

Jacek Generowicz

Jacek Generowicz said:
OK, here's a variaton on the theme, just wraps the object in-place in
a way which makes it return self.

I meant "makes any calls to methods return the original object in its
new state."
This way you can get the desired effect on any object you get,
without pre-meditation, so you can still fit the call chain on one
line (wasn't that the point?)

Of course, the chaining should work in Stephan's example too.
 
A

Alex Martelli

Terry Reedy wrote:
...
'Returners' could wrap no-return mutators with functions or
derived-class methods that do return the object, but I have never seen
anyone post a complete module that does so for, say, all list
mutators.

What about...:

def wrapReturning(func):
def returningWrapper(*args, **kwds):
func(*args, **kwds)
return args[0]
return returningWrapper


class metaReturner(type):
''' simplified metaReturner: deal with single inheritance only '''

def __new__(mcl, className, classBases, classDict):

# get the "real" base class, then wrap its mutators
for base in classBases:
if not isinstance(base, metaReturner):
for mutator in classDict['__mutators__']:
classDict[mutator] = wrapReturning(getattr(base,
mutator))
break

# delegate the rest to built-in 'type'
return type.__new__(mcl, className, classBases, classDict)

class Returner: __metaclass__ = metaReturner


# example usage

class returnerlist(Returner, list):
__mutators__ = 'sort reverse append extend insert'.split()

print returnerlist('hello').extend('ciao').sort().reverse()


Alex
 

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
474,102
Messages
2,570,646
Members
47,247
Latest member
GabrieleL2

Latest Threads

Top