.. _native-classes:

Native classes
==============

Classes in compiled modules are *native classes* by default (some
exceptions are discussed below). Native classes are compiled to C
extension classes, which have some important differences from normal
Python classes. Native classes are similar in many ways to built-in
types, such as ``int``, ``str``, and ``list``.

Immutable namespaces
--------------------

The type object namespace of native classes is mostly immutable (but
class variables can be assigned to)::

    class Cls:
        def method1(self) -> None:
            print("method1")

        def method2(self) -> None:
            print("method2")

    Cls.method1 = Cls.method2  # Error
    Cls.new_method = Cls.method2  # Error

Only attributes defined within a class definition (or in a base class)
can be assigned to (similar to using ``__slots__``)::

    class Cls:
        x: int

        def __init__(self, y: int) -> None:
            self.x = 0
            self.y = y

        def method(self) -> None:
            self.z = "x"

    o = Cls(0)
    print(o.x, o.y)  # OK
    o.z = "y"  # OK
    o.extra = 3  # Error: no attribute "extra"

.. _inheritance:

Inheritance
-----------

Only single inheritance is supported from native classes (except for
:ref:`traits <trait-types>`). Most non-native extension classes can't
be used as base classes, but regular Python classes can be used as
base classes unless they use unsupported metaclasses (see below for
more about this).

These non-native extension classes can be used as base classes of native
classes:

* ``object``
* ``dict`` (and ``dict[k, v]``)
* ``BaseException``
* ``Exception``
* ``ValueError``
* ``IndexError``
* ``LookupError``
* ``UserWarning``

By default, a non-native class can't inherit a native class, and you
can't inherit from a native class outside the compilation unit that
defines the class. You can enable these through
``mypy_extensions.mypyc_attr``::

    from mypy_extensions import mypyc_attr

    @mypyc_attr(allow_interpreted_subclasses=True)
    class Cls:
        ...

Allowing interpreted subclasses has only minor impact on performance
of instances of the native class.  Accessing methods and attributes of
a *non-native* subclass (or a subclass defined in another compilation
unit) will be slower, since it needs to use the normal Python
attribute access mechanism.

You need to install ``mypy-extensions`` to use ``@mypyc_attr``:

.. code-block:: text

    pip install --upgrade mypy-extensions

Additionally, mypyc recognizes these base classes as special, and
understands how they alter the behavior of classes (including native
classes) that subclass them:

* ``typing.NamedTuple``
* ``typing.Generic``
* ``typing.Protocol``
* ``enum.Enum``

Class variables
---------------

Class variables must be explicitly declared using ``attr: ClassVar``
or ``attr: ClassVar[<type>]``. You can't assign to a class variable
through an instance. Example::

    from typing import ClassVar

    class Cls:
        cv: ClassVar = 0

    Cls.cv = 2  # OK
    o = Cls()
    print(o.cv)  # OK (2)
    o.cv = 3  # Error!

.. tip::

    Constant class variables can be declared using ``typing.Final`` or
    ``typing.Final[<type>]``.

Generic native classes
----------------------

Native classes can be generic. Type variables are *erased* at runtime,
and instances don't keep track of type variable values.

Compiled code thus can't check the values of type variables when
performing runtime type checks. These checks are delayed to when
reading a value with a type variable type::

    from typing import TypeVar, Generic, cast

    T = TypeVar('T')

    class Box(Generic[T]):
        def __init__(self, item: T) -> None:
            self.item = item

    x = Box(1)  # Box[int]
    y = cast(Box[str], x)  # OK (type variable value not checked)
    y.item  # Runtime error: item is "int", but "str" expected

Metaclasses
-----------

Most metaclasses aren't supported with native classes, since their
behavior is too dynamic. You can use these metaclasses, however:

* ``abc.ABCMeta``
* ``typing.GenericMeta`` (used by ``typing.Generic``)

.. note::

   If a class definition uses an unsupported metaclass, *mypyc
   compiles the class into a regular Python class* (non-native
   class).

Class decorators
----------------

Similar to metaclasses, most class decorators aren't supported with
native classes, as they are usually too dynamic. These class
decorators can be used with native classes, however:

* ``mypy_extensions.trait`` (for defining :ref:`trait types <trait-types>`)
* ``mypy_extensions.mypyc_attr`` (see :ref:`above <inheritance>`)
* ``dataclasses.dataclass``
* ``@attr.s(auto_attribs=True)``

Dataclasses and attrs classes have partial native support, and they aren't as
efficient as pure native classes.

.. note::

   If a class definition uses an unsupported class decorator, *mypyc
   compiles the class into a regular Python class* (non-native class).

Defining non-native classes
---------------------------

You can use the ``@mypy_extensions.mypyc_attr(...)`` class decorator
with an argument ``native_class=False`` to explicitly define normal
Python classes (non-native classes)::

    from mypy_extensions import mypyc_attr

    @mypyc_attr(native_class=False)
    class NonNative:
        def __init__(self) -> None:
            self.attr = 1

    setattr(NonNative, "extra", 1)  # Ok

This only has an effect in classes compiled using mypyc. Non-native
classes are significantly less efficient than native classes, but they
are sometimes necessary to work around the limitations of native classes.

Non-native classes can use arbitrary metaclasses and class decorators,
and they support flexible multiple inheritance.  Mypyc will still
generate a compile-time error if you try to assign to a method, or an
attribute that is not defined in a class body, since these are static
type errors detected by mypy::

    o = NonNative()
    o.extra = "x"  # Static type error: "extra" not defined

However, these operations still work at runtime, including in modules
that are not compiled using mypyc. You can also use ``setattr`` and
``getattr`` for dynamic access of arbitrary attributes. Expressions
with an ``Any`` type are also not type checked statically, allowing
access to arbitrary attributes::

    a: Any = o
    a.extra = "x"  # Ok

    setattr(o, "extra", "y")  # Also ok

Implicit non-native classes
---------------------------

If a compiled class uses an unsupported metaclass or an unsupported
class decorator, it will implicitly be a non-native class, as
discussed above. You can still use ``@mypyc_attr(native_class=False)``
to explicitly mark it as a non-native class.

Explicit native classes
-----------------------

You can use ``@mypyc_attr(native_class=True)`` to explicitly declare a
class as a native class. It will be a compile-time error if mypyc
can't compile the class as a native class. You can use this to avoid
accidentally defining implicit non-native classes.

Deleting attributes
-------------------

By default, attributes defined in native classes can't be deleted. You
can explicitly allow certain attributes to be deleted by using
``__deletable__``::

   class Cls:
       x: int = 0
       y: int = 0
       other: int = 0

       __deletable__ = ['x', 'y']  # 'x' and 'y' can be deleted

   o = Cls()
   del o.x  # OK
   del o.y  # OK
   del o.other  # Error

You must initialize the ``__deletable__`` attribute in the class body,
using a list or a tuple expression with only string literal items that
refer to attributes. These are not valid::

   a = ['x', 'y']

   class Cls:
       x: int
       y: int

       __deletable__ = a  # Error: cannot use variable 'a'

   __deletable__ = ('a',)  # Error: not in a class body

Acyclic classes
---------------

By default, native classes participate in CPython's cyclic garbage
collector (GC). This adds some overhead to object allocation and
deallocation. If you know that instances of a class can never be
part of reference cycles, you can opt out of cyclic GC using
``@mypyc_attr(acyclic=True)``::

    from mypy_extensions import mypyc_attr

    @mypyc_attr(acyclic=True)
    class Leaf:
        def __init__(self, x: int, name: str) -> None:
            self.x = x
            self.name = name

This can improve performance, especially for classes that are
allocated and deallocated frequently. Acyclic instances also use
less memory, since CPython doesn't need to add a GC header to them.

The acyclic property is not inherited by subclasses. Each subclass
must explicitly use ``@mypyc_attr(acyclic=True)`` to also opt out
of cyclic GC.

.. warning::

    If instances of an acyclic class actually participate in reference
    cycles, those cycles will never be collected, resulting in memory
    leaks. Only use this for classes whose instances won't refer back
    to objects that (directly or indirectly) refer to the instance.

Other properties
----------------

Instances of native classes don't usually have a ``__dict__`` attribute.
