Two of the new context managers in contextlib are now wrapped in pass-through 
factory functions.  The intent is to make the help() look cleaner.  This 
practice does have downsides however.  

The usual way to detect whether something is usable with a with-statement is to 
check the presence of the __enter__ and __exit__ methods.   Wrapping the CM in 
a pass-through function defeats this and other forms of introspection.

Also, the help() output itself is worse-off.  When you run help on a CM(), 
you're trying to find-out what happens on entry and what happens on exit.  If 
those methods had docstrings, the question would be answered directly.   The 
wrapper (intentionally) hides how it works.  

Since I teach intermediate and advanced python classes to experienced Python 
users, I've become more sensitive to problems this practice will create.  
Defeating introspection can make the help look nicer, but it isn't a clean 
coding practice and is something I hope doesn't catch on.

To the extent there is a problem with the output of help(), I think efforts 
should be directed at making help() better.   A lot of work needs to be done on 
that end -- for example abstract base classes also don't look great in help().

There are a couple of other minor issues as well.  One is that the wrapper 
function hides the class, making it harder to do type checks such as 
"isinstance(x, suppress)".  The other issue is that wrappers cause extra 
jumping around for people who are tracing code through a debugger or using a 
visualization tool such as pythontutor.   These aren't terribly important 
issues, but it does support the notion that usually the cleanest code is the 
best code.

In short, I recommend that efforts be directed at improving help() rather than 
limiting introspection by way of less clean coding practices.


Raymond


-------- current code for suppress() --------

class _SuppressExceptions:
    """Helper for suppress."""
    def __init__(self, *exceptions):
        self._exceptions = exceptions

    def __enter__(self):
        pass

    def __exit__(self, exctype, excinst, exctb):
        return exctype is not None and issubclass(exctype, self._exceptions)

# Use a wrapper function since we don't care about supporting inheritance
# and a function gives much cleaner output in help()
def suppress(*exceptions):
    """Context manager to suppress specified exceptions

    After the exception is suppressed, execution proceeds with the next
    statement following the with statement.

         with suppress(FileNotFoundError):
             os.remove(somefile)
         # Execution still resumes here if the file was already removed
    """
    return _SuppressExceptions(*exceptions)


-------- current help() output for suppress() --------

Help on function suppress in module contextlib:

suppress(*exceptions)
    Context manager to suppress specified exceptions
    
    After the exception is suppressed, execution proceeds with the next
    statement following the with statement.
    
         with suppress(FileNotFoundError):
             os.remove(somefile)
         # Execution still resumes here if the file was already removed

-------- current help() output for closing() with does not have a function 
wrapper --------

Help on class closing in module contextlib:

class closing(builtins.object)
 |  Context to automatically close something at the end of a block.
 |  
 |  Code like this:
 |  
 |      with closing(<module>.open(<arguments>)) as f:
 |          <block>
 |  
 |  is equivalent to this:
 |  
 |      f = <module>.open(<arguments>)
 |      try:
 |          <block>
 |      finally:
 |          f.close()
 |  
 |  Methods defined here:
 |  
 |  __enter__(self)
 |  
 |  __exit__(self, *exc_info)
 |  
 |  __init__(self, thing)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors defined here:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)




_______________________________________________
Python-Dev mailing list
Python-Dev@python.org
https://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
https://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com

Reply via email to