Comprendre l'accès aux attributs en Python

Une traduction de l'article a été publiée spécialement pour les futurs étudiants du cours " Python Developer. Professional" .


, , Python ? , Lisp- , , ( Lisp ), Python , .

? , Python ? . -, , Python, / , - . -, , .

, , Python WebAssembly API bare bones C, , .

, , Python. , , , , . CPython , ( CPython 3.8.3, ).

, , , . , , , , , . 

, :

obj.attr

, – . , :

>>> def example(): 
...     obj.attr
... 
>>> import dis
>>> dis.dis(example)
  2           0 LOAD_GLOBAL              0 (obj)
              2 LOAD_ATTR                1 (attr)
              4 POP_TOP
              6 LOAD_CONST               0 (None)
              8 RETURN_VALUE

— LOADATTR. , , conames[i].

CPython Python/ceval.c. switch, . , LOADATTR:

case TARGET(LOAD_ATTR): {
            PyObject *name = GETITEM(names, oparg);
            PyObject *owner = TOP();
            PyObject *res = PyObject_GetAttr(owner, name);
            Py_DECREF(owner);
            SET_TOP(res);
            if (res == NULL)
                goto error;
            DISPATCH();
        }

– , . – PyObject_GetAttr(), .

getattr(), , CPython. Python/bltinmodule.c, Python, , . «getattr» , , «getattr» «builtin_getattr()»

static PyObject *
builtin_getattr(PyObject *self, PyObject *const *args, Py_ssize_t nargs)
{
    PyObject *v, *name, *result;


    if (!_PyArg_CheckPositional("getattr", nargs, 2, 3))
        return NULL;


    v = args[0];
    name = args[1];
    if (!PyUnicode_Check(name)) {
        PyErr_SetString(PyExc_TypeError,
                        "getattr(): attribute name must be string");
        return NULL;
    }
    if (nargs > 2) {
        if (_PyObject_LookupAttr(v, name, &result) == 0) {
            PyObject *dflt = args[2];
            Py_INCREF(dflt);
            return dflt;
        }
    }
    else {
        result = PyObject_GetAttr(v, name);
    }
    return result;
}

, , , , getattr(), PyObjectGetAttr()

? , , «» obj.attr getattr(obj, "attr"). , PyObjectGetAttr(), , , , Python.

getattr()

, , , obj.attr getattr(obj, "attr"). , CPython. , Python , , , , . «.», , , , .

getattr() . -, , , . -, str, TypeError (, , ). 

def getattr(obj: Any, attr: str, default: Any) -> Any:
    if not isinstance(attr, str):
        raise TypeError("getattr(): attribute name must be string")

    ...  # Fill in with PyObject_GetAttr().

getattr()

. – getattribute(), . – getattr(), AttributeError. ( ) , .

Python , . , , «»: – , – . , - type, : type(obj)

(method resolution order, MRO). . , Python Dylan C3. Python MRO type(obj).mro().

, . , , , - . CPython , struct . , , , . 

getattr() getattribute() getattr() , CPython , . , .

# Based on https://github.com/python/cpython/tree/v3.8.3.
from __future__ import annotations
import builtins

NOTHING = builtins.object()  # C: NULL


def getattr(obj: Any, attr: str, default: Any = NOTHING) -> Any:
    """Implement attribute access via  __getattribute__ and __getattr__."""
    # Python/bltinmodule.c:builtin_getattr
    if not isinstance(attr, str):
        raise TypeError("getattr(): attribute name must be string")

    obj_type_mro = type(obj).mro()
    attr_exc = NOTHING
    for base in obj_type_mro:
        if "__getattribute__" in base.__dict__:
            try:
                return base.__dict__["__getattribute__"](obj, attr)
            except AttributeError as exc:
                attr_exc = exc
                break
    # Objects/typeobject.c:slot_tp_getattr_hook
    # It is cheating to do this here as CPython actually rebinds the tp_getattro
    # slot with a wrapper that handles __getattr__() when present.
    for base in obj_type_mro:
        if "__getattr__" in base.__dict__:
            return base.__dict__["__getattr__"](obj, attr)

    if default is not NOTHING:
        return default
    elif attr_exc is not NOTHING:
        raise attr_exc
    else:
        raise AttributeError(f"{self.__name__!r} object has no attribute {attr!r}")

, getattr()

object.getattribute()

, getattr(), , , Python , getattribute() . object.getattribute().

, object.getattribute() – . , – , . , , , Python, , : , classmethod staticmethod – .

: (non-data). get , . set del, . – , classmethod staticmethod — . 

, , – . , dict, .

, , . , , , , , . 

, , , . , :

  • ;

  • ;

  • ;

  • .

, - , , , , , . , - . , self.attr = val init() . , , , - . , , , , , .

def _mro_getattr(type_: Type, attr: str) -> Any:
    """Get an attribute from a type based on its MRO."""
    for base in type_.mro():
        if attr in base.__dict__:
            return base.__dict__[attr]
    else:
        raise AttributeError(f"{type_.__name__!r} object has no attribute {attr!r}")


class object:
    def __getattribute__(self, attr: str, /) -> Any:
        """Attribute access."""
        # Objects/object.c:PyObject_GenericGetAttr
        self_type = type(self)
        if not isinstance(attr, str):
            raise TypeError(
                f"attribute name must be string, not {type(attr).__name__!r}"
            )

        type_attr = descriptor_type_get = NOTHING
        try:
            type_attr = _mro_getattr(self_type, attr)
        except AttributeError:
            pass  # Hopefully an instance attribute.
        else:
            type_attr_type = type(type_attr)
            try:
                descriptor_type_get = _mro_getattr(type_attr_type, "__get__")
            except AttributeError:
                pass  # At least a class attribute.
            else:
                # At least a non-data descriptor.
                for base in type_attr_type.mro():
                    if "__set__" in base.__dict__ or "__delete__" in base.__dict__:
                        # Data descriptor.
                        return descriptor_type_get(type_attr, self, self_type)

        if attr in self.__dict__:
            # Instance attribute.
            return self.__dict__[attr]
        elif descriptor_type_get is not NOTHING:
            # Non-data descriptor.
            return descriptor_type_get(type_attr, self, self_type)
        elif type_attr is not NOTHING:
            # Class attribute.
            return type_attr
        else:
            raise AttributeError(f"{self.__name__!r} object has no attribute {attr!r}")

object.getattribute()

, Python . , , , . Python, , .

, Python , «». Python 3, , , , .

«syntactic sugar» . .


"Python Developer. Professional".




All Articles