]> granicus.if.org Git - python/commitdiff
Patch #1520294: Support for getset and member descriptors in types.py,
authorBarry Warsaw <barry@python.org>
Thu, 27 Jul 2006 23:43:15 +0000 (23:43 +0000)
committerBarry Warsaw <barry@python.org>
Thu, 27 Jul 2006 23:43:15 +0000 (23:43 +0000)
inspect.py, and pydoc.py.  Specifically, this allows for querying the type of
an object against these built-in C types and more importantly, for getting
their docstrings printed in the interactive interpreter's help() function.

This patch includes a new built-in module called _types which provides
definitions of getset and member descriptors for use by the types.py module.
These types are exposed as types.GetSetDescriptorType and
types.MemberDescriptorType.  Query functions are provided as
inspect.isgetsetdescriptor() and inspect.ismemberdescriptor().  The
implementations of these are robust enough to work with Python implementations
other than CPython, which may not have these fundamental types.

The patch also includes documentation and test suite updates.

I commit these changes now under these guiding principles:

1. Silence is assent.  The release manager has not said "no", and of the few
   people that cared enough to respond to the thread, the worst vote was "0".

2. It's easier to ask for forgiveness than permission.

3. It's so dang easy to revert stuff in svn, that you could view this as a
   forcing function. :)

Windows build patches will follow.

Doc/lib/libinspect.tex
Doc/lib/libtypes.tex
Lib/inspect.py
Lib/pydoc.py
Lib/test/test_inspect.py
Lib/types.py
Makefile.pre.in
Misc/NEWS
Modules/_typesmodule.c [new file with mode: 0644]
Modules/config.c.in

index 5cabb804b656c7e023211164ebc7917798439d4e..b61a6e0bf23d35379cdd64b31f89a820c45120d3 100644 (file)
@@ -180,13 +180,32 @@ Note:
   Return true if the object is a data descriptor.
 
   Data descriptors have both a __get__ and a __set__ attribute.  Examples are
-  properties (defined in Python) and getsets and members (defined in C).
-  Typically, data descriptors will also have __name__ and __doc__ attributes 
-  (properties, getsets, and members have both of these attributes), but this 
-  is not guaranteed.
+  properties (defined in Python), getsets, and members.  The latter two are
+  defined in C and there are more specific tests available for those types,
+  which is robust across Python implementations.  Typically, data descriptors
+  will also have __name__ and __doc__ attributes (properties, getsets, and
+  members have both of these attributes), but this is not guaranteed.
 \versionadded{2.3}
 \end{funcdesc}
 
+\begin{funcdesc}{isgetsetdescriptor}{object}
+  Return true if the object is a getset descriptor.
+
+  getsets are attributes defined in extension modules via \code{PyGetSetDef}
+  structures.  For Python implementations without such types, this method will
+  always return \code{False}.
+\versionadded{2.5}
+\end{funcdesc}
+
+\begin{funcdesc}{ismemberdescriptor}{object}
+  Return true if the object is a member descriptor.
+
+  Member descriptors are attributes defined in extension modules via
+  \code{PyMemberDef} structures.  For Python implementations without such
+  types, this method will always return \code{False}.
+\versionadded{2.5}
+\end{funcdesc}
+
 \subsection{Retrieving source code
             \label{inspect-source}}
 
index 19d2faa21b5748ee60f8807030fe6ab120407f7e..5e0c5a6ab523a1c7bc4c57788a82d34ce0d0011a 100644 (file)
@@ -180,6 +180,30 @@ The type of buffer objects created by the
 \function{buffer()}\bifuncindex{buffer} function.
 \end{datadesc}
 
+\begin{datadesc}{DictProxyType}
+The type of dict proxies, such as \code{TypeType.__dict__}.
+\end{datadesc}
+
+\begin{datadesc}{NotImplementedType}
+The type of \code{NotImplemented}
+\end{datadesc}
+
+\begin{datadesc}{GetSetDescriptorType}
+The type of objects defined in extension modules with \code{PyGetSetDef}, such
+as \code{FrameType.f_locals} or \code{array.array.typecode}.  This constant is
+not defined in implementations of Python that do not have such extension
+types, so for portable code use \code{hasattr(types, 'GetSetDescriptorType')}.
+\versionadded{2.5}
+\end{datadesc}
+
+\begin{datadesc}{MemberDescriptorType}
+The type of objects defined in extension modules with \code{PyMemberDef}, such
+as \code {datetime.timedelta.days}.  This constant is not defined in
+implementations of Python that do not have such extension types, so for
+portable code use \code{hasattr(types, 'MemberDescriptorType')}.
+\versionadded{2.5}
+\end{datadesc}
+
 \begin{datadesc}{StringTypes}
 A sequence containing \code{StringType} and \code{UnicodeType} used to
 facilitate easier checking for any string object.  Using this is more
index dc2fa0818063a6ecce0475dd302a5fad6be6ac5c..0cbf521cab765b18bc80d3a1d4a3b1c442a325c7 100644 (file)
@@ -89,6 +89,40 @@ def isdatadescriptor(object):
     is not guaranteed."""
     return (hasattr(object, "__set__") and hasattr(object, "__get__"))
 
+if hasattr(types, 'MemberDescriptorType'):
+    # CPython and equivalent
+    def ismemberdescriptor(object):
+        """Return true if the object is a member descriptor.
+
+        Member descriptors are specialized descriptors defined in extension
+        modules."""
+        return isinstance(object, types.MemberDescriptorType)
+else:
+    # Other implementations
+    def ismemberdescriptor(object):
+        """Return true if the object is a member descriptor.
+
+        Member descriptors are specialized descriptors defined in extension
+        modules."""
+        return False
+
+if hasattr(types, 'GetSetDescriptorType'):
+    # CPython and equivalent
+    def isgetsetdescriptor(object):
+        """Return true if the object is a getset descriptor.
+
+        getset descriptors are specialized descriptors defined in extension
+        modules."""
+        return isinstance(object, types.GetSetDescriptorType)
+else:
+    # Other implementations
+    def isgetsetdescriptor(object):
+        """Return true if the object is a getset descriptor.
+
+        getset descriptors are specialized descriptors defined in extension
+        modules."""
+        return False
+        
 def isfunction(object):
     """Return true if the object is a user-defined function.
 
index ff6e7ca21f84e0e118ca4fc5ac0c7ece08f29319..0fc624eb5838ebf7361cb1d73d925ef4f2e52ca4 100755 (executable)
@@ -318,6 +318,8 @@ class Doc:
         # identifies something in a way that pydoc itself has issues handling;
         # think 'super' and how it is a descriptor (which raises the exception
         # by lacking a __name__ attribute) and an instance.
+        if inspect.isgetsetdescriptor(object): return self.docdata(*args)
+        if inspect.ismemberdescriptor(object): return self.docdata(*args)
         try:
             if inspect.ismodule(object): return self.docmodule(*args)
             if inspect.isclass(object): return self.docclass(*args)
@@ -333,7 +335,7 @@ class Doc:
             name and ' ' + repr(name), type(object).__name__)
         raise TypeError, message
 
-    docmodule = docclass = docroutine = docother = fail
+    docmodule = docclass = docroutine = docother = docproperty = docdata = fail
 
     def getdocloc(self, object):
         """Return the location of module docs or None"""
@@ -915,6 +917,10 @@ class HTMLDoc(Doc):
         lhs = name and '<strong>%s</strong> = ' % name or ''
         return lhs + self.repr(object)
 
+    def docdata(self, object, name=None, mod=None, cl=None):
+        """Produce html documentation for a data descriptor."""
+        return self._docdescriptor(name, object, mod)
+
     def index(self, dir, shadowed=None):
         """Generate an HTML index for a directory of modules."""
         modpkgs = []
@@ -1268,6 +1274,10 @@ class TextDoc(Doc):
         """Produce text documentation for a property."""
         return self._docdescriptor(name, object, mod)
 
+    def docdata(self, object, name=None, mod=None, cl=None):
+        """Produce text documentation for a data descriptor."""
+        return self._docdescriptor(name, object, mod)
+
     def docother(self, object, name=None, mod=None, parent=None, maxlen=None, doc=None):
         """Produce text documentation for a data object."""
         repr = self.repr(object)
@@ -1397,6 +1407,14 @@ def describe(thing):
             return 'module ' + thing.__name__
     if inspect.isbuiltin(thing):
         return 'built-in function ' + thing.__name__
+    if inspect.isgetsetdescriptor(thing):
+        return 'getset descriptor %s.%s.%s' % (
+            thing.__objclass__.__module__, thing.__objclass__.__name__,
+            thing.__name__)
+    if inspect.ismemberdescriptor(thing):
+        return 'member descriptor %s.%s.%s' % (
+            thing.__objclass__.__module__, thing.__objclass__.__name__,
+            thing.__name__)
     if inspect.isclass(thing):
         return 'class ' + thing.__name__
     if inspect.isfunction(thing):
@@ -1453,6 +1471,8 @@ def doc(thing, title='Python Library Documentation: %s', forceload=0):
         if not (inspect.ismodule(object) or
                 inspect.isclass(object) or
                 inspect.isroutine(object) or
+                inspect.isgetsetdescriptor(object) or
+                inspect.ismemberdescriptor(object) or
                 isinstance(object, property)):
             # If the passed object is a piece of data or an instance,
             # document its available methods instead of its value.
index 76f2566e509f2681396a15f413db3ce76ddf8b76..928af07c41d694bc4b3e2c2e7d7ec7614bfcfe3a 100644 (file)
@@ -1,6 +1,8 @@
 import sys
+import types
 import unittest
 import inspect
+import datetime
 
 from test.test_support import TESTFN, run_unittest
 
@@ -40,10 +42,11 @@ class IsTestBase(unittest.TestCase):
             self.failIf(other(obj), 'not %s(%s)' % (other.__name__, exp))
 
 class TestPredicates(IsTestBase):
-    def test_eleven(self):
-        # Doc/lib/libinspect.tex claims there are 11 such functions
+    def test_thirteen(self):
+        # Doc/lib/libinspect.tex claims there are 13 such functions
         count = len(filter(lambda x:x.startswith('is'), dir(inspect)))
-        self.assertEqual(count, 11, "There are %d (not 11) is* functions" % count)
+        self.assertEqual(count, 13,
+                         "There are %d (not 12) is* functions" % count)
 
     def test_excluding_predicates(self):
         self.istest(inspect.isbuiltin, 'sys.exit')
@@ -58,6 +61,15 @@ class TestPredicates(IsTestBase):
         self.istest(inspect.istraceback, 'tb')
         self.istest(inspect.isdatadescriptor, '__builtin__.file.closed')
         self.istest(inspect.isdatadescriptor, '__builtin__.file.softspace')
+        if hasattr(types, 'GetSetDescriptorType'):
+            self.istest(inspect.isgetsetdescriptor,
+                        'type(tb.tb_frame).f_locals')
+        else:
+            self.failIf(inspect.isgetsetdescriptor(type(tb.tb_frame).f_locals))
+        if hasattr(types, 'MemberDescriptorType'):
+            self.istest(inspect.ismemberdescriptor, 'datetime.timedelta.days')
+        else:
+            self.failIf(inspect.ismemberdescriptor(datetime.timedelta.days))
 
     def test_isroutine(self):
         self.assert_(inspect.isroutine(mod.spam))
index 39812ac3228d17f26eea573bd9093eddef512c28..6c8c2b26f392459c57d7f49a8d9c4fffce2a2d7f 100644 (file)
@@ -86,4 +86,16 @@ EllipsisType = type(Ellipsis)
 DictProxyType = type(TypeType.__dict__)
 NotImplementedType = type(NotImplemented)
 
-del sys, _f, _g, _C, _x                  # Not for export
+# Extension types defined in a C helper module.  XXX There may be no
+# equivalent in implementations other than CPython, so it seems better to
+# leave them undefined then to set them to e.g. None.
+try:
+    import _types
+except ImportError:
+    pass
+else:
+    GetSetDescriptorType = type(_types.Helper.getter)
+    MemberDescriptorType = type(_types.Helper.member)
+    del _types
+
+del sys, _f, _g, _C, _x                           # Not for export
index f61758c5428c3ab3735a80ccc99f7c094272c0f5..2e66304a5d0e2305eb38194849a20c208527de76 100644 (file)
@@ -317,6 +317,7 @@ OBJECT_OBJS=        \
 ##########################################################################
 # objects that get linked into the Python library
 LIBRARY_OBJS=  \
+               Modules/_typesmodule.o \
                Modules/getbuildinfo.o \
                $(PARSER_OBJS) \
                $(OBJECT_OBJS) \
@@ -353,6 +354,7 @@ sharedmods: $(BUILDPYTHON)
 $(LIBRARY): $(LIBRARY_OBJS)
        -rm -f $@
        $(AR) cr $@ Modules/getbuildinfo.o
+       $(AR) cr $@ Modules/_typesmodule.o
        $(AR) cr $@ $(PARSER_OBJS)
        $(AR) cr $@ $(OBJECT_OBJS)
        $(AR) cr $@ $(PYTHON_OBJS)
@@ -485,7 +487,7 @@ $(AST_H): $(AST_ASDL) $(ASDLGEN_FILES)
 
 $(AST_C): $(AST_ASDL) $(ASDLGEN_FILES)
        $(ASDLGEN) -c $(AST_C_DIR) $(AST_ASDL)
-       
+
 Python/compile.o Python/symtable.o: $(GRAMMAR_H) $(AST_H)
 
 Python/getplatform.o: $(srcdir)/Python/getplatform.c
index cfb8cf8fc4e47badae8a389b9328539c02328bd9..199fe82cda4f96a055697fcdd84d81c7e068b0ab 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -91,6 +91,11 @@ Library
 - Bug #1517996: IDLE now longer shows the default Tk menu when a
   path browser, class browser or debugger is the frontmost window on MacOS X
 
+- Patch #1520294: Support for getset and member descriptors in types.py,
+  inspect.py, and pydoc.py.  Specifically, this allows for querying the type
+  of an object against these built-in types and more importantly, for getting
+  their docstrings printed in the interactive interpreter's help() function.
+
 
 Extension Modules
 -----------------
diff --git a/Modules/_typesmodule.c b/Modules/_typesmodule.c
new file mode 100644 (file)
index 0000000..5a6f2b9
--- /dev/null
@@ -0,0 +1,94 @@
+/* This extension module exposes some types that are only available at the
+ * C level.  It should not be used directly, but instead through the Python
+ * level types modules, which imports this.
+ */
+
+#include "Python.h"
+#include "structmember.h"
+
+typedef struct
+{
+    PyObject_HEAD
+    int member;
+} Helper;
+
+static PyMemberDef helper_members[] = {
+    { "member", T_INT,  offsetof(Helper, member), READONLY,
+      PyDoc_STR("A member descriptor")
+    },
+    { NULL }
+};
+
+static PyObject *
+helper_getter(Helper *self, void *unused) 
+{
+    Py_RETURN_NONE;
+}
+
+static PyGetSetDef helper_getset[] = {
+    { "getter", (getter)helper_getter, NULL,
+      PyDoc_STR("A getset descriptor"),
+    },
+    { NULL }
+};
+
+static PyTypeObject HelperType = {
+    PyObject_HEAD_INIT(NULL)
+    0,                                         /* ob_size */
+    "_types.Helper",                           /* tp_name */
+    sizeof(Helper),                             /* tp_basicsize */
+    0,                                         /* tp_itemsize */
+    0,                                         /* tp_dealloc */
+    0,                                         /* tp_print */
+    0,                                         /* tp_getattr */
+    0,                                         /* tp_setattr */
+    0,                                         /* tp_compare */
+    0,                                          /* tp_repr */
+    0,                                          /* tp_as_number */
+    0,                                         /* tp_as_sequence */
+    0,                                         /* tp_as_mapping */
+    0,                                          /* tp_hash */
+    0,                                         /* tp_call */
+    0,                                         /* tp_str */
+    0,                                          /* tp_getattro */
+    0,                                         /* tp_setattro */
+    0,                                         /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,                         /* tp_flags */
+    0,                                         /* tp_doc */
+    0,                                         /* tp_traverse */
+    0,                                         /* tp_clear */
+    0,                                          /* tp_richcompare */
+    0,                                         /* tp_weaklistoffset */
+    0,                                         /* tp_iter */
+    0,                                         /* tp_iternext */
+    0,                                         /* tp_methods */
+    helper_members,                             /* tp_members */
+    helper_getset,                              /* tp_getset */
+    0,                                         /* tp_base */
+    0,                                         /* tp_dict */
+    0,                                         /* tp_descr_get */
+    0,                                         /* tp_descr_set */
+    0,                                         /* tp_dictoffset */
+    0,                                         /* tp_init */
+    0,                                         /* tp_alloc */
+    0,                                          /* tp_new */
+    0,                                         /* tp_free */
+};
+
+PyMODINIT_FUNC
+init_types(void)
+{
+    PyObject *m;
+
+    m = Py_InitModule3("_types", NULL, "A types module helper");
+    if (!m)
+        return;
+
+    if (PyType_Ready(&HelperType) < 0)
+        return;
+
+    Py_INCREF(&HelperType);
+    PyModule_AddObject(m, "Helper", (PyObject *)&HelperType);
+}
+
+    
index f8119914af6b4ed9e81995d1edf1b193cdd3bcaf..8c25eea2e94a17bf0b3415942e2728c365fd4589 100644 (file)
@@ -28,6 +28,7 @@ extern void PyMarshal_Init(void);
 extern void initimp(void);
 extern void initgc(void);
 extern void init_ast(void);
+extern void init_types(void);
 
 struct _inittab _PyImport_Inittab[] = {
 
@@ -42,6 +43,9 @@ struct _inittab _PyImport_Inittab[] = {
        /* This lives in Python/Python-ast.c */
        {"_ast", init_ast},
 
+       /* This lives in Python/_types.c */
+       {"_types", init_types},
+
        /* These entries are here for sys.builtin_module_names */
        {"__main__", NULL},
        {"__builtin__", NULL},