summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/lib/libinspect.tex27
-rw-r--r--Doc/lib/libtypes.tex24
-rw-r--r--Lib/inspect.py34
-rwxr-xr-xLib/pydoc.py22
-rw-r--r--Lib/test/test_inspect.py18
-rw-r--r--Lib/types.py14
-rw-r--r--Makefile.pre.in4
-rw-r--r--Misc/NEWS5
-rw-r--r--Modules/_typesmodule.c94
-rw-r--r--Modules/config.c.in4
10 files changed, 236 insertions, 10 deletions
diff --git a/Doc/lib/libinspect.tex b/Doc/lib/libinspect.tex
index 5cabb80..b61a6e0 100644
--- a/Doc/lib/libinspect.tex
+++ b/Doc/lib/libinspect.tex
@@ -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}}
diff --git a/Doc/lib/libtypes.tex b/Doc/lib/libtypes.tex
index 19d2faa..5e0c5a6 100644
--- a/Doc/lib/libtypes.tex
+++ b/Doc/lib/libtypes.tex
@@ -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
diff --git a/Lib/inspect.py b/Lib/inspect.py
index dc2fa08..0cbf521 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -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.
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index ff6e7ca..0fc624e 100755
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -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.
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 76f2566..928af07 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -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))
diff --git a/Lib/types.py b/Lib/types.py
index 39812ac..6c8c2b2 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -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
diff --git a/Makefile.pre.in b/Makefile.pre.in
index f61758c..2e66304 100644
--- a/Makefile.pre.in
+++ b/Makefile.pre.in
@@ -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
diff --git a/Misc/NEWS b/Misc/NEWS
index cfb8cf8..199fe82 100644
--- 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
index 0000000..5a6f2b9
--- /dev/null
+++ b/Modules/_typesmodule.c
@@ -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);
+}
+
+
diff --git a/Modules/config.c.in b/Modules/config.c.in
index f811991..8c25eea 100644
--- a/Modules/config.c.in
+++ b/Modules/config.c.in
@@ -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},