getting special from type, not instance (was Re: [Python-Dev] copy confusion)

Alex Martelli aleax at aleax.it
Wed Jan 12 09:03:59 CET 2005


Since this bug isn't the cause of Fredrik's problem I'm changing the 
subject (and keep discussing the specific problem that Fredrik 
uncovered under the original subject).

On 2005 Jan 12, at 05:11, Guido van Rossum wrote:
    ...
>> I had exactly the same metabug in the pep 246 reference 
>> implementation,
>> Armin Rigo showed how to fix it in his only recent post.
>
> Don't recall seeing that, but if you or he can fix this without
> breaking other stuff, it's clear you should go ahead. (This worked in
> 2.2, FWIW; it broke in 2.3.)

Armin's fix was to change:

    conform = getattr(type(obj), '__conform__', None)

into:

    for basecls in type(obj).__mro__:
        if '__conform__' in basecls.__dict__:
            conform = basecls.__dict__['__conform__']
            break
    else:
        # not found

I have only cursorily examined the rest of the standard library, but it 
seems to me there may be a few other places where getattr is being used 
on a type for this purpose, such as pprint.py which has a couple of 
occurrences of
     r = getattr(typ, "__repr__", None)

Since this very same replacement is needed in more than one place for 
"get the following special attribute from the type of the object', it 
seems that a function to do it should be introduced in one place and 
used from where it's needed:

def get_from_first_dict(dicts, name, default=None):
    for adict in dicts:
        try:
            return adict[name]
        except KeyError:
            pass
    return default

to be called, e.g. in the above example with '__conform__', as:

conform = get_from_first_dict(
               (basecls.__dict__ for basecls in type(obj).__mro__),
               '__conform__'
           )

The needed function could of course be made less general, by giving 
more work to the function and less work to the caller, all the way down 
to:

def getspecial(obj, name, default=None):
    for basecls in type(obj).__mro__:
        try:
            return basecls.__dict__[name]
        except KeyError:
            pass
    return default

to be called, e.g. in the above example with '__conform__', as:

conform = getspecial(obj, '__conform__')

This has the advantage of not needing the genexp, so it's usable to 
implement the fix in 2.3.5 as well as in 2.4.1.  Moreover, it can 
specialcase old-style class instances to provide the backwards 
compatible behavior, if desired -- that doesn't matter (but doesn't 
hurt) to fix the bug in copy.py, because in that case old-style 
instances have been already specialcases previously, and might help to 
avoid breaking anything in other similar bugfixes, so that's what I 
would suggest:

def getspecial(obj, name, default=None):
    if isinstance(obj, types.InstanceType):
        return getattr(obj, name, default)
    for basecls in type(obj).__mro__:
        try:
            return basecls.__dict__[name]
        except KeyError:
            pass
    return default

The tradeoff between using type(obj) and obj.__class__ isn't 
crystal-clear to me, but since the latter might apparently be faked by 
some proxy to survive isinstance calls type(obj) appears to me to be 
right.


Where in the standard library to place this function is not clear to me 
either.  Since it's going into bugfix-only releases, I assume it 
shouldn't be "published".  Maybe having it as copy._getspecial (i.e. 
with a private name) is best, as long as it's OK to introduce some 
coupling by having (e.g.) pprint call copy._getspecial too.

Performance might be a problem, but the few bugfix locations where a 
getattr would be replaced by this getspecial don't seem to be hotspots, 
so maybe we don't need to worry about it for 2.3 and 2.4 (it might be 
nice to have this functionality "published" in 2.5, of course, and then 
it should probably be made fast).

Feedback welcome -- the actual patch will doubtless be tiny, but it 
would be nice to have it right the first time (as it needs to go into 
both the 2.3 and 2.4 bugfix branches and the 2.5 head).


Alex



More information about the Python-Dev mailing list