On Sat, Oct 27, 2018 at 8:02 PM Steven D'Aprano <st...@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 :)
_______________________________________________ Python-ideas mailing list Python-ideas@python.org https://mail.python.org/mailman/listinfo/python-ideas Code of Conduct: http://python.org/psf/codeofconduct/