[Python-ideas] Fix documentation for __instancecheck__

Joy Diamond python.gem at gmail.com
Sat Oct 27 21:52:55 EDT 2018


On Sat, Oct 27, 2018 at 8:02 PM Steven D'Aprano <steve at pearwood.info> wrote:

> I don't think it is obvious that the behaviour is correct. Presumably
> Joy had a use-case for overriding isinstance(), and this optimization
> prevented it.
>
> Joy, can you comment on your use-case, and did you come up with a
> work-around?
>

I'm implementing traits (basically interfaces + members).

I am thus replacing the whole object/class/inheritance mechanism [Also
replacing __slots__ with actual members]

Thus, what I am doing is replacing all of:

__cmp__, __eq__, __ge__ ,__gt__, __le__, __lt__, __ne__ (you cannot compare
objects, unless they inherit from interface 'Comparable', or 'Hashable').
__delattr__ (thus my classes properly implement 'immutable', to make
immutable members).
__format__ (you cannot, currently, format objects)
__init__ (you cannot write a constructor; instead, it automatically creates
a hidden constructor, like C++.  You cannot write  a constructor, as
assignment to members often fails, since they are immutable & __delattr__ &
__setattr_ have been fixed to enforce that -- in debug mode)
__hash__ (You cannot hash an object, unless it inherits from interface
'Hashable', in which case you can).
__new__ (you cannot write a construtor -- see above under __init__)
__reduce__, and __reduce__ex__ (you, currently, cannot pickle or unpickle
objects -- this has to do with immutable members; and will be fixed if
pickling is required).
__setattr__ (thus my classes properly implement 'immutable', to make
immutable members)
__subclasshook__ (classes do not use inheritance, instead they are all
trait based)

And also, for metaclasses:

__call__ (calls an automatically created constructor to construct the
object, and work around the fact that some of its members are immutale).
__instancecheck__ (classes do not use inheritance, instead they are all
trait based)
__subclasscheck__ (classes do not use inheritance, instead they are all
trait based)
__subclasses__ (classes do not use inheritance, instead they are all trait
based).

My particular use case was:

I simply wanted to disable __instancecheck__, which I did, but my unit test
code, failed, when the call to __instancecheck__ was bypassed.


>
> - type(x) and x.__class__ don't necessarily agree; under what
>   circumstances are each used?
>
> (I've asked this before, and either never got a good answer, or I can't
> keep it straight in my head.)
>
> - what precisely does type(x) do?
>

1.  `type(x)` gets the true actual type of `x`.
2.  `x.__class__` gets the `.__class__` attribute for `x`, which by default
gets the actual true type of `x`, but may be replace by the user to do
other stuff.

In pythonic terms, `type(x)` does the following:

1.  It looks like a constructor to `type` -- Hence it calls type's
metaclass `__call__` function.
2.  To be precise it calls: `type.__class__.__call__(type, x)`
3.  This code can be seen here:
https://github.com/python/cpython/blob/master/Objects/typeobject.c#L914-L964
4.  Which basically calls `type.__new__` and if `type.__new__` returns a
type, calls `type.__init__` (i.e.: the usual way an object is constructed).
5.  `type.__new__` is special, it actually returns "the true actual type of
`x`".
6.  The code for `type.__new__` is here:
https://github.com/python/cpython/blob/master/Objects/typeobject.c#L2354-L2392
7.  As the code reads:

/* Special case: type(x) should return x->ob_type */
/* We only want type itself to accept the one-argument form (#27157)
Note: We don't call PyType_CheckExact as that also allows subclasses */
if (metatype == &PyType_Type) {
const Py_ssize_t nargs = PyTuple_GET_SIZE(args);
const Py_ssize_t nkwds = kwds == NULL ? 0 : PyDict_GET_SIZE(kwds);

if (nargs == 1 && nkwds == 0) {
PyObject *x = PyTuple_GET_ITEM(args, 0);
Py_INCREF(Py_TYPE(x));
return (PyObject *) Py_TYPE(x);
}

8.  So after going through `type.__call__`, `type.__new__` returns the true
actual type of `x` (why this is not optimized in `type.__call__` I've never
understood).
9.  Thus `type(x)` actually looks like:

A CONSTRUCTION of a type object; which is short-circuited to return the
previous actual type of `x`.

Thus, in many ways, I find `type(x)` to be usage, as I am sure many others
do, since it looks like a CONSTRUCtON of a type object, even though it is
not.

In pythonic terms, `x.__class__` does the following:

1.  Call `x.__getattribute__('__class__')`.  In general this will find
`Object.__dict__["__class__]"` (unless the user is playing games).
2.  The default implementation (if not replaced by the user) is here:
https://github.com/python/cpython/blob/master/Objects/typeobject.c#L4017-L4021
3.  Which calls `object_get_class` as defined here:
https://github.com/python/cpython/blob/master/Objects/typeobject.c#L3831-L3836

static PyObject *
object_get_class(PyObject *self, void *closure)
{
Py_INCREF(Py_TYPE(self));
return (PyObject *)(Py_TYPE(self));
}

4.  Thus the default implementation of `.__class__` is to return the true
type of `x`.

In terms of actual usage:

1.  It is cleaner, to use `.__class__`
2.  It is better to use `.__class__`, and the user can replace it, in rare
circumstances.
3.  *SOME* internal python code attempts to use `.__class__` (and if this
fails; then does a fall back to the true type of `x`)
4. *SOME* internal ptyhon code, bypasses `.__class__` and uses the true
type of `x` directly [as for example the implementation of `isinstance`
does]
5.  Use of `type(x)` does not looks as good [since it looks like a
construtor]; but is always accurate, and returns the true type of `x`, and
cannot be replaced by the user.

Hopefully this helps :)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20181027/e57a6b91/attachment-0001.html>


More information about the Python-ideas mailing list