#!# ============
#!#  Data Model
#!# ============
#!#
#!# This page contains a memo of the Python 3.6 data model.
#!#
#!# For a complete reference documentation, look at
#!# https://docs.python.org/3/reference/datamodel.html
#!#

####################################################################################################

import collections
import os

from Tools import *

####################################################################################################
#!#
#!# At __main__ level
#!# -----------------

a_global = 1

print('file:', os.path.basename(__file__))

#o#

####################################################################################################
#!#
#!# Basic Customisation
#!# -------------------

#h# print_rule()

class Foo:

    """Foo is a class"""

    attr1 = 1

    # Arbitrary code
    attr2 = []
    for i in range(10):
        attr2.append(i)

    def __init__(self, *args, **kwargs):
        print_method('Foo', '__init__', self, args, kwargs)

    def method(self, arg1, arg2, attr1='abc'):
        """a method"""

    method.attr1 = 'abc' # set method attributes

    # def __del__(self):
    # def __repr__(self):
    # def __str__(self):
    # def __bytes__(self):
    # def __format__(self, format_spec):

    # def __lt__(self, other):
    # def __le__(self, other):
    # def __eq__(self, other):
    # def __ne__(self, other):
    # def __gt__(self, other):
    # def __ge__(self, other):

    # def __hash__(self):
    # def __bool__(self):

#!#

foo = Foo(1, 2, attr1='abc')
#o#

print(Foo.__class__)
print('doc:', Foo.__doc__)
#o#

pretty_print('dict:', Foo.__dict__)
#o#

print('doc:', Foo.method.__doc__)
print('name:', Foo.method.__name__) # method
print('qualname:', Foo.method.__qualname__) # Foo.method
print('module:', Foo.method.__module__) # __main__
#o#

print('code:', Foo.method.__code__)
print('globals:', Foo.method.__globals__)
#o#

# A tuple containing default argument values for those arguments that have defaults, or None if no
# arguments have a default value
print('defaults:', Foo.method.__defaults__) # ('abc',)

# A dict containing defaults for keyword-only parameters.
print('kwdefaults:', Foo.method.__kwdefaults__) # None
#o#

# None or a tuple of cells that contain bindings for the function’s free variables.
# print('closure:', Foo.method.__closure__)
# A dict containing annotations of parameters. The keys of the dict are the parameter names, and
# 'return' for the return annotation, if provided.
# print('annotations:', Foo.method.__annotations__)

#!#

print(foo.method.attr1) # 'abc'
#o#

foo.method(1, 2)
#o#

#?# __func__
#?# __self__

####################################################################################################

#h# print_rule()

class FooWithNew:

    def __new__(cls, *args, **kwargs):
        print_method('FooWithNew', '__new__', cls, args, kwargs)

    def __init__(self, *args, **kwargs):
        # never called
        print_method(self, '__init__', self, args, kwargs)

#!#

foo = FooWithNew(1, 2, attr1='abc')

#o#

####################################################################################################
#!#
#!# Subclasses
#!# ----------

#h# print_rule()

class Bar(Foo):

    def __init__(self, *args, **kwargs):
        print_method(self, '__init__', self, args, kwargs)
        super().__init__(*args, **kwargs)

#!#

obj = Bar(1, 2, attr1='abc')

#o#

####################################################################################################
#!#
#!# Customizing Attribute Access
#!# ----------------------------

#h# print_rule()

class Attributes:

    attr1 = 1

    def __getattr__(self, name):
        print_method(self, '__getattr__', self, name)
        return None

    def __setattr__(self, name, value):
        print_method(self, '__setattr__', self, name)

    def __delattr__(self, name):
        print_method(self, '__deltattr__', self, name)

    # def __dir__(self):

#!#

obj = Attributes()
print(obj.attr1)
print(obj.foo)

#o#

####################################################################################################

class Attributes2:

    attr1 = 1

    # Fixme:
    def __getattribute__(self, name):
        print_method(self, '__getattribute__', self, name)
        raise AttributeError
        # return type.__getattribute__(self, name)
        # return object.__getattribute__(self, name)

#!#

obj = Attributes2()
#print(obj.attr1)
#obj.foo

##o#

####################################################################################################
#!#
#!# Implementing Descriptors
#!# ------------------------

#h# print_rule()

#!# :frompy:`3.6` :feature:`__set_name__`  `PEP 487 - Descriptor Protocol Enhancements <https://www.python.org/dev/peps/pep-0487>`_

class Descriptor:

    def __init__(self, name, value):

        self._name = name
        self._value = value

    def __get__(self, instance, owner):
        print_method(self, '__get__', self, instance, owner)
        return self._value

    def __set__(self, instance, value):
        print_method(self, '_set__', self, instance, value)
        self._value = value

    def __delete__(self, instance):
        print_method(self, '__delete__', self, instance)

    # since 3.6
    def __set_name__(self, owner, name):
        print_method(self, '__set_name__', self, owner, name)


class UsingDescriptor:
    attr1 = Descriptor('attr1', 1) # class __set_name__(__main__.UsingDescriptor, 'attr1')

#!# Invoking Descriptors

obj = UsingDescriptor()
print(obj.attr1) # call __get__
obj.attr1 = 2 # call __set__
del obj.attr1 # call __delete__

#o#

####################################################################################################
#!#
#!# Slots
#!# -----

#h# print_rule()

class Slotted:

    __slots__ = ('x', 'y')

#!#

obj = Slotted()
print(obj.__slots__)
obj.x = 1 # implemented as descriptor
obj.y = 2

#o#

####################################################################################################
#!#
#!# Properties
#!# ----------

#h# print_rule()

class Property:

    def get_x(self):
        return self.__x

    def set_x(self, value):
        self.__x = value

    def del_x(self):
        del self.__x

    x = property(get_x, set_x, del_x, "I'm the 'x' property.")

####################################################################################################
#!#
#!# Customising Class Creation
#!# --------------------------
#!#
#!# :frompy:`3.6`
#!#
#!# `PEP 487 - Simpler customization of class creation <https://www.python.org/dev/peps/pep-0487>`_

#h# print_rule()

class Philosopher:
    def __init_subclass__(cls, default_name, **kwargs):
        print_method(cls, '__init_subclass__', default_name, kwargs)
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    pass

obj = Philosopher()
obj = AustralianPhilosopher()
#o#

class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

print(PluginBase.subclasses)
#o#

####################################################################################################
#!#
#!# Metaclass
#!# ---------

#h# print_rule()

class Metaclass(type):

    @classmethod
    def __prepare__(meta_cls, name, bases, **kwargs):
        print_method(meta_cls, '__prepare__', name, bases, kwargs)
        return collections.OrderedDict()

    def __new__(meta_cls, name, bases, namespace, **kwargs):
        print_method('Metaclass', '__new__', meta_cls, name, bases, namespace, kwargs)
        result = type.__new__(meta_cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

    def __init__(cls, name, bases, namespace):
        # Attention: first parameter is cls, not meta_cls !
        print_method('Metaclass', '__init__', cls, name, bases, namespace)
        print_method('Metaclass', '__init__', cls.__dict__)
        type.__init__(cls, name, bases, namespace)
        # super(Metaclass, cls).__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        print_method(cls, '__call__', cls, args, kwargs)
        return type.__call__(cls, *args, **kwargs)

class Metaclassed(metaclass=Metaclass):

    def __init__(self, *args, **kwargs):
        print_method(self, '__init__', self, args, kwargs)

    def one(self): pass
    def two(self): pass
    def three(self): pass
    def four(self): pass
#o#

pretty_print(Metaclassed.__dict__)
#o#

class SubMetaclassed(Metaclassed):
    pass
#o#

print(Metaclassed.members)
#o#

obj = Metaclassed(1, 2, attr1='abc')
#o#

obj = SubMetaclassed(1, 2, attr1='abc')
#o#

####################################################################################################
#!#
#!# Customising Instance and Subclass Checks
#!# ----------------------------------------
#!#
#!# `PEP 3119 - Introducing Abstract Base Classes <https://www.python.org/dev/peps/pep-3119>`_

class CheckableMetaclass(type):

    def __instancecheck__(self, instance):
        print_method(self, '__init_subclass__', self, instance)
        return super().__instancecheck__(instance)

    def __subclasscheck__(self, subclass):
        print_method(self, '__subclasscheck__', self, subclass)
        return super().__subclasscheck__(subclass)

class CheckableClass(metaclass=CheckableMetaclass):
    pass

class SubCheckableClass(CheckableClass):
    pass

subobj = SubCheckableClass()
print(isinstance(subobj, CheckableClass))
print(isinstance(subobj, SubCheckableClass))
print(issubclass(subobj.__class__, CheckableClass))
#o#

####################################################################################################
#!#
#!# Emulating Callable Objects
#!# --------------------------

#h# print_rule()

class Callable:

    def __call__(self, *args, **kwargs):
        print_method(self, '__call__', args, kwargs)

#!#

obj = Callable()
obj(1, 2, attr1='abc') # call Callable.__call__

#o#

####################################################################################################
#!#
#!# Emulating Container Types
#!# -------------------------

#h# print_rule()

class Container:

    def __init__(self, *values):
        print_method(self, '__init__', values)
        self._values = list(values) # mutable

    def __len__(self):
        print_method(self, '__len__')
        return len(self._values)

    def __length_hint__(self):
        print_method(self, '__length_hint__')
        # Called to implement operator.length_hint()
        return 10

    def __getitem__(self, key):
        print_method(self, '__getitem__', key)
        return self._values[key] # key can be a slice

    def __missing__(self, key):
        # Called by dict.__getitem__() to implement self[key] for dict subclasses when key is not in the dictionary.
        print_method(self, '__missing__')

    def __setitem__(self, key, value):
        print_method(self, '__setitem__', key, value)
        self._values[key] = value

    def __delitem__(self, key):
        print_method(self, '__delitem__', key)
        del self._values[key]

    def __iter__(self):
        print_method(self, '__iter__')
        return iter(self._values)

    def __reversed__(self):
        print_method(self, '__reversed__')
        return reversed(self._values)

    def __contains__(self, item):
        print_method(self, '__contains__', item)
        return item in self._values

#!#

obj = Container(*range(5))
print(obj[1])
obj[4] = 6
print(len(obj))
print(list(obj))
print(list(reversed(obj)))
print(1 in obj)

#o#

####################################################################################################
#!#
#!# Emulating Numeric Types
#!# -----------------------

class Numeric:

    # These methods are called to implement the binary arithmetic operations:
    # + - * @ / // % divmod() pow() ** << >> & ^ |

    def __add__(self, other):
        pass

    def __sub__(self, other):
        pass

    def __mul__(self, other):
        pass

    def __matmul__(self, other):
        pass

    def __truediv__(self, other):
        pass

    def __floordiv__(self, other):
        pass

    def __mod__(self, other):
        pass

    def __divmod__(self, other):
        pass

    def __pow__(self, other, modulo):
        pass

    def __lshift__(self, other):
        pass

    def __rshift__(self, other):
        pass

    def __and__(self, other):
        pass

    def __xor__(self, other):
        pass

    def __or__(self, other):
        pass

    # These methods are called to implement the binary arithmetic operations with reflected (swapped) operands:
    # + - * @ / // % divmod() pow() ** << >> & ^ |

    def __radd__(self, other):
        pass

    def __rsub__(self, other):
        pass

    def __rmul__(self, other):
        pass

    def __rmatmul__(self, other):
        pass

    def __rtruediv__(self, other):
        pass

    def __rfloordiv__(self, other):
        pass

    def __rmod__(self, other):
        pass

    def __rdivmod__(self, other):
        pass

    def __rpow__(self, other):
        pass

    def __rlshift__(self, other):
        pass

    def __rrshift__(self, other):
        pass

    def __rand__(self, other):
        pass

    def __rxor__(self, other):
        pass

    def __ror__(self, other):
        pass

    # These methods are called to implement the augmented arithmetic assignments:
    # += -= *= @= /= //= %= **= <<= >>= &= ^= |=

    def __iadd__(self, other):
        pass

    def __isub__(self, other):
        pass

    def __imul__(self, other):
        pass

    def __imatmul__(self, other):
        pass

    def __itruediv__(self, other):
        pass

    def __ifloordiv__(self, other):
        pass

    def __imod__(self, other):
        pass

    def __ipow__(self, other, modulo):
        pass

    def __ilshift__(self, other):
        pass

    def __irshift__(self, other):
        pass

    def __iand__(self, other):
        pass

    def __ixor__(self, other):
        pass

    def __ior__(self, other):
        pass

    # Called to implement the unary arithmetic operations:
    # - + abs() ~

    def __neg__(self):
        pass

    def __pos__(self):
        pass

    def __abs__(self):
        pass

    def __invert__(self):
        pass

    # Called to implement the built-in functions complex(), int(), float() and round():

    def __complex__(self):
        pass

    def __int__(self):
        pass

    def __float__(self):
        pass

    def __round__(self, n):
        pass

    def __index__(self):
        # Called to implement operator.index()
        pass

####################################################################################################
#!#
#!# With Statement Context Managers
#!# -------------------------------

class ContextManager:

    def __init__(self, *args, **kwargs):
        print_method(self, '__init__', self, args, kwargs)

    def __enter__(self):
        print_method(self, '__enter__', self)
        return 'something' # value for as

    def __exit__(self, exc_type, exc_value, traceback):
        print_method(self, '__exit__', self, exc_type, exc_value, traceback)

#!#

with ContextManager(1, 2, attr1='abc') as obj:
    print('here', obj)

#o#

try:
    with ContextManager(1, 2, attr1='abc') as obj:
        print('here', obj)
        raise NameError('an error')
except NameError as exception:
    print(exception)
#o#

####################################################################################################
#!#
#!# Coroutines
#!# ----------

#!# Awaitable Objects
#!# ~~~~~~~~~~~~~~~~~

# object.__await__(self)

#!# Coroutine Objects
#!# ~~~~~~~~~~~~~~~~~

# coroutine.send(value)
# coroutine.throw(type[, value[, traceback]])
# coroutine.close()

#!# Asynchronous Iterators
#!# ~~~~~~~~~~~~~~~~~~~~~~
#!#
#!# An asynchronous iterable is able to call asynchronous code in its __aiter__ implementation, and
#!# an asynchronous iterator can call asynchronous code in its __anext__ method.

class Reader:

    async def readline(self):
        pass

    def __aiter__(self):
        # Must return an asynchronous iterator object.
        return self

    async def __anext__(self):
        # Must return an awaitable resulting in a next value of the iterator.
        # Should raise a StopAsyncIteration error when the iteration is over.
        val = await self.readline()
        if val == b'':
            raise StopAsyncIteration
        return val

#!# Asynchronous Context Managers
#!# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#!#
#!# An asynchronous context manager is a context manager that is able to suspend execution in its
#!# __aenter__ and __aexit__ methods.

class AsyncContextManager:

    async def __aenter__(self):
        # This method is semantically similar to the __enter__(),
        # with only difference that it must return an awaitable.
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        # This method is semantically similar to the __exit__(),
        # with only difference that it must return an awaitable.
        await log('exiting context')

3.3.5. Data Model

This page contains a memo of the Python 3.6 data model.

For a complete reference documentation, look at https://docs.python.org/3/reference/datamodel.html

import collections
import os

from Tools import *

3.3.5.1. At __main__ level

a_global = 1

print('file:', os.path.basename(__file__))
NameError

3.3.5.2. Basic Customisation

class Foo:

    """Foo is a class"""

    attr1 = 1

    # Arbitrary code
    attr2 = []
    for i in range(10):
        attr2.append(i)

    def __init__(self, *args, **kwargs):
        print_method('Foo', '__init__', self, args, kwargs)

    def method(self, arg1, arg2, attr1='abc'):
        """a method"""

    method.attr1 = 'abc' # set method attributes

    # def __del__(self):
    # def __repr__(self):
    # def __str__(self):
    # def __bytes__(self):
    # def __format__(self, format_spec):

    # def __lt__(self, other):
    # def __le__(self, other):
    # def __eq__(self, other):
    # def __ne__(self, other):
    # def __gt__(self, other):
    # def __ge__(self, other):

    # def __hash__(self):
    # def __bool__(self):
foo = Foo(1, 2, attr1='abc')
@Foo.__init__
    <__main__.Foo object at 0x7faff8f92c88>
    (1, 2)
    {'attr1': 'abc'}
print(Foo.__class__)
print('doc:', Foo.__doc__)
<class 'type'>
doc: Foo is a class
pretty_print('dict:', Foo.__dict__)
'dict:'
mappingproxy({   '__dict__': <attribute '__dict__' of 'Foo' objects>,
                 '__doc__': 'Foo is a '
                            'class',
                 '__init__': <function Foo.__init__ at 0x7faff8f786a8>,
                 '__module__': '__main__',
                 '__weakref__': <attribute '__weakref__' of 'Foo' objects>,
                 'attr1': 1,
                 'attr2': [   0,
                              1,
                              2,
                              3,
                              4,
                              5,
                              6,
                              7,
                              8,
                              9],
                 'i': 9,
                 'method': <function Foo.method at 0x7faff8f78378>})
print('doc:', Foo.method.__doc__)
print('name:', Foo.method.__name__) # method
print('qualname:', Foo.method.__qualname__) # Foo.method
print('module:', Foo.method.__module__) # __main__
doc: a method
name: method
qualname: Foo.method
module: __main__
print('code:', Foo.method.__code__)
print('globals:', Foo.method.__globals__)
code: <code object method at 0x7faff8f6c9c0, file "<ipython-input-1-f634fa719b33>", line 15>
globals: {'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': [''], '_oh': {}, '_dh': ['/tmp/tmp9kfthd8s'], 'In': [''], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7fb001358e48>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x7fb00008dfd0>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x7fb00008dfd0>, '_': '', '__': '', '___': '', 'save_figure': <function save_figure at 0x7faff97e4488>, 'collections': <module 'collections' from '/home/opt/python-virtual-env/py36/lib64/python3.6/collections/__init__.py'>, 'os': <module 'os' from '/home/opt/python-virtual-env/py36/lib64/python3.6/os.py'>, 'pretty_print': <function pretty_print at 0x7faff8f7d158>, 'print_function': <function print_function at 0x7faff8f7d1e0>, 'print_method': <function print_method at 0x7faff8f7d268>, 'print_rule': <function print_rule at 0x7faff8f7d2f0>, 'a_global': 1, 'Foo': <class '__main__.Foo'>, 'foo': <__main__.Foo object at 0x7faff8f92c88>}
# A tuple containing default argument values for those arguments that have defaults, or None if no
# arguments have a default value
print('defaults:', Foo.method.__defaults__) # ('abc',)

# A dict containing defaults for keyword-only parameters.
print('kwdefaults:', Foo.method.__kwdefaults__) # None
defaults: ('abc',)
kwdefaults: None
# None or a tuple of cells that contain bindings for the function’s free variables.
# print('closure:', Foo.method.__closure__)
# A dict containing annotations of parameters. The keys of the dict are the parameter names, and
# 'return' for the return annotation, if provided.
# print('annotations:', Foo.method.__annotations__)
print(foo.method.attr1) # 'abc'
abc
foo.method(1, 2)

class FooWithNew:

    def __new__(cls, *args, **kwargs):
        print_method('FooWithNew', '__new__', cls, args, kwargs)

    def __init__(self, *args, **kwargs):
        # never called
        print_method(self, '__init__', self, args, kwargs)
foo = FooWithNew(1, 2, attr1='abc')
@FooWithNew.__new__
    <class '__main__.FooWithNew'>
    (1, 2)
    {'attr1': 'abc'}

3.3.5.3. Subclasses

class Bar(Foo):

    def __init__(self, *args, **kwargs):
        print_method(self, '__init__', self, args, kwargs)
        super().__init__(*args, **kwargs)
obj = Bar(1, 2, attr1='abc')
@Bar.__init__
    <__main__.Bar object at 0x7faff8f92978>
    (1, 2)
    {'attr1': 'abc'}
@Foo.__init__
    <__main__.Bar object at 0x7faff8f92978>
    (1, 2)
    {'attr1': 'abc'}

3.3.5.4. Customizing Attribute Access

class Attributes:

    attr1 = 1

    def __getattr__(self, name):
        print_method(self, '__getattr__', self, name)
        return None

    def __setattr__(self, name, value):
        print_method(self, '__setattr__', self, name)

    def __delattr__(self, name):
        print_method(self, '__deltattr__', self, name)

    # def __dir__(self):
obj = Attributes()
print(obj.attr1)
print(obj.foo)
1
@Attributes.__getattr__
    <__main__.Attributes object at 0x7faff8f92e10>
    'foo'
None
class Attributes2:

    attr1 = 1

    # Fixme:
    def __getattribute__(self, name):
        print_method(self, '__getattribute__', self, name)
        raise AttributeError
        # return type.__getattribute__(self, name)
        # return object.__getattribute__(self, name)
obj = Attributes2()
#print(obj.attr1)
#obj.foo

##o#

3.3.5.5. Implementing Descriptors

:frompy:`3.6` :feature:`__set_name__` PEP 487 - Descriptor Protocol Enhancements

class Descriptor:

    def __init__(self, name, value):

        self._name = name
        self._value = value

    def __get__(self, instance, owner):
        print_method(self, '__get__', self, instance, owner)
        return self._value

    def __set__(self, instance, value):
        print_method(self, '_set__', self, instance, value)
        self._value = value

    def __delete__(self, instance):
        print_method(self, '__delete__', self, instance)

    # since 3.6
    def __set_name__(self, owner, name):
        print_method(self, '__set_name__', self, owner, name)


class UsingDescriptor:
    attr1 = Descriptor('attr1', 1) # class __set_name__(__main__.UsingDescriptor, 'attr1')

Invoking Descriptors

obj = UsingDescriptor()
print(obj.attr1) # call __get__
obj.attr1 = 2 # call __set__
del obj.attr1 # call __delete__
@Descriptor.__get__
    <__main__.Descriptor object at 0x7faff8f0a2e8>
    <__main__.UsingDescriptor object at 0x7faff8f019b0>
    <class '__main__.UsingDescriptor'>
1
@Descriptor._set__
    <__main__.Descriptor object at 0x7faff8f0a2e8>
    <__main__.UsingDescriptor object at 0x7faff8f019b0>
    2
@Descriptor.__delete__
    <__main__.Descriptor object at 0x7faff8f0a2e8>
    <__main__.UsingDescriptor object at 0x7faff8f019b0>

3.3.5.6. Slots

class Slotted:

    __slots__ = ('x', 'y')
obj = Slotted()
print(obj.__slots__)
obj.x = 1 # implemented as descriptor
obj.y = 2
('x', 'y')

3.3.5.7. Properties

class Property:

    def get_x(self):
        return self.__x

    def set_x(self, value):
        self.__x = value

    def del_x(self):
        del self.__x

    x = property(get_x, set_x, del_x, "I'm the 'x' property.")

3.3.5.8. Customising Class Creation

:frompy:`3.6`

PEP 487 - Simpler customization of class creation

class Philosopher:
    def __init_subclass__(cls, default_name, **kwargs):
        print_method(cls, '__init_subclass__', default_name, kwargs)
        super().__init_subclass__(**kwargs)
        cls.default_name = default_name

class AustralianPhilosopher(Philosopher, default_name="Bruce"):
    pass

obj = Philosopher()
obj = AustralianPhilosopher()
@type.__init_subclass__
    'Bruce'
    {}
class PluginBase:
    subclasses = []

    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        cls.subclasses.append(cls)

class Plugin1(PluginBase):
    pass

class Plugin2(PluginBase):
    pass

print(PluginBase.subclasses)
[<class '__main__.Plugin1'>, <class '__main__.Plugin2'>]

3.3.5.9. Metaclass

class Metaclass(type):

    @classmethod
    def __prepare__(meta_cls, name, bases, **kwargs):
        print_method(meta_cls, '__prepare__', name, bases, kwargs)
        return collections.OrderedDict()

    def __new__(meta_cls, name, bases, namespace, **kwargs):
        print_method('Metaclass', '__new__', meta_cls, name, bases, namespace, kwargs)
        result = type.__new__(meta_cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

    def __init__(cls, name, bases, namespace):
        # Attention: first parameter is cls, not meta_cls !
        print_method('Metaclass', '__init__', cls, name, bases, namespace)
        print_method('Metaclass', '__init__', cls.__dict__)
        type.__init__(cls, name, bases, namespace)
        # super(Metaclass, cls).__init__(name, bases, namespace)

    def __call__(cls, *args, **kwargs):
        print_method(cls, '__call__', cls, args, kwargs)
        return type.__call__(cls, *args, **kwargs)

class Metaclassed(metaclass=Metaclass):

    def __init__(self, *args, **kwargs):
        print_method(self, '__init__', self, args, kwargs)

    def one(self): pass
    def two(self): pass
    def three(self): pass
    def four(self): pass
@type.__prepare__
    'Metaclassed'
    ()
    {}
@Metaclass.__new__
    <class '__main__.Metaclass'>
    'Metaclassed'
    ()
    OrderedDict([   (   '__module__',
                        '__main__'),
                    (   '__qualname__',
                        'Metaclassed'),
                    (   '__init__',
                        <function Metaclassed.__init__ at 0x7faff8f730d0>),
                    (   'one',
                        <function Metaclassed.one at 0x7faff8f73e18>),
                    (   'two',
                        <function Metaclassed.two at 0x7faff8f731e0>),
                    (   'three',
                        <function Metaclassed.three at 0x7faff8f7dc80>),
                    (   'four',
                        <function Metaclassed.four at 0x7faff8f78268>)])
    {}
@Metaclass.__init__
    <class '__main__.Metaclassed'>
    'Metaclassed'
    ()
    OrderedDict([   (   '__module__',
                        '__main__'),
                    (   '__qualname__',
                        'Metaclassed'),
                    (   '__init__',
                        <function Metaclassed.__init__ at 0x7faff8f730d0>),
                    (   'one',
                        <function Metaclassed.one at 0x7faff8f73e18>),
                    (   'two',
                        <function Metaclassed.two at 0x7faff8f731e0>),
                    (   'three',
                        <function Metaclassed.three at 0x7faff8f7dc80>),
                    (   'four',
                        <function Metaclassed.four at 0x7faff8f78268>)])
@Metaclass.__init__
    mappingproxy({   '__dict__': <attribute '__dict__' of 'Metaclassed' objects>,
                     '__doc__': None,
                     '__init__': <function Metaclassed.__init__ at 0x7faff8f730d0>,
                     '__module__': '__main__',
                     '__weakref__': <attribute '__weakref__' of 'Metaclassed' objects>,
                     'four': <function Metaclassed.four at 0x7faff8f78268>,
                     'members': (   '__module__',
                                    '__qualname__',
                                    '__init__',
                                    'one',
                                    'two',
                                    'three',
                                    'four'),
                     'one': <function Metaclassed.one at 0x7faff8f73e18>,
                     'three': <function Metaclassed.three at 0x7faff8f7dc80>,
                     'two': <function Metaclassed.two at 0x7faff8f731e0>})
pretty_print(Metaclassed.__dict__)
mappingproxy({   '__dict__': <attribute '__dict__' of 'Metaclassed' objects>,
                 '__doc__': None,
                 '__init__': <function Metaclassed.__init__ at 0x7faff8f730d0>,
                 '__module__': '__main__',
                 '__weakref__': <attribute '__weakref__' of 'Metaclassed' objects>,
                 'four': <function Metaclassed.four at 0x7faff8f78268>,
                 'members': (   '__module__',
                                '__qualname__',
                                '__init__',
                                'one',
                                'two',
                                'three',
                                'four'),
                 'one': <function Metaclassed.one at 0x7faff8f73e18>,
                 'three': <function Metaclassed.three at 0x7faff8f7dc80>,
                 'two': <function Metaclassed.two at 0x7faff8f731e0>})
class SubMetaclassed(Metaclassed):
    pass
@type.__prepare__
    'SubMetaclassed'
    (<class '__main__.Metaclassed'>,)
    {}
@Metaclass.__new__
    <class '__main__.Metaclass'>
    'SubMetaclassed'
    (<class '__main__.Metaclassed'>,)
    OrderedDict([   (   '__module__',
                        '__main__'),
                    (   '__qualname__',
                        'SubMetaclassed')])
    {}
@Metaclass.__init__
    <class '__main__.SubMetaclassed'>
    'SubMetaclassed'
    (<class '__main__.Metaclassed'>,)
    OrderedDict([   (   '__module__',
                        '__main__'),
                    (   '__qualname__',
                        'SubMetaclassed')])
@Metaclass.__init__
    mappingproxy({   '__doc__': None,
                     '__module__': '__main__',
                     'members': (   '__module__',
                                    '__qualname__')})
print(Metaclassed.members)
('__module__', '__qualname__', '__init__', 'one', 'two', 'three', 'four')
obj = Metaclassed(1, 2, attr1='abc')
@Metaclass.__call__
    <class '__main__.Metaclassed'>
        (1, 2)
    {'attr1': 'abc'}
@Metaclassed.__init__
    <__main__.Metaclassed object at 0x7faff8f0a1d0>
    (1, 2)
    {'attr1': 'abc'}
obj = SubMetaclassed(1, 2, attr1='abc')
@Metaclass.__call__
    <class '__main__.SubMetaclassed'>
    (1, 2)
    {'attr1': 'abc'}
@SubMetaclassed.__init__
    <__main__.SubMetaclassed object at 0x7faff8f0a860>
    (1, 2)
    {'attr1': 'abc'}

3.3.5.10. Customising Instance and Subclass Checks

PEP 3119 - Introducing Abstract Base Classes

class CheckableMetaclass(type):

    def __instancecheck__(self, instance):
        print_method(self, '__init_subclass__', self, instance)
        return super().__instancecheck__(instance)

    def __subclasscheck__(self, subclass):
        print_method(self, '__subclasscheck__', self, subclass)
        return super().__subclasscheck__(subclass)

class CheckableClass(metaclass=CheckableMetaclass):
    pass

class SubCheckableClass(CheckableClass):
    pass

subobj = SubCheckableClass()
print(isinstance(subobj, CheckableClass))
print(isinstance(subobj, SubCheckableClass))
print(issubclass(subobj.__class__, CheckableClass))
@CheckableMetaclass.__init_subclass__
    <class '__main__.CheckableClass'>
    <__main__.SubCheckableClass object at 0x7faff8f01cc0>
True
True
@CheckableMetaclass.__subclasscheck__
    <class '__main__.CheckableClass'>
    <class '__main__.SubCheckableClass'>
True

3.3.5.11. Emulating Callable Objects

class Callable:

    def __call__(self, *args, **kwargs):
        print_method(self, '__call__', args, kwargs)
obj = Callable()
obj(1, 2, attr1='abc') # call Callable.__call__
@Callable.__call__
    (1, 2)
    {'attr1': 'abc'}

3.3.5.12. Emulating Container Types

class Container:

    def __init__(self, *values):
        print_method(self, '__init__', values)
        self._values = list(values) # mutable

    def __len__(self):
        print_method(self, '__len__')
        return len(self._values)

    def __length_hint__(self):
        print_method(self, '__length_hint__')
        # Called to implement operator.length_hint()
        return 10

    def __getitem__(self, key):
        print_method(self, '__getitem__', key)
        return self._values[key] # key can be a slice

    def __missing__(self, key):
        # Called by dict.__getitem__() to implement self[key] for dict subclasses when key is not in the dictionary.
        print_method(self, '__missing__')

    def __setitem__(self, key, value):
        print_method(self, '__setitem__', key, value)
        self._values[key] = value

    def __delitem__(self, key):
        print_method(self, '__delitem__', key)
        del self._values[key]

    def __iter__(self):
        print_method(self, '__iter__')
        return iter(self._values)

    def __reversed__(self):
        print_method(self, '__reversed__')
        return reversed(self._values)

    def __contains__(self, item):
        print_method(self, '__contains__', item)
        return item in self._values
obj = Container(*range(5))
print(obj[1])
obj[4] = 6
print(len(obj))
print(list(obj))
print(list(reversed(obj)))
print(1 in obj)
@Container.__init__
    (0, 1, 2, 3, 4)
@Container.__getitem__
    1
1
@Container.__setitem__
    4
    6
@Container.__len__
5
@Container.__iter__
@Container.__len__
[0, 1, 2, 3, 6]
@Container.__reversed__
[6, 3, 2, 1, 0]
@Container.__contains__
    1
True

3.3.5.13. Emulating Numeric Types

class Numeric:

    # These methods are called to implement the binary arithmetic operations:
    # + - * @ / // % divmod() pow() ** << >> & ^ |

    def __add__(self, other):
        pass

    def __sub__(self, other):
        pass

    def __mul__(self, other):
        pass

    def __matmul__(self, other):
        pass

    def __truediv__(self, other):
        pass

    def __floordiv__(self, other):
        pass

    def __mod__(self, other):
        pass

    def __divmod__(self, other):
        pass

    def __pow__(self, other, modulo):
        pass

    def __lshift__(self, other):
        pass

    def __rshift__(self, other):
        pass

    def __and__(self, other):
        pass

    def __xor__(self, other):
        pass

    def __or__(self, other):
        pass

    # These methods are called to implement the binary arithmetic operations with reflected (swapped) operands:
    # + - * @ / // % divmod() pow() ** << >> & ^ |

    def __radd__(self, other):
        pass

    def __rsub__(self, other):
        pass

    def __rmul__(self, other):
        pass

    def __rmatmul__(self, other):
        pass

    def __rtruediv__(self, other):
        pass

    def __rfloordiv__(self, other):
        pass

    def __rmod__(self, other):
        pass

    def __rdivmod__(self, other):
        pass

    def __rpow__(self, other):
        pass

    def __rlshift__(self, other):
        pass

    def __rrshift__(self, other):
        pass

    def __rand__(self, other):
        pass

    def __rxor__(self, other):
        pass

    def __ror__(self, other):
        pass

    # These methods are called to implement the augmented arithmetic assignments:
    # += -= *= @= /= //= %= **= <<= >>= &= ^= |=

    def __iadd__(self, other):
        pass

    def __isub__(self, other):
        pass

    def __imul__(self, other):
        pass

    def __imatmul__(self, other):
        pass

    def __itruediv__(self, other):
        pass

    def __ifloordiv__(self, other):
        pass

    def __imod__(self, other):
        pass

    def __ipow__(self, other, modulo):
        pass

    def __ilshift__(self, other):
        pass

    def __irshift__(self, other):
        pass

    def __iand__(self, other):
        pass

    def __ixor__(self, other):
        pass

    def __ior__(self, other):
        pass

    # Called to implement the unary arithmetic operations:
    # - + abs() ~

    def __neg__(self):
        pass

    def __pos__(self):
        pass

    def __abs__(self):
        pass

    def __invert__(self):
        pass

    # Called to implement the built-in functions complex(), int(), float() and round():

    def __complex__(self):
        pass

    def __int__(self):
        pass

    def __float__(self):
        pass

    def __round__(self, n):
        pass

    def __index__(self):
        # Called to implement operator.index()
        pass

3.3.5.14. With Statement Context Managers

class ContextManager:

    def __init__(self, *args, **kwargs):
        print_method(self, '__init__', self, args, kwargs)

    def __enter__(self):
        print_method(self, '__enter__', self)
        return 'something' # value for as

    def __exit__(self, exc_type, exc_value, traceback):
        print_method(self, '__exit__', self, exc_type, exc_value, traceback)
with ContextManager(1, 2, attr1='abc') as obj:
    print('here', obj)
@ContextManager.__init__
    <__main__.ContextManager object at 0x7faff8f1bf98>
    (1, 2)
    {'attr1': 'abc'}
@ContextManager.__enter__
    <__main__.ContextManager object at 0x7faff8f1bf98>
here something
@ContextManager.__exit__
    <__main__.ContextManager object at 0x7faff8f1bf98>
    None
    None
    None
try:
    with ContextManager(1, 2, attr1='abc') as obj:
        print('here', obj)
        raise NameError('an error')
except NameError as exception:
    print(exception)
@ContextManager.__init__
    <__main__.ContextManager object at 0x7faff8f0a0f0>
    (1, 2)
    {'attr1': 'abc'}
@ContextManager.__enter__
    <__main__.ContextManager object at 0x7faff8f0a0f0>
here something
@ContextManager.__exit__
    <__main__.ContextManager object at 0x7faff8f0a0f0>
    <class 'NameError'>
    NameError('an error',)
    <traceback object at 0x7fb0010f1988>
an error

3.3.5.15. Coroutines

3.3.5.15.1. Awaitable Objects

# object.__await__(self)

3.3.5.15.2. Coroutine Objects

# coroutine.send(value)
# coroutine.throw(type[, value[, traceback]])
# coroutine.close()

3.3.5.15.3. Asynchronous Iterators

An asynchronous iterable is able to call asynchronous code in its __aiter__ implementation, and an asynchronous iterator can call asynchronous code in its __anext__ method.

class Reader:

    async def readline(self):
        pass

    def __aiter__(self):
        # Must return an asynchronous iterator object.
        return self

    async def __anext__(self):
        # Must return an awaitable resulting in a next value of the iterator.
        # Should raise a StopAsyncIteration error when the iteration is over.
        val = await self.readline()
        if val == b'':
            raise StopAsyncIteration
        return val

3.3.5.15.4. Asynchronous Context Managers

An asynchronous context manager is a context manager that is able to suspend execution in its __aenter__ and __aexit__ methods.

class AsyncContextManager:

    async def __aenter__(self):
        # This method is semantically similar to the __enter__(),
        # with only difference that it must return an awaitable.
        await log('entering context')

    async def __aexit__(self, exc_type, exc, tb):
        # This method is semantically similar to the __exit__(),
        # with only difference that it must return an awaitable.
        await log('exiting context')