Re: Discussions-To PEP-0604: Complementary syntax for Union[] and Optional[]
My current implementation <https://github.com/pprados/cpython/blob/add_INVERT_to_types/Objects/typeobje...> is not perfect. static PyObject * type_or(PyTypeObject* self, PyObject* param) { // Check param is a PyType or GenericAlias if ((param == NULL) || (strcmp("_GenericAlias", Py_TYPE(param)->tp_name) != 0 && (param != Py_None) && (PyObject_IsInstance(param, (PyObject *) &PyType_Type) != 1) ) ) { PyErr_SetString(PyExc_TypeError, "'type' expected"); return NULL; } // 1. Create a tuple with types PyObject *tuple=PyTuple_Pack(2,self, param); // 2. Create Union with tuple PyObject* typing=PyImport_ImportModule("typing"); PyObject* unionType = PyObject_GetAttrString(typing,"Union"); PyObject *newUnion=PyObject_GetItem(unionType, tuple); // 3. Clean memory Py_DECREF(typing); Py_DECREF(unionType); Py_DECREF(tuple); // 4. Return instance return newUnion; } PyObject * type_invert(PyTypeObject* self) { return type_or(self,Py_None); } static PyNumberMethods type_as_number = { .nb_or = (binaryfunc)type_or, // Add __or__ function .nb_invert = (unaryfunc)type_invert, // Add __invert__ function }; PyTypeObject PyType_Type = { ... &type_as_number, /* tp_as_number */ ... }; Things not good: - I compare the string of Py_TYPE(param)->tp_name with "_GenericAlias". - I import module typing in the function type_or(). What is the best practice ? - static import the module typing ? - lazy loading the module typing ? - Make the class _GenericAlias a first class type like tuple, list, etc to have a direct access of this type ? The current implementation <https://github.com/pprados/cpython/blob/update_isinstance/Objects/abstract.c...> of isinstance() and issubclass() use this function: static PyObject* union_to_tuple(PyObject* cls) { if (!strcmp(Py_TYPE(cls)->tp_name,"_GenericAlias")) { PyObject* origin = PyObject_GetAttrString(cls, "__origin__"); //printf("origin = %p\n",origin); if (origin == NULL) { printf("origin == NULL, return cls"); return cls; } if (PyObject_HasAttrString(origin, "_name")) { PyObject* name = PyObject_GetAttrString(origin, "_name"); if (name==NULL) { printf("_name = NULL\n"); Py_DECREF(origin); return cls; } const char* data = (char*)PyUnicode_1BYTE_DATA(name); if (data != NULL && !strcmp(data,"Union")) { PyObject* new_cls = PyObject_GetAttrString(cls, "__args__"); if (new_cls != NULL) { cls = new_cls; } } Py_DECREF(name); } Py_DECREF(origin); } return cls; } I have the same problem with a comparison of "_GenericAlias". I must do that, because it's impossible to import the module due to infinite recursion. I add just one line in PyObject_IsInstance(PyObject *inst, PyObject *cls) and PyObject_IsSubclass(PyObject *derived, PyObject *cls) to extract the inner tuple. cls = union_to_tuple(cls); I think it's be cool to add the class _GenericAlias to be compatible with PyType_FastSubclass() Any idea ? Philippe Prados Le mer. 25 sept. 2019 à 10:25, Philippe Prados <philippe.prados@gmail.com> a écrit :
Ok. I agree.
I will split the PEP. The next version I'm going to submit propose only int | str, int | None, and isinstance(x str | int). What do you think about that ? except int | str :
Philippe Prados
Le mar. 24 sept. 2019 à 22:42, Ivan Levkivskyi <levkivskyi@gmail.com> a écrit :
On Tue, 24 Sep 2019 at 13:13, Guido van Rossum <guido@python.org> wrote:
Since you mentioned my name, let me explain my current position on how to spell "optional".
- We've had a lot of feedback from mypy users who were confused by Optional[T] and thought this was needed for "optional" function arguments (i.e. those that have a default).
- Another situation where people have been confused Optional is in TypedDict, where we wanted to have a way to say "this key may be present or absent" (in the end we couldn't find a good keyword for this so we went with "total=<bool>" for the entire dict).
- It would probably have been less confusing if it had been called "nullable" (or "noneable"? Ruby's Sorbet type system calls it "nilable").
- In TypeScript you just write "int | None", and that looks readable enough to me -- and perfectly unambiguous.
So my preference would be to drop the "~" proposal (or aspirations for "?") and instead just write "int | None".
IIRC at last Friday's Bay Area typing meetup there was general agreement with this sentiment.
Yes, most people agreed that `~` is not a good idea, and we should just go ahead with `int | None`. Also most people agreed we may reconsider `int?` in a separate PEP later (possibly in conjunction with PEP 505). Btw, majority of people also agreed that allowing `isinstance(x, int | str)` is a good idea (mostly for type aliases that can be used both at runtime and in an annotation) assuming `isinstance(x, str | List[str])` is still an error.
If the PEP authors don't have problems with this, then I would propose to move towards discussing details of the proposed implementation.
-- Ivan
participants (1)
-
Philippe Prados