内建属性
===========

__name__
-----------------

``__name__`` 标识模块的名字,可以显示一个模块的某功能是 **被自己执行还是被别的文件调用执行** 。

如果被自己执行,那么 ``__name__== "__main__"`` ;

如果被调用执行,那么 ``__name__`` 值为被调用模块的名字。


__doc__
-----------------

``__doc__`` 是模块的文档字符串(docstring),是由三引号包围的字符串,定义在文件/类/函数的头部。

定义 **Module.py** 内容如下:

.. code-block:: python
    :linenos:

    """ An example module."""

    class A(object):
        """
        class A.
        """
        pass

    def func():
        """ function f. """
        pass

.. code-block:: python
    :linenos:

    >>> import Module
    >>> print(Module.__doc__)
    An example module.
    >>> print(Module.A.__doc__) 

        class A.
                
    >>> print(Module.func.__doc__) 

        function f.
        

__call__
-----------------

如果在类中实现了 ``__call__`` 方法,那么实例对象将成为一个可调用对象。自定义函数、内建函数和类都属于可调用对象,但凡是可以把一对括号 ``()`` 应用到某个对象身上都可称之为可调用对象,判断对象是否为可调用对象可以用函数 ``callable`` 。

.. code-block:: python
    :linenos:

    class A(object):
        def __init__(self, a):
            self._a = a
        
        def __call__(self, b):
            return self._a * b

.. code-block:: python
    :linenos:

    >>> obj = A(5) 
    >>> callable(A)
    True
    >>> callable(obj) 
    True
    >>> obj(10)
    50


__dict__
-----------------

``__dict__`` 存储了类和实例的一些属性。

在 ``__init__`` 中声明的变量( ``self.xxx`` ),会存到实例的 ``__dict__`` 中;类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的 ``__dict__`` 中。


.. code-block:: python
    :linenos:

    class A(object):
    """ class A. """
        Aa = 10
        def __init__(self, a):
            self._a = a
        
        def __call__(self, b):
            return self._a * b

.. code-block:: python
    :linenos:

    >>> from Module import *
    >>> A.__dict__
    mappingproxy({'__module__': 'Module', '__doc__': ' class A. ', 'Aa': 10, '__init__': <function A.__init__ at 0x000001F1D9E2CEE0>, '__call__': <function A.__call__ at 0x000001F1D9E2CF70>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>})
    >>> obj = A(0)
    >>> obj.__dict__
    {'_a': 0}

.. code-block:: python
    :linenos:

    class A(object):
        Aa = 10
        def __init__(self, **kwargs):
            self.__dict__.update(kwargs)
        
        def func(self, b):
            pass

.. code-block:: python
    :linenos:

    >>> kwargs = {"a":1, "b":2, "c": 3}
    >>> obj = A(**kwargs) 
    >>> obj.__dict__
    {'a': 1, 'b': 2, 'c': 3}
    >>> obj.c
    3
    >>> A.__dict__
    mappingproxy({'__module__': 'Module', 'Aa': 10, '__init__': <function A.__init__ at 0x0000024E2C82CEE0>, 'func': <function A.func at 0x0000024E2C82CF70>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None})


__setattr__
-----------------

默认情况下,实例属性赋值,被赋值的属性和值会存入实例属性字典 ``__dict__`` 中。

如果类自定义了 ``__setattr__`` 方法,当通过实例获取属性(instance.attr)并尝试赋值时,就会调用 ``__setattr__`` 。

.. code-block:: python
    :linenos:

    class A(object):
        def __init__(self, a):
            self._a = a

.. code-block:: python
    :linenos:

    >>> obj = A(0)
    >>> obj._b = 1
    >>> obj.__dict__
    {'_a': 0, '_b': 1}

.. code-block:: python
    :linenos:

    class A(object):
        def __init__(self, a):
            self._a = a
        def __setattr__(self, name, value):
            print("set attr.", name)
            self.__dict__[name] = value


.. code-block:: python
    :linenos:

    >>> obj = A(0) ## 初始化会调用 __setattr__
    set attr. _a
    >>> obj._b = 1
    set attr. _b
    >>> obj.__dict__
    {'_a': 0, '_b': 1}

.. warning::

    如果在 ``__setattr__`` 中试图通过  ``self.xxx`` 来访问其他属性,容易出现错误。比如,初始化之前, ``__dict__`` 中还没有插入属性,是无法访问的。

__getattr__
-----------------

实例通过 **点号** 访问属性(instance.attr),只有当属性没有在实例的 ``__dict__`` 或类的 ``__dict__`` 中没有找到,才会调用 ``__getattr__`` 。当属性可以通过正常机制追溯到时,``__getattr__`` 是不会被调用的。

.. code-block:: python
    :linenos:

    class A(object):
        def __init__(self, a):
            self._a = a
            self.dic = {"_b": 1, "_c": 2}

        def __getattr__(self, attr):
            print("get attr.", attr)
            if attr in self.dic:
                return self.dic[attr]
            return -1

.. code-block:: python
    :linenos:

    >>> obj = A(0)
    >>> obj._a
    0
    >>> obj._b
    get attr._b
    1

.. note::

    多进程 multiprocessing 为了实现数据共享,会对数据进行序列化(pickling),其他进程读的时候再反序列化(unpickling)。反序列化时,数据所对应的类的 ``__init__`` 方法不会被调用,因而上例中 ``self.dic`` 对执行反序列化的进程是不可见的,会造成 ``__getattr__`` 无限循环调用::

        >>> import pickle
        >>> obj = A(0)
        >>> new_obj = pickle.loads(pickle.dumps(obj))
        ...
        RecursionError: maximum recursion depth exceeded while calling a Python object.

    解决方法:直接用 ``self.__dict__`` 来存放原本 ``self.dic`` 的数据。
    参考:https://stackoverflow.com/questions/62331047/why-am-i-receiving-a-recursion-error-with-multiprocessing

__getattribute__
-------------------------

实例通过 **点号** 访问属性(instance.attr), ``__getattribute__`` 方法始终会被调用,无论属性是否能通过 ``__dict__`` 追溯到。如果类还定义了 ``__getattr__`` 方法,除非它被 ``__getattribute__`` 显式调用,或者 ``__getattribute__`` 方法出现 ``AttributeError`` 异常,否则 ``__getattr__`` 方法永远不会被调用。

.. code-block:: python
    :linenos:

    class A(object):
        def __init__(self, a):
            self._a = a
            self.dic = {"_b": 1, "_c": 2}

        def __getattribute__(self, attribute):
            if attribute == "_a":
                return -1
            else:
                raise AttributeError("no attribute {}".format(attribute))

        def __getattr__(self, attr):
            print("get attr.", attr)
            return 2

.. code-block:: python
    :linenos:

    >>> obj =A(0)
    >>> obj._a
    -1
    >>> obj._b
    get attr. _b
    2


.. warning::

    在抛出 ``AttributeError`` 异常之后,如果此时在 ``__getattr__`` 中试图通过  ``self.xxx`` 来访问其他属性(如 ``self.dic`` )时,会出现死循环。


参考资料
-----------------

1. Python __dict__属性详解

  https://www.cnblogs.com/alvin2010/p/9102344.html

2. python 自定义属性访问 __setattr__, __getattr__,__getattribute__, __call__

  https://www.cnblogs.com/elie/p/6685429.html

3. Python中__setattr__, __getattr__和__getattribute__

  https://www.jianshu.com/p/0beee5a49b90