On Wed, 10 Nov 2004, John P Speno wrote:
Hi, sorry for the delayed response.
> While using subprocess (aka popen5), I came across one potential gotcha. I've had
> exceptions ending like this:
>
> File "test.py", line 5, in test
> cmd = popen5.Popen(args, stdout=PIPE)
> File "popen5.py", line 577, in __init__
> data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
> OSError: [Errno 4] Interrupted system call
>
> (on Solaris 9)
>
> Would it make sense for subprocess to use a more robust read() function
> which can handle these cases, i.e. when the parent's read on the pipe
> to the child's stderr is interrupted by a system call, and returns EINTR?
> I imagine it could catch EINTR and EAGAIN and retry the failed read().
I assume you are using signals in your application? The os.read above is
not the only system call that can fail with EINTR. subprocess.py is full
of other system calls that can fail, and I suspect that many other Python
modules are as well.
I've made a patch (attached) to subprocess.py (and test_subprocess.py)
that should guard against EINTR, but I haven't committed it yet. It's
quite large.
Are Python modules supposed to handle EINTR? Why not let the C code handle
this? Or, perhaps the signal module should provide a sigaction function,
so that users can use SA_RESTART.
Index: subprocess.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/subprocess.py,v
retrieving revision 1.8
diff -u -r1.8 subprocess.py
--- subprocess.py 7 Nov 2004 14:30:34 -0000 1.8
+++ subprocess.py 17 Nov 2004 19:42:30 -0000
@@ -888,6 +888,50 @@
pass
+ def _read_no_intr(self, fd, buffersize):
+ """Like os.read, but retries on EINTR"""
+ while True:
+ try:
+ return os.read(fd, buffersize)
+ except OSError, e:
+ if e.errno == errno.EINTR:
+ continue
+ else:
+ raise
+
+
+ def _read_all(self, fd, buffersize):
+ """Like os.read, but retries on EINTR, and reads until EOF"""
+ all = ""
+ while True:
+ data = self._read_no_intr(fd, buffersize)
+ all += data
+ if data == "":
+ return all
+
+
+ def _write_no_intr(self, fd, s):
+ """Like os.write, but retries on EINTR"""
+ while True:
+ try:
+ return os.write(fd, s)
+ except OSError, e:
+ if e.errno == errno.EINTR:
+ continue
+ else:
+ raise
+
+ def _waitpid_no_intr(self, pid, options):
+ """Like os.waitpid, but retries on EINTR"""
+ while True:
+ try:
+ return os.waitpid(pid, options)
+ except OSError, e:
+ if e.errno == errno.EINTR:
+ continue
+ else:
+ raise
+
def _execute_child(self, args, executable, preexec_fn, close_fds,
cwd, env, universal_newlines,
startupinfo, creationflags, shell,
@@ -963,7 +1007,7 @@
exc_value,
tb)
exc_value.child_traceback = ''.join(exc_lines)
- os.write(errpipe_write, pickle.dumps(exc_value))
+ self._write_no_intr(errpipe_write, pickle.dumps(exc_value))
# This exitcode won't be reported to applications, so it
# really doesn't matter what we return.
@@ -979,7 +1023,7 @@
os.close(errwrite)
# Wait for exec to fail or succeed; possibly raising exception
- data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
+ data = self._read_all(errpipe_read, 1048576) # Exceptions limited to 1 MB
os.close(errpipe_read)
if data != "":
child_exception = pickle.loads(data)
@@ -1003,7 +1047,7 @@
attribute."""
if self.returncode == None:
try:
- pid, sts = os.waitpid(self.pid, os.WNOHANG)
+ pid, sts = self._waitpid_no_intr(self.pid, os.WNOHANG)
if pid == self.pid:
self._handle_exitstatus(sts)
except os.error:
@@ -1015,7 +1059,7 @@
"""Wait for child process to terminate. Returns returncode
attribute."""
if self.returncode == None:
- pid, sts = os.waitpid(self.pid, 0)
+ pid, sts = self._waitpid_no_intr(self.pid, 0)
self._handle_exitstatus(sts)
return self.returncode
@@ -1049,27 +1093,33 @@
stderr = []
while read_set or write_set:
- rlist, wlist, xlist = select.select(read_set, write_set, [])
+ try:
+ rlist, wlist, xlist = select.select(read_set, write_set, [])
+ except select.error, e:
+ if e[0] == errno.EINTR:
+ continue
+ else:
+ raise
if self.stdin in wlist:
# When select has indicated that the file is writable,
# we can write up to PIPE_BUF bytes without risk
# blocking. POSIX defines PIPE_BUF >= 512
- bytes_written = os.write(self.stdin.fileno(), input[:512])
+ bytes_written = self._write_no_intr(self.stdin.fileno(), input[:512])
input = input[bytes_written:]
if not input:
self.stdin.close()
write_set.remove(self.stdin)
if self.stdout in rlist:
- data = os.read(self.stdout.fileno(), 1024)
+ data = self._read_no_intr(self.stdout.fileno(), 1024)
if data == "":
self.stdout.close()
read_set.remove(self.stdout)
stdout.append(data)
if self.stderr in rlist:
- data = os.read(self.stderr.fileno(), 1024)
+ data = self._read_no_intr(self.stderr.fileno(), 1024)
if data == "":
self.stderr.close()
read_set.remove(self.stderr)
Index: test/test_subprocess.py
===================================================================
RCS file: /cvsroot/python/python/dist/src/Lib/test/test_subprocess.py,v
retrieving revision 1.14
diff -u -r1.14 test_subprocess.py
--- test/test_subprocess.py 12 Nov 2004 15:51:48 -0000 1.14
+++ test/test_subprocess.py 17 Nov 2004 19:42:30 -0000
@@ -7,6 +7,7 @@
import tempfile
import time
import re
+import errno
mswindows = (sys.platform == "win32")
@@ -35,6 +36,16 @@
fname = tempfile.mktemp()
return os.open(fname, os.O_RDWR|os.O_CREAT), fname
+ def read_no_intr(self, obj):
+ while True:
+ try:
+ return obj.read()
+ except IOError, e:
+ if e.errno == errno.EINTR:
+ continue
+ else:
+ raise
+
#
# Generic tests
#
@@ -123,7 +134,7 @@
p = subprocess.Popen([sys.executable, "-c",
'import sys; sys.stdout.write("orange")'],
stdout=subprocess.PIPE)
- self.assertEqual(p.stdout.read(), "orange")
+ self.assertEqual(self.read_no_intr(p.stdout), "orange")
def test_stdout_filedes(self):
# stdout is set to open file descriptor
@@ -151,7 +162,7 @@
p = subprocess.Popen([sys.executable, "-c",
'import sys; sys.stderr.write("strawberry")'],
stderr=subprocess.PIPE)
- self.assertEqual(remove_stderr_debug_decorations(p.stderr.read()),
+ self.assertEqual(remove_stderr_debug_decorations(self.read_no_intr(p.stderr)),
"strawberry")
def test_stderr_filedes(self):
@@ -186,7 +197,7 @@
'sys.stderr.write("orange")'],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
- output = p.stdout.read()
+ output = self.read_no_intr(p.stdout)
stripped = remove_stderr_debug_decorations(output)
self.assertEqual(stripped, "appleorange")
@@ -220,7 +231,7 @@
stdout=subprocess.PIPE,
cwd=tmpdir)
normcase = os.path.normcase
- self.assertEqual(normcase(p.stdout.read()), normcase(tmpdir))
+ self.assertEqual(normcase(self.read_no_intr(p.stdout)), normcase(tmpdir))
def test_env(self):
newenv = os.environ.copy()
@@ -230,7 +241,7 @@
'sys.stdout.write(os.getenv("FRUIT"))'],
stdout=subprocess.PIPE,
env=newenv)
- self.assertEqual(p.stdout.read(), "orange")
+ self.assertEqual(self.read_no_intr(p.stdout), "orange")
def test_communicate(self):
p = subprocess.Popen([sys.executable, "-c",
@@ -305,7 +316,8 @@
'sys.stdout.write("\\nline6");'],
stdout=subprocess.PIPE,
universal_newlines=1)
- stdout = p.stdout.read()
+
+ stdout = self.read_no_intr(p.stdout)
if hasattr(open, 'newlines'):
# Interpreter with universal newline support
self.assertEqual(stdout,
@@ -343,7 +355,7 @@
def test_no_leaking(self):
# Make sure we leak no resources
- max_handles = 1026 # too much for most UNIX systems
+ max_handles = 10 # too much for most UNIX systems
if mswindows:
max_handles = 65 # a full test is too slow on Windows
for i in range(max_handles):
@@ -424,7 +436,7 @@
'sys.stdout.write(os.getenv("FRUIT"))'],
stdout=subprocess.PIPE,
preexec_fn=lambda: os.putenv("FRUIT", "apple"))
- self.assertEqual(p.stdout.read(), "apple")
+ self.assertEqual(self.read_no_intr(p.stdout), "apple")
def test_args_string(self):
# args is a string
@@ -457,7 +469,7 @@
p = subprocess.Popen(["echo $FRUIT"], shell=1,
stdout=subprocess.PIPE,
env=newenv)
- self.assertEqual(p.stdout.read().strip(), "apple")
+ self.assertEqual(self.read_no_intr(p.stdout).strip(), "apple")
def test_shell_string(self):
# Run command through the shell (string)
@@ -466,7 +478,7 @@
p = subprocess.Popen("echo $FRUIT", shell=1,
stdout=subprocess.PIPE,
env=newenv)
- self.assertEqual(p.stdout.read().strip(), "apple")
+ self.assertEqual(self.read_no_intr(p.stdout).strip(), "apple")
def test_call_string(self):
# call() function with string argument on UNIX
@@ -525,7 +537,7 @@
p = subprocess.Popen(["set"], shell=1,
stdout=subprocess.PIPE,
env=newenv)
- self.assertNotEqual(p.stdout.read().find("physalis"), -1)
+ self.assertNotEqual(self.read_no_intr(p.stdout).find("physalis"), -1)
def test_shell_string(self):
# Run command through the shell (string)
@@ -534,7 +546,7 @@
p = subprocess.Popen("set", shell=1,
stdout=subprocess.PIPE,
env=newenv)
- self.assertNotEqual(p.stdout.read().find("physalis"), -1)
+ self.assertNotEqual(self.read_no_intr(p.stdout).find("physalis"), -1)
def test_call_string(self):
# call() function with string argument on Windows
/Peter Åstrand <astrand(a)lysator.liu.se>
Problem:
When the code contains list comprehensions (or for that matter any other
looping construct), the only way to get quickly through this code in pdb
is to set a temporary breakpoint on the line after the loop, which is
inconvenient..
There is a SF bug report #1248119 about this behavior.
Solution:
Should pdb's next command accept an optional numeric argument? It would
specify how many actual lines of code (not "line events")
should be skipped in the current frame before stopping,
i.e "next 5" would mean stop when
line>=line_where_next_N_happened+5
is reached.
This would allow to easily get over/out of loops in the debugger
What do you think?
Ilya
Hello,
This patch is about to celebrate its second birthday :-)
https://sourceforge.net/tracker/?func=detail&atid=305470&aid=790710&group_i…
It seems from the comments that the feature is nice but the
implementation was not OK.
I redid the implem according to the comments.
What should I do to get it reviewed further ? (perhaps just this :
posting to python-dev :-)
Best,
--
Grégoire
Hi,
I'm having 2 problems with the current cvs :
During compilation this warning occurs:
*** WARNING: renaming "dbm" since importing it failed: build/lib.linux-i686-2.5/
dbm.so: undefined symbol: dbm_firstkey
and the 'dbm' module is unavailable.
I'm running MandrakeLinux 2005 (10.2) gcc 3.4.3
(I'm also having this problem when compiling python 2.3.5 or 2.4.1)
furthermore the 'make install' of current cvs fails halfway trough
with the following errors:
.....
.....
Compiling /opt/python25/lib/python2.5/bsddb/test/test_associate.py ...
Sorry: TabError: ('inconsistent use of tabs and spaces in indentation',
('/opt/python25/lib/python2.5/bsddb/test/test_associate.py', 97, 23, '\t
os.mkdir(homeDir)\n'))
Compiling /opt/python25/lib/python2.5/bsddb/test/test_basics.py ...
Sorry: TabError: ('inconsistent use of tabs and spaces in indentation',
('/opt/python25/lib/python2.5/bsddb/test/test_basics.py', 400, 26, '\t if
get_raises_error:\n'))
Compiling /opt/python25/lib/python2.5/bsddb/test/test_compare.py ...
Sorry: TabError: ('inconsistent use of tabs and spaces in indentation',
('/opt/python25/lib/python2.5/bsddb/test/test_compare.py', 167, 5, '\t"""\n'))
.....
.....
Compiling /opt/python25/lib/python2.5/bsddb/test/test_recno.py ...
Sorry: TabError: ('inconsistent use of tabs and spaces in indentation',
('/opt/python25/lib/python2.5/bsddb/test/test_recno.py', 38, 46, '\tget_returns_none =
d.set_get_returns_none(2)\n'))
.....
.....
make: *** [libinstall] Error 1
$
And then it quits.
Fixing the tab indentation errors locally makes the problem go away.
Regards,
Irmen de Jong
[Christoph, please keep the python-dev list in the loop here, at least
until they get annoyed and decide we're off-topic. I think this is
crucial to the way they package and deliver Python]
Christoph Ludwig <cludwig(a)cdc.informatik.tu-darmstadt.de> writes:
> On Thu, Jul 07, 2005 at 06:27:46PM -0400, David Abrahams wrote:
>> "Martin v. Löwis" <martin(a)v.loewis.de> writes:
>>
>> > David Abrahams wrote:
>> >> I'm wondering if there has been a well-known recent change either in Python
>> >> or GCC that would account for these new reports. Any relevant
>> >> information would be appreciated.
> [...]
>> > Python is linked with g++ if configure thinks this is necessary
>>
>> Right. The question is, when should configure "think it's necessary?"
>
> Just to add to the confusion... I encountered the case that configure decided
> to use gcc for linking but it should have used g++. (It is Python
> PR #1189330 <http://tinyurl.com/dlheb>. This was on a x86 Linux system with
> g++ 3.4.2.)
>
> Background: The description of --with-cxx in the README of the
> Python 2.4.1 source distribution made me think that I need to
> configure my Python installation with
> --with-configure=/opt/gcc/gcc-3.4.2/bin/g++ if I plan to use C++
> extensions built with this compiler. (That was possibly a
> misunderstanding on my part,
AFAICT, yes.
> but Python should build with this option anyway.)
>
> configure set `LINKCC=$(PURIFY) $(CC)'. The result was that make failed when
> linking the python executable due to an unresolved reference to
> __gxx_personality_v0. I had to replace CC by CXX in the definition of LINKCC
> to finish the build of Python.
>
> When I looked into this problem I saw that configure in fact builds a test
> executable that included an object file compiled with g++. If the link step
> with gcc succeeds then LINKCC is set as above, otherwise CXX is
> used. Obviously, on my system this test was successful so configure decided
> to link with gcc. However, minimal changes to the source of the test program
> caused the link step to fail. It was not obvious to me at all why the latter
> source code should cause a dependency on the C++ runtime if the original
> code does not. My conclusion was that this test is fragile and should be
> skipped.
Sounds like it. I have never understood what the test was really
checking for since the moment it was first described to me, FWIW.
> If Python is built with --with-cxx then it should be linked with CXX
> as well.
U betcha.
> I gather from posts on the Boost mailing lists that you can import
> Boost.Python extensions even if Python was configured
> --without-cxx.
Yes, all the tests are passing that way.
> (On ELF based Linux/x86, at least.) That leaves me wondering
>
> * when is --with-cxx really necessary?
I think it's plausible that if you set sys.dlopenflags to share
symbols it *might* end up being necessary, but IIRC Ralf does use
sys.dlopenflags with a standard build of Python (no
--with-cxx)... right, Ralf?
> * what happens if I import extensions built with different g++ versions? Will
> there be a conflict between the different versions of libstdc++ those
> extensions depend on?
Not unless you set sys.dlopenflags to share symbols.
It's conceivable that they might conflict through their shared use of
libboost_python.so, but I think you have to accept that an extension
module and the libboost_python.so it is linked with have to be built
with compatible ABIs anyway. So in that case you may need to make
sure each group of extensions built with a given ABI use their own
libboost_python.so (or just link statically to libboost_python.a if
you don't need cross-module conversions).
HTH,
--
Dave Abrahams
Boost Consulting
www.boost-consulting.com
As promised, here is a full set of real-world comparative code
transformations using str.partition(). The patch isn't intended to be
applied; rather, it is here to test/demonstrate whether the new
construct offers benefits under a variety of use cases.
Overall, I found that partition() usefully encapsulated commonly
occurring low-level programming patterns. In most cases, it completely
eliminated the need for slicing and indices. In several cases, code was
simplified dramatically; in some, the simplification was minor; and in a
few cases, the complexity was about the same. No cases were made worse.
Most patterns using str.find() directly translated into an equivalent
using partition. The only awkwardness that arose was in cases where the
original code had a test like, "if s.find(pat) > 0". That case
translated to a double-term test, "if found and head". Also, some
pieces of code needed a tail that included the separator. That need was
met by inserting a line like "tail = sep + tail". And that solution led
to a minor naming discomfort for the middle term of the result tuple, it
was being used as both a Boolean found flag and as a string containing
the separator (hence conflicting the choice of names between "found" and
"sep").
In most cases, there was some increase in efficiency resulting fewer
total steps and tests, and from eliminating double searches. However,
in a few cases, the new code was less efficient because the fragment
only needed either the head or tail but not both as provided by
partition().
In every case, the code was clearer after the transformation. Also,
none of the transformations required str.partition() to be used in a
tricky way. In contrast, I found many contortions using str.find()
where I had to diagram every possible path to understand what the code
was trying to do or to assure myself that it worked.
The new methods excelled at reducing cyclomatic complexity by
eliminating conditional paths. The methods were especially helpful in
the context of multiple finds (i.e. split at the leftmost colon if
present within a group following the rightmost forward slash if
present). In several cases, the replaced code exactly matched the pure
python version of str.partition() -- this confirms that people are
routinely writing multi-step low-level in-line code that duplicates was
str.partition() does in a single step.
The more complex transformations were handled by first figuring out
exactly was the original code did under all possible cases and then
writing the partition() version to match that spec. The lesson was that
it is much easier to program from scratch using partition() than it is
to code using find(). The new method more naturally expresses a series
of parsing steps interleaved with other code.
With further ado, here are the comparative code fragments:
Index: CGIHTTPServer.py
===================================================================
*** 106,121 ****
def run_cgi(self):
"""Execute a CGI script."""
dir, rest = self.cgi_info
! i = rest.rfind('?')
! if i >= 0:
! rest, query = rest[:i], rest[i+1:]
! else:
! query = ''
! i = rest.find('/')
! if i >= 0:
! script, rest = rest[:i], rest[i:]
! else:
! script, rest = rest, ''
scriptname = dir + '/' + script
scriptfile = self.translate_path(scriptname)
if not os.path.exists(scriptfile):
--- 106,113 ----
def run_cgi(self):
"""Execute a CGI script."""
dir, rest = self.cgi_info
! rest, _, query = rest.rpartition('?')
! script, _, rest = rest.partition('/')
scriptname = dir + '/' + script
scriptfile = self.translate_path(scriptname)
if not os.path.exists(scriptfile):
Index: ConfigParser.py
===================================================================
*** 599,612 ****
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rest)
while rest:
! p = rest.find("%")
! if p < 0:
! accum.append(rest)
return
! if p > 0:
! accum.append(rest[:p])
! rest = rest[p:]
! # p is no longer used
c = rest[1:2]
if c == "%":
accum.append("%")
--- 599,611 ----
if depth > MAX_INTERPOLATION_DEPTH:
raise InterpolationDepthError(option, section, rest)
while rest:
! head, sep, rest = rest.partition("%")
! if not sep:
! accum.append(head)
return
! rest = sep + rest
! if found and head:
! accum.append(head)
c = rest[1:2]
if c == "%":
accum.append("%")
Index: cgi.py
===================================================================
*** 337,346 ****
key = plist.pop(0).lower()
pdict = {}
for p in plist:
! i = p.find('=')
! if i >= 0:
! name = p[:i].strip().lower()
! value = p[i+1:].strip()
if len(value) >= 2 and value[0] == value[-1] == '"':
value = value[1:-1]
value = value.replace('\\\\', '\\').replace('\\"',
'"')
--- 337,346 ----
key = plist.pop(0).lower()
pdict = {}
for p in plist:
! name, found, value = p.partition('=')
! if found:
! name = name.strip().lower()
! value = value.strip()
if len(value) >= 2 and value[0] == value[-1] == '"':
value = value[1:-1]
value = value.replace('\\\\', '\\').replace('\\"',
'"')
Index: cookielib.py
===================================================================
*** 610,618 ****
def request_port(request):
host = request.get_host()
! i = host.find(':')
! if i >= 0:
! port = host[i+1:]
try:
int(port)
except ValueError:
--- 610,617 ----
def request_port(request):
host = request.get_host()
! _, sep, port = host.partition(':')
! if sep:
try:
int(port)
except ValueError:
***************
*** 670,681 ****
'.local'
"""
! i = h.find(".")
! if i >= 0:
! #a = h[:i] # this line is only here to show what a is
! b = h[i+1:]
! i = b.find(".")
! if is_HDN(h) and (i >= 0 or b == "local"):
return "."+b
return h
--- 669,677 ----
'.local'
"""
! a, found, b = h.partition('.')
! if found:
! if is_HDN(h) and ('.' in b or b == "local"):
return "."+b
return h
***************
*** 1451,1463 ****
else:
path_specified = False
path = request_path(request)
! i = path.rfind("/")
! if i != -1:
if version == 0:
# Netscape spec parts company from reality here
! path = path[:i]
else:
! path = path[:i+1]
if len(path) == 0: path = "/"
# set default domain
--- 1447,1459 ----
else:
path_specified = False
path = request_path(request)
! head, sep, _ = path.rpartition('/')
! if sep:
if version == 0:
# Netscape spec parts company from reality here
! path = head
else:
! path = head + sep
if len(path) == 0: path = "/"
# set default domain
Index: gopherlib.py
===================================================================
*** 57,65 ****
"""Send a selector to a given host and port, return a file with
the reply."""
import socket
if not port:
! i = host.find(':')
! if i >= 0:
! host, port = host[:i], int(host[i+1:])
if not port:
port = DEF_PORT
elif type(port) == type(''):
--- 57,65 ----
"""Send a selector to a given host and port, return a file with
the reply."""
import socket
if not port:
! head, found, tail = host.partition(':')
! if found:
! host, port = head, int(tail)
if not port:
port = DEF_PORT
elif type(port) == type(''):
Index: httplib.py
===================================================================
*** 490,498 ****
while True:
if chunk_left is None:
line = self.fp.readline()
! i = line.find(';')
! if i >= 0:
! line = line[:i] # strip chunk-extensions
chunk_left = int(line, 16)
if chunk_left == 0:
break
--- 490,496 ----
while True:
if chunk_left is None:
line = self.fp.readline()
! line, _, _ = line.partition(';') # strip
chunk-extensions
chunk_left = int(line, 16)
if chunk_left == 0:
break
***************
*** 586,599 ****
def _set_hostport(self, host, port):
if port is None:
! i = host.rfind(':')
! j = host.rfind(']') # ipv6 addresses have [...]
! if i > j:
try:
! port = int(host[i+1:])
except ValueError:
! raise InvalidURL("nonnumeric port: '%s'" %
host[i+1:])
! host = host[:i]
else:
port = self.default_port
if host and host[0] == '[' and host[-1] == ']':
--- 584,595 ----
def _set_hostport(self, host, port):
if port is None:
! host, _, port = host.rpartition(':')
! if ']' not in port: # ipv6 addresses have [...]
try:
! port = int(port)
except ValueError:
! raise InvalidURL("nonnumeric port: '%s'" % port)
else:
port = self.default_port
if host and host[0] == '[' and host[-1] == ']':
***************
*** 976,998 ****
L = [self._buf]
self._buf = ''
while 1:
! i = L[-1].find("\n")
! if i >= 0:
break
s = self._read()
if s == '':
break
L.append(s)
! if i == -1:
# loop exited because there is no more data
return "".join(L)
else:
! all = "".join(L)
! # XXX could do enough bookkeeping not to do a 2nd search
! i = all.find("\n") + 1
! line = all[:i]
! self._buf = all[i:]
! return line
def readlines(self, sizehint=0):
total = 0
--- 972,990 ----
L = [self._buf]
self._buf = ''
while 1:
! head, found, tail = L[-1].partition('\n')
! if found:
break
s = self._read()
if s == '':
break
L.append(s)
! if not found:
# loop exited because there is no more data
return "".join(L)
else:
! self._buf = found + tail
! return "".join(L) + head
def readlines(self, sizehint=0):
total = 0
Index: ihooks.py
===================================================================
*** 426,438 ****
return None
def find_head_package(self, parent, name):
! if '.' in name:
! i = name.find('.')
! head = name[:i]
! tail = name[i+1:]
! else:
! head = name
! tail = ""
if parent:
qname = "%s.%s" % (parent.__name__, head)
else:
--- 426,432 ----
return None
def find_head_package(self, parent, name):
! head, _, tail = name.partition('.')
if parent:
qname = "%s.%s" % (parent.__name__, head)
else:
***************
*** 449,457 ****
def load_tail(self, q, tail):
m = q
while tail:
! i = tail.find('.')
! if i < 0: i = len(tail)
! head, tail = tail[:i], tail[i+1:]
mname = "%s.%s" % (m.__name__, head)
m = self.import_it(head, mname, m)
if not m:
--- 443,449 ----
def load_tail(self, q, tail):
m = q
while tail:
! head, _, tail = tail.partition('.')
mname = "%s.%s" % (m.__name__, head)
m = self.import_it(head, mname, m)
if not m:
Index: locale.py
===================================================================
*** 98,106 ****
seps = 0
spaces = ""
if s[-1] == ' ':
! sp = s.find(' ')
! spaces = s[sp:]
! s = s[:sp]
while s and grouping:
# if grouping is -1, we are done
if grouping[0]==CHAR_MAX:
--- 98,105 ----
seps = 0
spaces = ""
if s[-1] == ' ':
! spaces, sep, tail = s.partition(' ')
! s = sep + tail
while s and grouping:
# if grouping is -1, we are done
if grouping[0]==CHAR_MAX:
***************
*** 148,156 ****
# so, kill as much spaces as there where separators.
# Leading zeroes as fillers are not yet dealt with, as it is
# not clear how they should interact with grouping.
! sp = result.find(" ")
! if sp==-1:break
! result = result[:sp]+result[sp+1:]
seps -= 1
return result
--- 147,156 ----
# so, kill as much spaces as there where separators.
# Leading zeroes as fillers are not yet dealt with, as it is
# not clear how they should interact with grouping.
! head, found, tail = result.partition(' ')
! if not found:
! break
! result = head + tail
seps -= 1
return result
Index: mailcap.py
===================================================================
*** 105,117 ****
key, view, rest = fields[0], fields[1], fields[2:]
fields = {'view': view}
for field in rest:
! i = field.find('=')
! if i < 0:
! fkey = field
! fvalue = ""
! else:
! fkey = field[:i].strip()
! fvalue = field[i+1:].strip()
if fkey in fields:
# Ignore it
pass
--- 105,113 ----
key, view, rest = fields[0], fields[1], fields[2:]
fields = {'view': view}
for field in rest:
! fkey, found, fvalue = field.partition('=')
! fkey = fkey.strip()
! fvalue = fvalue.strip()
if fkey in fields:
# Ignore it
pass
Index: mhlib.py
===================================================================
*** 356,364 ****
if seq == 'all':
return all
# Test for X:Y before X-Y because 'seq:-n' matches both
! i = seq.find(':')
! if i >= 0:
! head, dir, tail = seq[:i], '', seq[i+1:]
if tail[:1] in '-+':
dir, tail = tail[:1], tail[1:]
if not isnumeric(tail):
--- 356,364 ----
if seq == 'all':
return all
# Test for X:Y before X-Y because 'seq:-n' matches both
! head, found, tail = seq.partition(':')
! if found:
! dir = ''
if tail[:1] in '-+':
dir, tail = tail[:1], tail[1:]
if not isnumeric(tail):
***************
*** 394,403 ****
i = bisect(all, anchor-1)
return all[i:i+count]
# Test for X-Y next
! i = seq.find('-')
! if i >= 0:
! begin = self._parseindex(seq[:i], all)
! end = self._parseindex(seq[i+1:], all)
i = bisect(all, begin-1)
j = bisect(all, end)
r = all[i:j]
--- 394,403 ----
i = bisect(all, anchor-1)
return all[i:i+count]
# Test for X-Y next
! head, found, tail = seq.find('-')
! if found:
! begin = self._parseindex(head, all)
! end = self._parseindex(tail, all)
i = bisect(all, begin-1)
j = bisect(all, end)
r = all[i:j]
Index: modulefinder.py
===================================================================
*** 140,148 ****
assert caller is parent
self.msgout(4, "determine_parent ->", parent)
return parent
! if '.' in pname:
! i = pname.rfind('.')
! pname = pname[:i]
parent = self.modules[pname]
assert parent.__name__ == pname
self.msgout(4, "determine_parent ->", parent)
--- 140,147 ----
assert caller is parent
self.msgout(4, "determine_parent ->", parent)
return parent
! pname, found, _ = pname.rpartition('.')
! if found:
parent = self.modules[pname]
assert parent.__name__ == pname
self.msgout(4, "determine_parent ->", parent)
***************
*** 152,164 ****
def find_head_package(self, parent, name):
self.msgin(4, "find_head_package", parent, name)
! if '.' in name:
! i = name.find('.')
! head = name[:i]
! tail = name[i+1:]
! else:
! head = name
! tail = ""
if parent:
qname = "%s.%s" % (parent.__name__, head)
else:
--- 151,157 ----
def find_head_package(self, parent, name):
self.msgin(4, "find_head_package", parent, name)
! head, _, tail = name.partition('.')
if parent:
qname = "%s.%s" % (parent.__name__, head)
else:
Index: pdb.py
===================================================================
*** 189,200 ****
# split into ';;' separated commands
# unless it's an alias command
if args[0] != 'alias':
! marker = line.find(';;')
! if marker >= 0:
! # queue up everything after marker
! next = line[marker+2:].lstrip()
self.cmdqueue.append(next)
! line = line[:marker].rstrip()
return line
# Command definitions, called by cmdloop()
--- 189,200 ----
# split into ';;' separated commands
# unless it's an alias command
if args[0] != 'alias':
! line, found, next = line.partition(';;')
! if found:
! # queue up everything after command separator
! next = next.lstrip()
self.cmdqueue.append(next)
! line = line.rstrip()
return line
# Command definitions, called by cmdloop()
***************
*** 217,232 ****
filename = None
lineno = None
cond = None
! comma = arg.find(',')
! if comma > 0:
# parse stuff after comma: "condition"
! cond = arg[comma+1:].lstrip()
! arg = arg[:comma].rstrip()
# parse stuff before comma: [filename:]lineno | function
- colon = arg.rfind(':')
funcname = None
! if colon >= 0:
! filename = arg[:colon].rstrip()
f = self.lookupmodule(filename)
if not f:
print '*** ', repr(filename),
--- 217,232 ----
filename = None
lineno = None
cond = None
! arg, found, cond = arg.partition(',')
! if found and arg:
# parse stuff after comma: "condition"
! arg = arg.rstrip()
! cond = cond.lstrip()
# parse stuff before comma: [filename:]lineno | function
funcname = None
! filename, found, arg = arg.rpartition(':')
! if found:
! filename = filename.rstrip()
f = self.lookupmodule(filename)
if not f:
print '*** ', repr(filename),
***************
*** 234,240 ****
return
else:
filename = f
! arg = arg[colon+1:].lstrip()
try:
lineno = int(arg)
except ValueError, msg:
--- 234,240 ----
return
else:
filename = f
! arg = arg.lstrip()
try:
lineno = int(arg)
except ValueError, msg:
***************
*** 437,445 ****
return
if ':' in arg:
# Make sure it works for "clear C:\foo\bar.py:12"
! i = arg.rfind(':')
! filename = arg[:i]
! arg = arg[i+1:]
try:
lineno = int(arg)
except:
--- 437,443 ----
return
if ':' in arg:
# Make sure it works for "clear C:\foo\bar.py:12"
! filename, _, arg = arg.rpartition(':')
try:
lineno = int(arg)
except:
Index: rfc822.py
===================================================================
*** 197,205 ****
You may override this method in order to use Message parsing
on tagged
data in RFC 2822-like formats with special header formats.
"""
! i = line.find(':')
! if i > 0:
! return line[:i].lower()
return None
def islast(self, line):
--- 197,205 ----
You may override this method in order to use Message parsing
on tagged
data in RFC 2822-like formats with special header formats.
"""
! head, found, tail = line.partition(':')
! if found and head:
! return head.lower()
return None
def islast(self, line):
***************
*** 340,348 ****
else:
if raw:
raw.append(', ')
! i = h.find(':')
! if i > 0:
! addr = h[i+1:]
raw.append(addr)
alladdrs = ''.join(raw)
a = AddressList(alladdrs)
--- 340,348 ----
else:
if raw:
raw.append(', ')
! head, found, tail = h.partition(':')
! if found and head:
! addr = tail
raw.append(addr)
alladdrs = ''.join(raw)
a = AddressList(alladdrs)
***************
*** 859,867 ****
data = stuff + data[1:]
if len(data) == 4:
s = data[3]
! i = s.find('+')
! if i > 0:
! data[3:] = [s[:i], s[i+1:]]
else:
data.append('') # Dummy tz
if len(data) < 5:
--- 859,867 ----
data = stuff + data[1:]
if len(data) == 4:
s = data[3]
! head, found, tail = s.partition('+')
! if found and head:
! data[3:] = [head, tail]
else:
data.append('') # Dummy tz
if len(data) < 5:
Index: robotparser.py
===================================================================
*** 104,112 ****
entry = Entry()
state = 0
# remove optional comment and strip line
! i = line.find('#')
! if i>=0:
! line = line[:i]
line = line.strip()
if not line:
continue
--- 104,110 ----
entry = Entry()
state = 0
# remove optional comment and strip line
! line, _, _ = line.partition('#')
line = line.strip()
if not line:
continue
Index: smtpd.py
===================================================================
*** 144,156 ****
self.push('500 Error: bad syntax')
return
method = None
! i = line.find(' ')
! if i < 0:
! command = line.upper()
arg = None
else:
! command = line[:i].upper()
! arg = line[i+1:].strip()
method = getattr(self, 'smtp_' + command, None)
if not method:
self.push('502 Error: command "%s" not implemented' %
command)
--- 144,155 ----
self.push('500 Error: bad syntax')
return
method = None
! command, found, arg = line.partition(' ')
! command = command.upper()
! if not found:
arg = None
else:
! arg = tail.strip()
method = getattr(self, 'smtp_' + command, None)
if not method:
self.push('502 Error: command "%s" not implemented' %
command)
***************
*** 495,514 ****
usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
# split into host/port pairs
! i = localspec.find(':')
! if i < 0:
usage(1, 'Bad local spec: %s' % localspec)
! options.localhost = localspec[:i]
try:
! options.localport = int(localspec[i+1:])
except ValueError:
usage(1, 'Bad local port: %s' % localspec)
! i = remotespec.find(':')
! if i < 0:
usage(1, 'Bad remote spec: %s' % remotespec)
! options.remotehost = remotespec[:i]
try:
! options.remoteport = int(remotespec[i+1:])
except ValueError:
usage(1, 'Bad remote port: %s' % remotespec)
return options
--- 494,513 ----
usage(1, 'Invalid arguments: %s' % COMMASPACE.join(args))
# split into host/port pairs
! head, found, tail = localspec.partition(':')
! if not found:
usage(1, 'Bad local spec: %s' % localspec)
! options.localhost = head
try:
! options.localport = int(tail)
except ValueError:
usage(1, 'Bad local port: %s' % localspec)
! head, found, tail = remotespec.partition(':')
! if not found:
usage(1, 'Bad remote spec: %s' % remotespec)
! options.remotehost = head
try:
! options.remoteport = int(tail)
except ValueError:
usage(1, 'Bad remote port: %s' % remotespec)
return options
Index: smtplib.py
===================================================================
*** 276,284 ****
"""
if not port and (host.find(':') == host.rfind(':')):
! i = host.rfind(':')
! if i >= 0:
! host, port = host[:i], host[i+1:]
try: port = int(port)
except ValueError:
raise socket.error, "nonnumeric port"
--- 276,283 ----
"""
if not port and (host.find(':') == host.rfind(':')):
! host, found, port = host.rpartition(':')
! if found:
try: port = int(port)
except ValueError:
raise socket.error, "nonnumeric port"
Index: urllib2.py
===================================================================
*** 289,301 ****
def add_handler(self, handler):
added = False
for meth in dir(handler):
! i = meth.find("_")
! protocol = meth[:i]
! condition = meth[i+1:]
!
if condition.startswith("error"):
! j = condition.find("_") + i + 1
! kind = meth[j+1:]
try:
kind = int(kind)
except ValueError:
--- 289,297 ----
def add_handler(self, handler):
added = False
for meth in dir(handler):
! protocol, _, condition = meth.partition('_')
if condition.startswith("error"):
! _, _, kind = condition.partition('_')
try:
kind = int(kind)
except ValueError:
Index: zipfile.py
===================================================================
*** 117,125 ****
self.orig_filename = filename # Original file name in
archive
# Terminate the file name at the first null byte. Null bytes in file
# names are used as tricks by viruses in archives.
! null_byte = filename.find(chr(0))
! if null_byte >= 0:
! filename = filename[0:null_byte]
# This is used to ensure paths in generated ZIP files always use
# forward slashes as the directory separator, as required by the
# ZIP format specification.
--- 117,123 ----
self.orig_filename = filename # Original file name in
archive
# Terminate the file name at the first null byte. Null bytes in file
# names are used as tricks by viruses in archives.
! filename, _, _ = filename.partition(chr(0))
# This is used to ensure paths in generated ZIP files always use
# forward slashes as the directory separator, as required by the
# ZIP format specification.
Most of the changes in PEP 3000 are tightening up of "There should be
one obvious way to do it.":
* Remove multiple forms of raising exceptions, leaving just "raise instance"
* Remove exec as statement, leaving the compatible tuple/call form.
* Remove <>, ``, leaving !=, repr
etc.
Other changes are to disallow things already considered poor style like:
* No assignment to True/False/None
* No input()
* No access to list comprehension variable
And there is also completely new stuff like static type checking.
While a lot of existing code will break on 3.0 it is still generally
possible to write code that will run on both 2.x and 3.0: use only the
"proper" forms above, do not assume the result of zip or range is a
list, use absolute imports (and avoid static types, of course). I
already write all my new code this way.
Is this "common subset" a happy coincidence or a design principle?
Not all proposed changes remove redundancy or add completely new
things. Some of them just change the way certain things must be done.
For example:
* Moving compile, id, intern to sys
* Replacing print with write/writeln
And possibly the biggest change:
* Reorganize the standard library to not be as shallow
I'm between +0 and -1 on these. I don't find them enough of an
improvement to break this "common subset" behavior. It's not quite the
same as strict backward compatibility and I find it worthwhile to try
to keep it.
Writing programs that run on both 2.x and 3 may require ugly
version-dependent tricks like:
try:
compile
except NameError:
from sys import compile
or perhaps
try:
import urllib
except ImportError:
from www import urllib
Should the "common subset" be a design principle of Python 3? Do
compile and id really have to be moved from __builtins__ to sys? Could
the rearrangement of the standard library be a bit less aggressive and
try to leave commonly used modules in place?
Oren
OK, once the cron job comes around and is run,
http://www.python.org/peps/pep-0348.html will not be a 404 but be the
latest version of the PEP.
Differences since my last public version is that it has
BaseException/Exception as the naming hierarchy, Warning inherits from
Exception, UserException is UserError, and StandardError inherits from
Exception. I also added better annotations on the tree for noticing
where inheritance changed and whether it become broader (and thus had
a new exception in its MRO) or more restrictive (and thus lost an
exception). Basically everything that Guido has brought up today
(08-03).
I may have made some mistakes changing over to BaseException/Exception
thanks to their names being so similar and tossing back in
StandardError so if people catch what seems like odd sentences that is
why (obviously let me know of the mistake).
-Brett
Fredrik Lundh wrote:
> the problem isn't the time it takes to unpack the return value, the
> problem is that it takes time to create the substrings that you don't
> need.
I'm actually starting to think that this may be a good use case for
views of strings i.e. rather than create 3 new strings, each "string" is
a view onto the string that was partitioned.
Most of the use cases I've seen, the partitioned bits are discarded
almost as soon as the original string, and often the original string
persists beyond the partitioned bits.
Tim Delaney