[Python-ideas] Enhancing vars()

Nick Coghlan ncoghlan at gmail.com
Wed Dec 14 03:01:00 EST 2016


On 14 December 2016 at 10:40, Steven D'Aprano <steve at pearwood.info> wrote:
> On Wed, Dec 14, 2016 at 12:12:39AM +0000, Emanuel Barry wrote:
>> > From Steven D'Aprano
>> > Sent: Tuesday, December 13, 2016 6:49 PM
>> > To: python-ideas at python.org
>> > Subject: Re: [Python-ideas] Enhancing vars()
>> >
>> > But if the object has __slots__, with or without a __dict__, then vars
>> > should return a proxy which direct reads and writes to the correct slot
>> > or dict.
>> >
>> > It might be helpful to have a slotsproxy object which provides a
>> > dict-like interface to an object with __slots__ but no __dict__, and
>> > build support for both __slots__ and a __dict__ on top of that.
>>
>> That might be a bit tricky, for example, it's possible that a class has a
>> `foo` slot *and* a `foo` instance attribute (by virtue of subclasses). What
>> would you do in that case?
>
> vars() shouldn't need to care about inheritance: it only cares about the
> object's own individual namespace, not attributes inherited from the
> class or superclasses. That's how vars() works now:
>
> py> class C:
> ...     cheese = 1
> ...
> py> obj = C()
> py> ns = vars(obj)
> py> 'cheese' in ns
> False
>
> The only difference here is that if the direct parent class has
> __slots__, the instance will use them instead of (or in addition to) a
> __dict__. We don't need to care about superclass __slots__, because they
> aren't inherited.

If folks genuinely want an attrproxy that provides a dict-like view
over an instance, that's essentially:

    from collections import MutableMapping
    class AttrProxy(MutableMapping):
        def __init__(self, obj):
            self._obj = obj
        def __len__(self):
            return len(dir(self._obj))
        def __iter__(self):
            for attr in dir(self._obj):
                yield attr
        def __contains__(self, attr):
            return hasattr(self._obj, attr)
        def __getitem__(self, attr):
            return getattr(self._obj, attr)
        def __setitem__(self, attr, value):
            setattr(self._obj, attr, value)
        def __delitem__(self, attr):
            delattr(self._obj, attr)

    >>> class C:
    ...     a = 1
    ...     b = 2
    ...     c = 3
    ...
    >>> ns = AttrProxy(C)
       >>> list(ns.keys())
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__',
'__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__',
'__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'a', 'b',
'c']
    >>> ns["d"] = 4
    >>> C.d
    4
    >>> C.c
    3
    >>> del ns["c"]
    >>> C.c
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: type object 'C' has no attribute 'c'
    >>> ns["a"] = 5
    >>> C.a
    5

Replacing the calls to `dir(self._obj)` with a method that filters out
dunder-methods and updating the other methods to reject them as keys
is also relatively straightforward if people want that behaviour.

Indirecting through dir(), hasattr(), getattr(), setattr() and
delattr() this way means you don't have to worry about the vagaries of
the descriptor protocol or inheritance or instance attributes vs class
attributes or anything else like that, while inheriting from
MutableMapping automatically gives you view-based keys(), values() and
items() implementations.

I wouldn't have any real objection to providing an API that behaves
like this (in simple cases its functionally equivalent to manipulating
__dict__ directly, while in more complex cases, the attrproxy approach
is likely to just work, whereas __dict__ manipulation may fail).
(Re-using vars() likely wouldn't be appropriate in that case though,
due to the change in the way inheritance is handled)

I *would* object to a new proxy type that duplicated descriptor logic
that's already programmatically accessible in other builtins, or only
selectively supported certain descriptors (like those created for
__slots__) while ignoring others. Pseudo-lookups like
inspect.getattr_static() exist to help out IDEs, debuggers and other
code analysers, rather than as something we want people to be doing as
part of their normal application execution.

Cheers,
Nick.

-- 
Nick Coghlan   |   ncoghlan at gmail.com   |   Brisbane, Australia


More information about the Python-ideas mailing list