Metaklasy i ABCs

https://docs.python.org/3/reference/datamodel.html

PEP 3115 - Metaclasses in Python 3000

https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/

http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python

WPROWADZENIE

W Pythonie mamy do czynienia z obiektami. Funkcja jest obiektem, klasa jest obiektem, itp. Obiekt instancji jest instancją obiektu klasy, obiekt klasy jest instacją obiektu metaklasy.

Funkcjonalność metaklas częściowo pokrywa się z funkcjonalnością dekoratorów klas. Metaklasy są typowo używane jako fabryki klas (class-factory), ale badano różne inne zastosowania. Wg dokumentacji Pythona:
Some ideas that have been explored include enum, logging, interface checking, automatic delegation, automatic property creation, proxies, frameworks, and automatic resource locking/synchronization.

Kiedy wykonywana jest instrukcja 'class', wykonywane są następujące kroki:

TWORZENIE METAKLASY

Metaklasy najczęściej implementują dwie metody (__init__, __new__), przejmując kontrolę nad procedurą tworzenia i inicjalizacji nowej instancji klasy. Klasy otrzymują nową warstwę logiki.


# Domyślnie klasy są konstruowane przy użyciu type().
# Nazwa klasy jest łączona z wynikiem type(name, bases, clsdict), np.
# MyClass = type("MyClass", (), {})
# class MyClass: pass   # równoważnym kod

MyClass = type("MyClass", (BaseClass,), {'attribute' : 42})

# To jest równoważne z kodem:

class MyClass(BaseClass):
    attribute = 42

# Metaklasy najczęściej implementują dwie metody.
class Meta(type):   # dziedziczenie z 'type' w klasach w nowym stylu
    def __init__ ...   # opcjonalne
    def __new__(mcls, name, bases, clsdict):
        clsdict['foo'] = 'Meta was here'
        # Dodajemy atrybut 'foo' do słownika klasy
        # zanim zostanie utworzona nowa klasa.
        return type.__new__(mcls, name, bases, clsdict)

#class C(object):   # klasy w nowym stylu
#    __metaclass__ = Meta   # składnia Py2.6+
# Teraz zamiast type(name, bases, clsdict)
# do budowy klasy będzie użyte Meta.

class C(metaclass=Meta):
    pass   # składnia Py3 (PEP 3115)

print(dir(C))   # znajdziemy klucz 'foo'

ABSTRACT BASE CLASSES

https://docs.python.org/3/library/abc.html

https://docs.python.org/3/library/collections.abc.html

https://www.geeksforgeeks.org/abstract-base-class-abc-in-python/
Abstract Base Class (abc) in Python.

https://realpython.com/python-interface/
Implementing an Interface in Python.

PEP 3119 - Introducing Abstract Base Classes (Py3)

PEP 3141 - A Type Hierarchy for Numbers.
Number (the root of the numeric hierarchy),
Complex (real, imag, conversions to complex and bool),
Real (conversion to float, comparisons),
Rational (numerator, denominator, conversion to float),
Integral (conversion to int).

Abstrakcyjne klasy bazowe (ABC) dostarczają standardowego sposobu sprawdzania, czy obiekt spełnia daną specyfikację. Próba utworzenia podklasy bez nadpisania przygotowanych metod z klasy bazowej nie uda się.

Moduł 'abc' dostarcza infrastrukturę do definiowania ABCs. W module 'collections' znajdują się pewne konkretne klasy wyprowadzone z ABCs. W podmodule 'collections.abc' znajdują się pewne ABCs, które można użyć do testowania interfejsów.

Abstrakcyjne klasy bazowe mają zestaw metod abstrakcyjnych.


from abc import ABCMeta

class MyABC(object):   # tworzenie abstrakcyjnej klasy bazowej
    __metaclass__ = ABCMeta   # Py2.7
    instrukcje

from abc import ABCMeta

class MyABC(metaclass=ABCMeta):   # Py3.4+
    instrukcje

# Klasa pomocnicza 'ABC' ma 'ABCMeta' jako metaklasę.
# Klasę wprowadzono, aby uniknąć niezręcznej składni metaklas.
# Teraz można zrobić zwykłe dziedziczenie po 'ABC'.

from abc import ABC   # Py3.4+
from abc import abstractmethod   # dekorator wskazujący na metodę abstrakcyjną

class MyABC(ABC):   # tworzenie abstrakcyjnej klasy bazowej

    @abstractmethod
    def my_abstract_method(self, arg1):
        instrukcje

    @classmethod    # ważna jest kolejność dekoratorów
    @abstractmethod
    def my_abstract_classmethod(cls, arg2):
        instrukcje

    @staticmethod
    @abstractmethod
    def my_abstract_staticmethod(arg3):
        instrukcje

    @property
    @abstractmethod
    def my_abstract_property(self):
        instrukcje

    @my_abstract_property.setter
    @abstractmethod
    def my_abstract_property(self, val):
        instrukcje

# Przyłączamy do naszej abstrakcyjne klasy bazowej wbudowaną klasę tuple.
# register(subclass)
# Register subclass as a "virtual subclass" of this ABC.

MyABC.register(tuple)

assert issubclass(tuple, MyABC)
assert isinstance(tuple(), MyABC)

# Tworzenie nowej klasy przez bezpośrednie dziedziczenie 
# po abstrakcyjne klasie bazowej.

from collections.abc import Sequence

class C(Sequence):   # bezpośrednie dziedziczenie
    def __init__(self):   # dodatkowa metoda nie wymagana przez ABC
        instrukcje
    def __getitem__(self, index):   # wymagana metoda abstrakcyjna
        instrukcje
    def __len__(self):   # wymagana metoda abstrakcyjna
        instrukcje
    def count(self, value):   # mixin method (opcjonalnie nadpisana)
        instrukcje
    def index(self, value):   # mixin method (opcjonalnie nadpisana)
        instrukcje

assert issubclass(C, Sequence)
assert isinstance(C(), Sequence)

# Rejestracja istniejącej klasy jako 'wirtualnej podklasy'
# pewnej abstrakcyjnej klasy bazowej.

from collections.abc import Sequence

class D:
    def __init__(self):   # dodatkowa metoda nie wymagana przez ABC
        instrukcje
    def __getitem__(self, index):   # metoda abstrakcyjna
        instrukcje
    def __len__(self):   # metoda abstrakcyjna
        instrukcje
    def count(self, value):   # mixin method
        instrukcje
    def index(self, value):   # mixin method
        instrukcje

Sequence.register(D)   # rejestracja zamiast dziedziczenia

assert issubclass(D, Sequence)
assert isinstance(D(), Sequence)

# Możemy pytać klasę lub instancję o pewną funkcjonalność.

import collections

size = None
#if isinstance(my_variable, collections.Sized):   # Py2.7
if isinstance(my_variable, collections.abc.Sized):   # Py3.3+
    size = len(my_variable)   # wiemy, że istnieje __len__