My current implementation 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:
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 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