Prob. Code Downloaded for Programming the Semantic Web (python code)

Chris Angelico rosuav at gmail.com
Fri Jul 25 23:25:15 EDT 2014


On Sat, Jul 26, 2014 at 10:06 AM, Bruce Whealton
<futurewavewebdevelopment at gmail.com> wrote:
> OK, Eclipse with PyDev doesn't like this first line, with the function:
> def add(self, (sub, pred, obj)):

As others have said, this is something that changed in Python 3. So
you have two parts to the problem: firstly, your code is bound to
Python 2 by a triviality, and secondly, Eclipse is complaining about
it.

One solution would be to teach Eclipse that this is legal, or for you
to just ignore its complaints. If your code works in Python 2.7, then
there's no big problem. You could try telling Eclipse that you're
using Python 2 (maybe by putting a shebang at the top of your script),
but that may not work; in any case, that's just an issue with the
editor.

But a better solution, IMO, would be to avoid that implicit tuple
unpacking. It's not a particularly clear feature, and I'm not sorry
it's gone from Py3. The simplest way to change it is to just move it
into the body:

def add(self, args):
    sub, pred, obj = args
    # rest of code as before

Preferably with a better name than 'args'. Alternatively, change the
places that call add() and have them provide four separate arguments,
in which case the signature would simply be:

def add(self, sub, pred, obj):

like you'd expect.

>         triples = list(self.triples((sub, pred, obj)))
>
> Are the two sets parentheses needed after self.triples?  That syntax is
> confusing to me.  It seems that it should be
> triples = list(self.triples(sub, pred, obj))

No, that's correct. The extra parens force that triple to be a single
tuple of three items, rather than three separate arguments. Here's a
simpler example:

>>> lst = []
>>> lst.append(1,2,3)
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    lst.append(1,2,3)
TypeError: append() takes exactly one argument (3 given)
>>> lst.append((1,2,3))
>>> addme = 4,5,6
>>> lst.append(addme)
>>> lst
[(1, 2, 3), (4, 5, 6)]

The list append method wants one argument, and appends that argument
to the list. Syntactically, the comma has multiple meanings; when I
assign 4,5,6 to a single name, it makes a tuple, but in a function
call, it separates args in the list. I don't see why the triples()
function should be given a single argument, though; all it does is
immediately unpack it. It'd be better to just remove the parens and
have separate args:

        triples = list(self.triples(sub, pred, obj))

   def triples(self, sub, pred, obj):

While I'm looking at the code, a few other comments. I don't know how
much of this is your code and how much came straight from the book,
but either way, don't take this as criticism, but just as suggestions
for ways to get more out of Python.

Inside remove(), you call a generator (triples() uses yield to return
multiple values), then construct a list, and then iterate exactly once
over that list. Much more efficient and clean to iterate directly over
what triples() returns, as in save(); that's what generators are good
for.

In triples(), the code is deeply nested and repetitive. I don't know
if there's a way to truly solve that, but I would be inclined to
flatten it out a bit; maybe check for just one presence, to pick your
index, and then merge some of the code that iterates over an index.
Not sure though.

(Also: It's conventional to use "is not None" rather than "!= None" to
test for singletons. It's possible for something to be equal to None
without actually being None.)

I would recommend moving to Python 3, if you can. Among other
benefits, the Py3 csv module allows you to open a text file rather
than opening a binary file and manually encoding/decoding all the
parts separately. Alternatively, if you don't need this to be saving
and loading another program's files, you could simply use a different
file format, which would remove the restrictions (and messes) of the
CSV structure.

Instead of explicitly putting "f.close()" at the end of your load and
save methods, check out the 'with' statement. It'll guarantee that the
file's closed even if you leave early, get an exception, or anything
like that. Also, I'd tend to use the .decode() and .encode() methods,
rather than the constructors. So here's how I'd write a Py2 load:

    def load(self, filename):
        with open(filename, "rb") as f:
            for sub, pred, obj in csv.reader(f):
                self.add((sub.decode("UTF-8"), pred.decode("UTF-8"),
obj.decode("UTF-8")))

(You might want to break that back out into three more lines, but this
parallels save(). If you break this one, you probably want to break
save() too.)

Hope that helps!

ChrisA



More information about the Python-list mailing list