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
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
a écrit : On Tue, 24 Sep 2019 at 13:13, Guido van Rossum
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