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

2005-01-13 Thread Armin Rigo
Hi Guido,

On Wed, Jan 12, 2005 at 09:59:13AM -0800, Guido van Rossum wrote:
 The descriptor for __getattr__ and other special attributes could
 claim to be a data descriptor

This has the nice effect that x[y] and x.__getitem__(y) would again be
equivalent, which looks good.

On the other hand, I fear that if there is a standard metamethod decorator
(named after Phillip's one), it will be misused.  Reading the documentation
will probably leave most programmers with the feeling it's something magical
to put on methods with __ in their names, and it won't be long before someone
notices that you can put this decorator everywhere in your classes (because it
won't break most programs) and gain a tiny performance improvement.

I guess that a name-based hack in type_new() to turn all __*__() methods into
data descriptors would be even more obscure?

Finally, I wonder if turning all methods whatsoever into data descriptors
(ouch! don't hit!) would be justifiable by the feeling that it's often bad
style and confusing to override a method in an instance (as opposed to
defining a method in an instance when there is none on the class).  
(Supporting this claim: Psyco does this simplifying hypothesis for performance
reasons and I didn't see yet a bug report for this.)

In all cases, I'm +1 on seeing built-in method objects (PyMethodDescr_Type)  
become data descriptors (classy descriptors? :-).


Armin
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


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

2005-01-12 Thread Alex Martelli
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
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] copy confusion

2005-01-11 Thread Alex Martelli
On 2005 Jan 11, at 23:20, Fredrik Lundh wrote:
back in Python 2.1 (and before), an object could define how copy.copy 
should
work simply by definining a __copy__ method.  here's the relevant 
portion:

...
try:
copierfunction = _copy_dispatch[type(x)]
except KeyError:
try:
copier = x.__copy__
except AttributeError:
raise error, \
  un(shallow)copyable object of type %s % type(x)
y = copier()
...
I recently discovered that this feature has disappeared in 2.3 and 
2.4.  in-
stead of looking for an instance method, the code now looks at the 
object's
type:
Hmmm, yes, we were discussing this general issue as part of the huge 
recent thread about pep 246.  In the new-style object model, special 
methods are supposed to be looked up on the type, not on the object; 
otherwise, having a class with special methods would be a problem -- 
are the methods meant to apply to the class object itself, or to its 
instances?

However, apparently, the code you quote is doing it wrong:
cls = type(x)
copier = _copy_dispatch.get(cls)
if copier:
return copier(x)
copier = getattr(cls, __copy__, None)
if copier:
return copier(x)
...because getattr is apparently the wrong way to go about it (e.g., it 
could get the '__copy__' from type(cls), which would be mistaken).  
Please see Armin Rigo's only recent post to Python-Dev for the way it 
should apparently be done instead -- assuming Armin is right (he 
generally is), there should be plenty of bugs in copy.py (ones that 
emerge when you're using custom metaclasses c -- are you doing that?).

Still, if you're using an instance of an old-style class, the lookup in 
_copy_dispatch should be on types.InstanceType -- is that what you're 
trying to copy, an instance of an old-style class?


(copy.deepcopy still seems to be able to use __deepcopy__ hooks, 
though)
It starts with a peek into a dispatch dictionary for the type of the 
object, too, just like shallow copy does.  What's the type of what 
you're trying to copy?


is this a bug, or a feature of the revised copy/pickle design?   (the 
code in
copy_reg/copy/pickle might be among the more convoluted pieces of 
python
coding that I ever seen...  and what's that smiley doing in copy.py?)

and if it's a bug, does the fact that nobody reported this for 2.3 
indicate that
I'm the only one using this feature?  is there a better way to control 
copying
that I should use instead?
When I can, I use __getstate__ and __setstate__, simply because they 
seem clear and flexible to be (usable for copying, deep copying, 
pickling).  But that doesn't mean __copy__ or __deepcopy__ should be 
left broken, of course.

Although there are features of design intent here, it does appear to me 
there may be bugs too (if the getattr on the type is wrong); in this 
case it's worrisome, not just that nobody else reported problems, but 
also that the unit tests didn't catch them...:-(

Alex
___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] copy confusion

2005-01-11 Thread Phillip J. Eby
At 11:56 PM 1/11/05 +0100, Alex Martelli wrote:
What both issues?  There's only one issue, it seems to me -- one of 
metaconfusion.
I was relying on Fredrik's report of a problem with the code; that is the 
other issue I referred to.

___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com


Re: [Python-Dev] copy confusion

2005-01-11 Thread Phillip J. Eby
At 02:58 PM 1/11/05 -0800, Guido van Rossum wrote:
[Phillip]
 Looks like a bug to me; it breaks the behavior of classic classes, since
 type(classicInstance) returns InstanceType.
I'm not so sure. I can't seem to break this for classic classes.
Sorry; I was extrapolating from what I thought was Fredrik's description of 
this behavior as a bug, and examining of the history of the code that he 
referenced.  I saw that the current version of that code had evolved 
directly from a version that was retrieving instance.__copy__; I therefore 
assumed that the loss-of-feature Fredrik was reporting was that.

That is, I thought that the problem he was experiencing was that classic 
classes no longer supported __copy__ because this code had changed.  I 
guess I should have looked at other lines of code besides the ones he 
pointed out; sorry about that.  :(


The only thing this intends to break, and then only for new-style
classes, is the ability to have __copy__ be an instance variable
(whose value should be a callable without arguments) -- it must be a
method on the class. This is the same thing that I've done for all
built-in operations (__add__, __getitem__ etc.).
Presumably, this is the actual feature loss that Fredrik's describing; i.e. 
lack of per-instance __copy__ on new-style classes.  That would make more 
sense.


 However, it also looks like it might have been introduced to fix the
 possibility that calling '__copy__' on a new-style class with a custom
 metaclass would result in ending up with an unbound method.  (Similar to
 the metaconfusion issue being recently discussed for PEP 246.)
Sorry, my head just exploded. :-(
The issue is that for special attributes (like __copy__, __conform__, etc.) 
that do not have a corresponding type slot, using getattr() is not 
sufficient to obtain slot-like behavior.  This is because 
'aType.__special__' may refer to a __special__ intended for *instances* of 
'aType', instead of the __special__ for aType.

As Armin points out, the only way to fully emulate type slot behavior for 
unslotted special attributes is to perform a search of the __dict__ of each 
type in the MRO of the type of the object for which you wish to obtain the 
special attribute.

So, in this specific case, __copy__ does not have a type slot, so it is 
impossible using getattr (or simple attribute access) to guarantee that you 
are retrieving the correct version of __copy__ in the presence of metaclasses.

This is what Alex and I dubbed metaconfusion in discussion of the same 
issue for PEP 246's __adapt__ and __conform__ methods; until they have 
tp_adapt and tp_conform slots, they can have this same problem.

Alex and I also just speculated that perhaps the stdlib should include a 
function that can do this, so that stdlib modules that define unslotted 
special attributes (such as __copy__) can ensure they work correctly in the 
presence of metaclasses.

___
Python-Dev mailing list
Python-Dev@python.org
http://mail.python.org/mailman/listinfo/python-dev
Unsubscribe: 
http://mail.python.org/mailman/options/python-dev/archive%40mail-archive.com