Best way to disconnect from ldap?

J

John Gordon

I'm writing an application that interacts with ldap, and I'm looking
for advice on how to handle the connection. Specifically, how to
close the ldap connection when the application is done.

I wrote a class to wrap an LDAP connection, similar to this:

import ldap
import ConfigParser

class MyLDAPWrapper(object):

def __init__(self):

config = ConfigParser.SafeConfigParser()
config.read('sample.conf')

uri = config.get('LDAP', 'uri')
user = config.get('LDAP', 'user')
password = config.get('LDAP', 'password')

self.ldapClient = ldap.initialize(uri)
self.ldapClient.simple_bind_s(user, password)

My question is this: what is the best way to ensure the ldap connection
gets closed when it should? I could write an explicit close() method,
but that seems a bit messy; there would end up being lots of calls to
close() scattered around in my code (primarily inside exception handlers.)

Or I could write a __del__ method:

def __del__(self):
self.ldapClient.unbind_s()

This seems like a much cleaner solution, as I don't ever have to worry
about closing the connection; it gets done automatically.

I haven't ever used __del__ before. Are there any 'gotchas' I need to
worry about?

Thanks!
 
J

J. Cliff Dyer

Write a context manager.

Then you just do

with MyLDAPWrapper() as ldap
ldap.this()
ldap.that()

and when you leave the scope of the with statement, your ldap __exit__
method will get called regardless of how you left.

Cheers,
Cliff
 
C

Chris Rebert

I'm writing an application that interacts with ldap, and I'm looking
for advice on how to handle the connection.  Specifically, how to
close the ldap connection when the application is done.

I wrote a class to wrap an LDAP connection, similar to this:

   import ldap
   import ConfigParser

   class MyLDAPWrapper(object):

       def __init__(self):

           config = ConfigParser.SafeConfigParser()
           config.read('sample.conf')

           uri = config.get('LDAP', 'uri')
           user = config.get('LDAP', 'user')
           password = config.get('LDAP', 'password')

           self.ldapClient = ldap.initialize(uri)
           self.ldapClient.simple_bind_s(user, password)

My question is this: what is the best way to ensure the ldap connection
gets closed when it should?  I could write an explicit close() method,
but that seems a bit messy; there would end up being lots of calls to
close() scattered around in my code (primarily inside exception handlers.)

Or I could write a __del__ method:

       def __del__(self):
           self.ldapClient.unbind_s()

This seems like a much cleaner solution, as I don't ever have to worry
about closing the connection; it gets done automatically.

Yes, but not necessarily in a timely manner. Since its uses reference
counting, CPython /just so happens/ to finalize
non-cyclically-referenced objects promptly when they go out of scope,
but Python-the-language makes no such guarantee, and indeed some of
the other Python implementations explicitly disclaim that there may be
a significant delay before finalization is performed.
I haven't ever used __del__ before.  Are there any 'gotchas' I need to
worry about?

In addition to the aforementioned problem regarding portability to
other Python implementations, see also the Warning box under:
http://docs.python.org/reference/datamodel.html#object.__del__

I concur with J.'s context manager suggestion.

Cheers,
Chris
 
C

Chris Kaynor

Yes, but not necessarily in a timely manner. Since its uses reference
counting, CPython /just so happens/ to finalize
non-cyclically-referenced objects promptly when they go out of scope,
but Python-the-language makes no such guarantee, and indeed some of
the other Python implementations explicitly disclaim that there may be
a significant delay before finalization is performed.


In addition to the aforementioned problem regarding portability to
other Python implementations, see also the Warning box under:
http://docs.python.org/reference/datamodel.html#object.__del__

I concur with J.'s context manager suggestion.

Personally, I would combine both methods (and maybe throw in a close
option as well). The standard interface would be to use the with
context, however in cases where that is not possible, an explicit
close is useful, and just in-case that is forgotten or missed, the
__del__ is there as a final backup.

The main case that context managers fail is when you need to break the
creation and teardown into separate methods, such as when writing a
more complex context manager.

As Chris Rebert pointed out, there is no guarantee as to when the
__del__ method is called. CPython will generally call it immediately,
however if there are reference cycles it may never call it:

class O(object):
def __del__(self):
print 'del'

a = O()
b = O()
a.obj = b
b.obj = a
del a
del b # After this, all references should be gone. Netiher a nor b are
accessable anymore, right?
# Yet del was never printed. Maybe a full garbage collection will help?
import gc
gc.collect()
# Nope...



Also, if the object exists and an exception is thrown, the object may
be held onto for extended periods of time, or may never get cleaned
up. A quick example of this issue:
.... def __del__(self):
.... print 'del'
........ o = O()
.... raise RuntimeError()
....RuntimeError
Traceback (most recent call last):
File "<stdin-inspect>", line 1, in <module>
del
ValueError
Traceback (most recent call last):
File "<stdin-inspect>", line 1, in <module>
ValueError

In any case, it still makes a decent fall-back in case the user of
your code fails to properly clean-up. It will cover many of the common
cases, though you do need to be careful to never get into a reference
cycle if you have __del__ methods, or you get memory leaks.
 
T

Tycho Andersen

And more maddeningly, modules/objects used/called from within the
__del__ may have already gone out of scope, producing
head-scratching errors. I've been bitten by this enough times that
I just stopped using __del__ completely.

I've had similar experiences. In fact, in light of all this - why does
__del__ exist at all? Novice python users may (reasonably) assume it
behaves similarly to a C++ destructor (even though the docs warn
otherwise).

Given that you can't trust __del__, is there a legitimate use case for
it?

\t
 
C

Chris Rebert

I've had similar experiences. In fact, in light of all this - why does
__del__ exist at all? Novice python users may (reasonably) assume it
behaves similarly to a C++ destructor (even though the docs warn
otherwise).

Given that you can't trust __del__, is there a legitimate use case for
it?

Writing resource classes (like `file`) in C? Their __del__()s
typically involve little/less Python-level stuff and thus less
paranoia need be exercised.
There is somewhat of a perverse incentive in having such last-ditch
clean-up mechanisms though. "This code seems to work fine without
`with`, so why bother changing it?"

Cheers,
Chris
 
T

Tycho Andersen

Writing resource classes (like `file`) in C? Their __del__()s
typically involve little/less Python-level stuff and thus less
paranoia need be exercised.

Sure, but you still have no guarantee that __del__ will ever be
called, so it's a bad idea to rely on it to clean up anything.
There is somewhat of a perverse incentive in having such last-ditch
clean-up mechanisms though. "This code seems to work fine without
`with`, so why bother changing it?"

Yeah, I guess I can see doing something like:
__del__ = __exit__
but anything beyond that seems risky...

\t
 
S

Steven D'Aprano

I've had similar experiences. In fact, in light of all this - why does
__del__ exist at all? Novice python users may (reasonably) assume it
behaves similarly to a C++ destructor (even though the docs warn
otherwise).

What makes you think that novice Python users will be familiar with C++
destructors?

Be careful about assuming that idioms in <INSERT FAVOURITE LANGUAGE HERE>
will be shared by all Python programmers, novice or expert.

Given that you can't trust __del__, is there a legitimate use case for
it?

I've never found the need to write one.
 
T

Tim Chase

I've never found the need to write one.

I've found the need to write them...then been frustrated by
things falling out of namespace reach, and found that context
managers do a much more reliable/understandable job, saving what
little sanity I had left. ;-)

So I'd say that __del__ was really only useful (for some sick,
sick definition of "useful") in versions of Python before
context-managers were readily available.

-tkc
 
T

Terry Reedy

It is part of original or early Python and pretty well superceded by
cyclic gc (which does not work for object with __del__ *because* of the
unreliability), explicit close methods, and now context managers.
I've found the need to write them...then been frustrated by things
falling out of namespace reach, and found that context managers do a
much more reliable/understandable job, saving what little sanity I had
left. ;-)

Which is one reason they were added ;-).
So I'd say that __del__ was really only useful (for some sick, sick
definition of "useful") in versions of Python before context-managers
were readily available.

And before cyclic gc, which does a better job of ordering deletions.
 
T

Tycho Andersen

What makes you think that novice Python users will be familiar with C++
destructors?

I don't, really. It's just natural to assume that __del__ is the
"opposite" of __init__, when it's really not (i.e. every object is
__init__ed, but not every object is destructed and thus __del__'d).
Novice programmers may make this assumption (indeed, many experienced
programmers do as well).
Be careful about assuming that idioms in <INSERT FAVOURITE LANGUAGE HERE>
will be shared by all Python programmers, novice or expert.

Yeah, C++ was the first language which has destructors that came to
mind. It's certainly not my favorite ;-)

\t
 
J

John Gordon

In said:
I'm writing an application that interacts with ldap, and I'm looking
for advice on how to handle the connection. Specifically, how to
close the ldap connection when the application is done.
I wrote a class to wrap an LDAP connection, similar to this:
import ldap
import ConfigParser
class MyLDAPWrapper(object):
def __init__(self):
config = ConfigParser.SafeConfigParser()
config.read('sample.conf')

uri = config.get('LDAP', 'uri')
user = config.get('LDAP', 'user')
password = config.get('LDAP', 'password')
self.ldapClient = ldap.initialize(uri)
self.ldapClient.simple_bind_s(user, password)
My question is this: what is the best way to ensure the ldap connection
gets closed when it should? I could write an explicit close() method,
but that seems a bit messy; there would end up being lots of calls to
close() scattered around in my code (primarily inside exception handlers.)
Or I could write a __del__ method:
def __del__(self):
self.ldapClient.unbind_s()

Thanks everyone for your input. I learned a lot!

However, I just ran across this bit of documentation on python-ldap.org:

class ldap.LDAPObject
Instances of LDAPObject are returned by initialize() and open()
(deprecated). The connection is automatically unbound and closed
when the LDAP object is deleted.

So, given that, do I need to do anything at all?
 
M

Michael Ströder

John said:
class ldap.LDAPObject
Instances of LDAPObject are returned by initialize() and open()
(deprecated). The connection is automatically unbound and closed
when the LDAP object is deleted.

So, given that, do I need to do anything at all?

Hmm, maybe the author of this statement (have to check who) did not know about
the caveats with __del__() when this was written ages ago. IIRC first
python-ldap release was for Python 1.4 back in '98. See use of dealloc() in
Modules/LDAPObject.c.

So I'd recommend to use the modern with-statement to make sure
LDAPObject.unbind_s() is really called. Being old-fashioned I used
try-finally-blocks until now.

Ciao, Michael.
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top