Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
C
cpython
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Kirill Smelkov
cpython
Commits
95fc51df
Commit
95fc51df
authored
Nov 20, 2010
by
Michael Foord
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Issue 9732: addition of getattr_static to the inspect module
parent
89197fe9
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
295 additions
and
2 deletions
+295
-2
Doc/glossary.rst
Doc/glossary.rst
+8
-0
Doc/library/inspect.rst
Doc/library/inspect.rst
+67
-0
Lib/inspect.py
Lib/inspect.py
+64
-0
Lib/test/test_inspect.py
Lib/test/test_inspect.py
+151
-1
Misc/NEWS
Misc/NEWS
+2
-0
Misc/python-wing4.wpr
Misc/python-wing4.wpr
+3
-1
No files found.
Doc/glossary.rst
View file @
95fc51df
...
@@ -435,6 +435,14 @@ Glossary
...
@@ -435,6 +435,14 @@ Glossary
its first :term:`argument` (which is usually called ``self``).
its first :term:`argument` (which is usually called ``self``).
See :term:`function` and :term:`nested scope`.
See :term:`function` and :term:`nested scope`.
method resolution order
Method Resolution Order is the order in which base classes are searched
for a member during lookup. See `The Python 2.3 Method Resolution Order
<http://www.python.org/download/releases/2.3/mro/>`_.
MRO
See :term:`method resolution order`.
mutable
mutable
Mutable objects can change their value but keep their :func:`id`. See
Mutable objects can change their value but keep their :func:`id`. See
also :term:`immutable`.
also :term:`immutable`.
...
...
Doc/library/inspect.rst
View file @
95fc51df
...
@@ -563,3 +563,70 @@ line.
...
@@ -563,3 +563,70 @@ line.
entry in the list represents the caller; the last entry represents where the
entry in the list represents the caller; the last entry represents where the
exception was raised.
exception was raised.
Fetching attributes statically
------------------------------
Both :func:`getattr` and :func:`hasattr` can trigger code execution when
fetching or checking for the existence of attributes. Descriptors, like
properties, will be invoked and :meth:`__getattr__` and :meth:`__getattribute__`
may be called.
For cases where you want passive introspection, like documentation tools, this
can be inconvenient. `getattr_static` has the same signature as :func:`getattr`
but avoids executing code when it fetches attributes.
.. function:: getattr_static(obj, attr, default=None)
Retrieve attributes without triggering dynamic lookup via the
descriptor protocol, `__getattr__` or `__getattribute__`.
Note: this function may not be able to retrieve all attributes
that getattr can fetch (like dynamically created attributes)
and may find attributes that getattr can't (like descriptors
that raise AttributeError). It can also return descriptors objects
instead of instance members.
There are several cases that will break `getattr_static` or be handled
incorrectly. These are pathological enough not to worry about (i.e. if you do
any of these then you deserve to have everything break anyway):
* :data:`~object.__dict__` existing (e.g. as a property) but returning the
wrong dictionary or even returning something other than a
dictionary
* classes created with :data:`~object.__slots__` that have the `__slots__`
member deleted from the class, or a fake `__slots__` attribute
attached to the instance, or any other monkeying with
`__slots__`
* objects that lie about their type by having `__class__` as a
descriptor (`getattr_static` traverses the :term:`MRO` of whatever type
`obj.__class__` returns instead of the real type)
* type objects that lie about their :term:`MRO`
Descriptors are not resolved (for example slot descriptors or
getset descriptors on objects implemented in C). The descriptor
is returned instead of the underlying attribute.
You can handle these with code like the following. Note that
for arbitrary getset descriptors invoking these may trigger
code execution::
# example code for resolving the builtin descriptor types
class _foo(object):
__slots__ = ['foo']
slot_descriptor = type(_foo.foo)
getset_descriptor = type(type(open(__file__)).name)
wrapper_descriptor = type(str.__dict__['__add__'])
descriptor_types = (slot_descriptor, getset_descriptor, wrapper_descriptor)
result = getattr_static(some_object, 'foo')
if type(result) in descriptor_types:
try:
result = result.__get__()
except AttributeError:
# descriptors can raise AttributeError to
# indicate there is no underlying value
# in which case the descriptor itself will
# have to do
pass
Lib/inspect.py
View file @
95fc51df
...
@@ -1054,3 +1054,67 @@ def stack(context=1):
...
@@ -1054,3 +1054,67 @@ def stack(context=1):
def
trace
(
context
=
1
):
def
trace
(
context
=
1
):
"""Return a list of records for the stack below the current exception."""
"""Return a list of records for the stack below the current exception."""
return
getinnerframes
(
sys
.
exc_info
()[
2
],
context
)
return
getinnerframes
(
sys
.
exc_info
()[
2
],
context
)
# ------------------------------------------------ static version of getattr
_sentinel
=
object
()
def
_check_instance
(
obj
,
attr
):
instance_dict
=
{}
try
:
instance_dict
=
object
.
__getattribute__
(
obj
,
"__dict__"
)
except
AttributeError
:
pass
return
instance_dict
.
get
(
attr
,
_sentinel
)
def
_check_class
(
klass
,
attr
):
for
entry
in
getmro
(
klass
):
try
:
return
entry
.
__dict__
[
attr
]
except
KeyError
:
pass
return
_sentinel
def
getattr_static
(
obj
,
attr
,
default
=
_sentinel
):
"""Retrieve attributes without triggering dynamic lookup via the
descriptor protocol, __getattr__ or __getattribute__.
Note: this function may not be able to retrieve all attributes
that getattr can fetch (like dynamically created attributes)
and may find attributes that getattr can't (like descriptors
that raise AttributeError). It can also return descriptor objects
instead of instance members in some cases. See the
documentation for details.
"""
instance_result
=
_sentinel
if
not
isinstance
(
obj
,
type
):
instance_result
=
_check_instance
(
obj
,
attr
)
klass
=
obj
.
__class__
else
:
klass
=
obj
klass_result
=
_check_class
(
klass
,
attr
)
if
instance_result
is
not
_sentinel
and
klass_result
is
not
_sentinel
:
if
(
_check_class
(
type
(
klass_result
),
'__get__'
)
is
not
_sentinel
and
_check_class
(
type
(
klass_result
),
'__set__'
)
is
not
_sentinel
):
return
klass_result
if
instance_result
is
not
_sentinel
:
return
instance_result
if
klass_result
is
not
_sentinel
:
return
klass_result
if
obj
is
klass
:
# for types we check the metaclass too
for
entry
in
getmro
(
type
(
klass
)):
try
:
return
entry
.
__dict__
[
attr
]
except
KeyError
:
pass
if
default
is
not
_sentinel
:
return
default
raise
AttributeError
(
attr
)
Lib/test/test_inspect.py
View file @
95fc51df
...
@@ -706,12 +706,162 @@ class TestGetcallargsUnboundMethods(TestGetcallargsMethods):
...
@@ -706,12 +706,162 @@ class TestGetcallargsUnboundMethods(TestGetcallargsMethods):
locs
=
dict
(
locs
or
{},
inst
=
self
.
inst
)
locs
=
dict
(
locs
or
{},
inst
=
self
.
inst
)
return
(
func
,
'inst,'
+
call_params_string
,
locs
)
return
(
func
,
'inst,'
+
call_params_string
,
locs
)
class
TestGetattrStatic
(
unittest
.
TestCase
):
def
test_basic
(
self
):
class
Thing
(
object
):
x
=
object
()
thing
=
Thing
()
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
Thing
.
x
)
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
,
None
),
Thing
.
x
)
with
self
.
assertRaises
(
AttributeError
):
inspect
.
getattr_static
(
thing
,
'y'
)
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'y'
,
3
),
3
)
def
test_inherited
(
self
):
class
Thing
(
object
):
x
=
object
()
class
OtherThing
(
Thing
):
pass
something
=
OtherThing
()
self
.
assertEqual
(
inspect
.
getattr_static
(
something
,
'x'
),
Thing
.
x
)
def
test_instance_attr
(
self
):
class
Thing
(
object
):
x
=
2
def
__init__
(
self
,
x
):
self
.
x
=
x
thing
=
Thing
(
3
)
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
3
)
del
thing
.
x
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
2
)
def
test_property
(
self
):
class
Thing
(
object
):
@
property
def
x
(
self
):
raise
AttributeError
(
"I'm pretending not to exist"
)
thing
=
Thing
()
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
Thing
.
x
)
def
test_descriptor
(
self
):
class
descriptor
(
object
):
def
__get__
(
*
_
):
raise
AttributeError
(
"I'm pretending not to exist"
)
desc
=
descriptor
()
class
Thing
(
object
):
x
=
desc
thing
=
Thing
()
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
desc
)
def
test_classAttribute
(
self
):
class
Thing
(
object
):
x
=
object
()
self
.
assertEqual
(
inspect
.
getattr_static
(
Thing
,
'x'
),
Thing
.
x
)
def
test_inherited_classattribute
(
self
):
class
Thing
(
object
):
x
=
object
()
class
OtherThing
(
Thing
):
pass
self
.
assertEqual
(
inspect
.
getattr_static
(
OtherThing
,
'x'
),
Thing
.
x
)
def
test_slots
(
self
):
class
Thing
(
object
):
y
=
'bar'
__slots__
=
[
'x'
]
def
__init__
(
self
):
self
.
x
=
'foo'
thing
=
Thing
()
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
Thing
.
x
)
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'y'
),
'bar'
)
del
thing
.
x
self
.
assertEqual
(
inspect
.
getattr_static
(
thing
,
'x'
),
Thing
.
x
)
def
test_metaclass
(
self
):
class
meta
(
type
):
attr
=
'foo'
class
Thing
(
object
,
metaclass
=
meta
):
pass
self
.
assertEqual
(
inspect
.
getattr_static
(
Thing
,
'attr'
),
'foo'
)
class
sub
(
meta
):
pass
class
OtherThing
(
object
,
metaclass
=
sub
):
x
=
3
self
.
assertEqual
(
inspect
.
getattr_static
(
OtherThing
,
'attr'
),
'foo'
)
class
OtherOtherThing
(
OtherThing
):
pass
# this test is odd, but it was added as it exposed a bug
self
.
assertEqual
(
inspect
.
getattr_static
(
OtherOtherThing
,
'x'
),
3
)
def
test_no_dict_no_slots
(
self
):
self
.
assertEqual
(
inspect
.
getattr_static
(
1
,
'foo'
,
None
),
None
)
self
.
assertNotEqual
(
inspect
.
getattr_static
(
'foo'
,
'lower'
),
None
)
def
test_no_dict_no_slots_instance_member
(
self
):
# returns descriptor
with
open
(
__file__
)
as
handle
:
self
.
assertEqual
(
inspect
.
getattr_static
(
handle
,
'name'
),
type
(
handle
).
name
)
def
test_inherited_slots
(
self
):
# returns descriptor
class
Thing
(
object
):
__slots__
=
[
'x'
]
def
__init__
(
self
):
self
.
x
=
'foo'
class
OtherThing
(
Thing
):
pass
# it would be nice if this worked...
# we get the descriptor instead of the instance attribute
self
.
assertEqual
(
inspect
.
getattr_static
(
OtherThing
(),
'x'
),
Thing
.
x
)
def
test_descriptor
(
self
):
class
descriptor
(
object
):
def
__get__
(
self
,
instance
,
owner
):
return
3
class
Foo
(
object
):
d
=
descriptor
()
foo
=
Foo
()
# for a non data descriptor we return the instance attribute
foo
.
__dict__
[
'd'
]
=
1
self
.
assertEqual
(
inspect
.
getattr_static
(
foo
,
'd'
),
1
)
# if the descriptor is a data-desciptor we should return the
# descriptor
descriptor
.
__set__
=
lambda
s
,
i
,
v
:
None
self
.
assertEqual
(
inspect
.
getattr_static
(
foo
,
'd'
),
Foo
.
__dict__
[
'd'
])
def
test_metaclass_with_descriptor
(
self
):
class
descriptor
(
object
):
def
__get__
(
self
,
instance
,
owner
):
return
3
class
meta
(
type
):
d
=
descriptor
()
class
Thing
(
object
,
metaclass
=
meta
):
pass
self
.
assertEqual
(
inspect
.
getattr_static
(
Thing
,
'd'
),
meta
.
__dict__
[
'd'
])
def
test_main
():
def
test_main
():
run_unittest
(
run_unittest
(
TestDecorators
,
TestRetrievingSourceCode
,
TestOneliners
,
TestBuggyCases
,
TestDecorators
,
TestRetrievingSourceCode
,
TestOneliners
,
TestBuggyCases
,
TestInterpreterStack
,
TestClassesAndFunctions
,
TestPredicates
,
TestInterpreterStack
,
TestClassesAndFunctions
,
TestPredicates
,
TestGetcallargsFunctions
,
TestGetcallargsMethods
,
TestGetcallargsFunctions
,
TestGetcallargsMethods
,
TestGetcallargsUnboundMethods
)
TestGetcallargsUnboundMethods
,
TestGetattrStatic
)
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
test_main
()
test_main
()
Misc/NEWS
View file @
95fc51df
...
@@ -25,6 +25,8 @@ Library
...
@@ -25,6 +25,8 @@ Library
complex zeros on systems where the log1p function fails to respect
complex zeros on systems where the log1p function fails to respect
the sign of zero. This fixes a test failure on AIX.
the sign of zero. This fixes a test failure on AIX.
- Issue #9732: Addition of getattr_static to the inspect module.
- Issue #10446: Module documentation generated by pydoc now links to a
- Issue #10446: Module documentation generated by pydoc now links to a
version-specific online reference manual.
version-specific online reference manual.
...
...
Misc/python-wing4.wpr
View file @
95fc51df
...
@@ -5,7 +5,9 @@
...
@@ -5,7 +5,9 @@
##################################################################
##################################################################
[project attributes]
[project attributes]
proj.directory-list = [{'dirloc': loc('..'),
proj.directory-list = [{'dirloc': loc('..'),
'excludes': [u'Lib/__pycache__'],
'excludes': [u'Lib/__pycache__',
u'Doc/build',
u'build'],
'filter': '*',
'filter': '*',
'include_hidden': False,
'include_hidden': False,
'recursive': True,
'recursive': True,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment