python/typing

typing/Generic __orig_class__ availability during __init__ in 3.6 vs 3.7

baltus-atomicrules opened this issue · 9 comments

I observe that when executing __init__ in a Generic class, the value of __orig_class__ is available in version 3.6 but not in 3.7

(I am comparing 3.6.7 with 3.7.0)

I've attached a simple example that defines a Generic class and then accesses __orig_class__ in the associated __init__ method.

Simple.py.txt

N = TypeVar('N')
class ZZ(Generic[N]):
    def __init__(self):
        print("__orig_class__ is: ", self.__orig_class__)

In 3.6, this code runs fine:

In [42]: import Simple

In [51]: a = Simple.ZZ[int]

In [56]: b = a()
__orig_class__ is:  Simple.ZZ[int]

In 3.7 however, the value of __orig_class__ is not available:

In [4]: import Simple

In [5]: a = Simple.ZZ[int]

In [6]: b = a()

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-6-52a6c422c17e> in <module>
----> 1 b = a()
/tool/pandora64/.package/python-3.7.0/lib/python3.7/typing.py in __call__(self, *args, **kwargs)
    668             raise TypeError(f"Type {self._name} cannot be instantiated; "
    669                             f"use {self._name.lower()}() instead")
--> 670         result = self.__origin__(*args, **kwargs)
    671         try:
    672             result.__orig_class__ = self
/proj/emu_scratch4/users/dbaltus/python/experiment/Simple.py in __init__(self)
      4 class ZZ(Generic[N]):
      5     def __init__(self):
----> 6         print("__orig_class__ is: ", self.__orig_class__)
AttributeError: 'ZZ' object has no attribute '__orig_class__'

> /proj/emu_scratch4/users/dbaltus/python/experiment/Simple.py(6)__init__()
      2
      3 N = TypeVar('N')
      4 class ZZ(Generic[N]):
      5     def __init__(self):
----> 6         print("__orig_class__ is: ", self.__orig_class__)
ipdb> q

This occurs because in 3.7, the __call__ method of _GenericAlias does not set the value of __orig_class__ until after __init__ is called. (It is done differently in 3.6).

    def __call__(self, *args, **kwargs):
        if not self._inst:
            raise TypeError(f"Type {self._name} cannot be instantiated; "
                            f"use {self._name.lower()}() instead")
        result = self.__origin__(*args, **kwargs)
        try:
            result.__orig_class__ = self
        except AttributeError:
            pass
        return result

Yes, this is unfortunate, but as you know, this is undocumented internal attribute that you are using totally at your own risk. So unless you have a simple idea how to fix this, I don't think there is anything we can do here.

@baltus-atomicrules
For the record, you can use pytypes.get_orig_class to solve this.
Note: It's only available in master as of this writing. (I hope to make an new release soon.)

mitar commented

this is undocumented internal attribute that you are using totally at your own risk.

Sure, but that makes also typing_inspect fail inside constructor: ilevkivskyi/typing_inspect#35

We can't change what typing looks like in 3.6 or 3.7, so I don't think there's anything actionable in this issue.

mitar commented

I mean, the same still holds true in 3.8, 3.9, 3.10. So the issue in all of those that inside __init__ you cannot access __orig_class__. The issue was only reported when 3.7 was out, but it is still valid.

This has been annoying me as of late, couldn't we just have it not be available in __new__ but available in __init__?

Hi, is there any updates? Thanks

I still face this issue with Python 3.11. Are there any valid workarounds for this?

Please report any issues related to issues with the typing module to CPython's issue tracker: https://github.com/python/cpython/issues, where the module is maintained nowadays.