[Zope-dev] Re: "hasattr" geddon

Tim Peters tim.peters at gmail.com
Sat Jul 10 17:55:51 EDT 2004


[Shane Hathaway]
> Here is what often happens in Zope:
>
> def setFoo(self, value):
>  try:
>    self.foo = value
>  except:
>    LOG("Oops", ERROR, "Some error happened", error=sys.exc_info())
>  self.change_count += 1
>  self.get_indexes().update(self)
>
> Some piece of code has a legitimate reason to catch but log all exceptions.
> Some other piece of code updates indexes.  The database has now committed a
> partial transaction.

I need more words -- I don't see a commit() in that code, and I don't
know what "partial transaction" might mean (in ZODB you can commit a
transaction, or abort it -- there's no facility I know of for a
partial commit (or a partial abort, for that matter)).

Is a commit() hiding inside the update() call?  If so, and we're
assuming the bare "except" swallowed a ReadConflictError, then I have
to repeat:

>> The logic in Connection.commit() raises ReadConflictError upon an attempt to
>> commit an object that previously suffered a conflict during the transaction.
>> How does that differ from what you want?

> Even worse, this can happen within the indexes, making the indexes
> inconsistent with themselves.

Sorry, since I didn't understand the first part, I'm way lost here.

> Once a conflict error has occurred on any object, the rest of the transaction is
> on shaky grounds.

Which is why the current ZODB releases intend to prevent committing a
transaction if a conflict error occurred during the transaction.  It
shouldn't matter to this ZODB machinery if the application suppressed
the original conflict error(s), ZODB remembers that it raised
ReadConflictError regardless (via the Connection._conflicts
set-implemented-as-a-dict).

Hmm.  The lack of design documentation is frustrating.  Staring at the
code, it could be that  ZODB only prevents a transaction commit if the
transaction tries to *commit* at least one object that suffered a
conflict error,  I don't know ... if you have a specific case in mind,
please open a Collector report about it.

> That's unfortunate.  Now I'm inclined to never use hasattr again.  Can we
> petition for "hasattr2" then? :-)

Dieter already wrote it <wink>.  Zope/ZODB's use of Python's machinery
is sometimes so far from the goals Guido had in mind that Zope is
alone in wanting a Python change.  For example, I don't know of other
apps where calling hasattr() can have disastrous consequences.  If so,
there's slim constituency for adding another variant of hasattr to the
core language.  "Does a thing have attribute so-and-so?" is such a
mind-numbingly complex question in Zope that I would indeed never use
hasattr() in Zope/ZODB code to query general objects.  Parts of ZODB
3.3 have been rewritten to use this instead when querying general
objects:

_marker = object()

# The point of this is to avoid hiding exceptions (which the builtin
# hasattr() does).
def myhasattr(obj, attr):
    return getattr(obj, attr, _marker) is not _marker

That's certainly not needed for "ordinary" uses, like

     if hasattr(errno, "WSAEWOULDBLOCK"):    # Windows

Uses of hasattr() on base Python objects and modules is safe (as I
believe it is in *almost* all applications outside our part of the
Python world).


More information about the Zope-Dev mailing list