Variants of getattr() - Python technicalities

Skip to content
Back to top
View this page
Edit this page

# Variants of getattr()

class sage.cpython.getattr.AttributeErrorMessage

  Bases: `object`

  Try to emulate the standard Python `AttributeError` message.

  Note

  The typical fate of an attribute error is being caught. Hence, under normal circumstances, nobody will ever see the error message. The idea for this class is to provide an object that is fast to create and whose string representation is an attribute error’s message. That string representation is only created if someone wants to see it.

  EXAMPLES:

  Sage

      sage: 1.bla  #indirect doctest
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute 'bla'...
      sage: x = polygen(ZZ, 'x')
      sage: QQ[x].gen().bla                                                           # needs sage.libs.flint
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint' object has no attribute 'bla'...

  Python

      >>> from sage.all import *
      >>> Integer(1).bla  #indirect doctest
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute 'bla'...
      >>> x = polygen(ZZ, 'x')
      >>> QQ[x].gen().bla                                                           # needs sage.libs.flint
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.polynomial.polynomial_rational_flint.Polynomial_rational_flint' object has no attribute 'bla'...

  Sage

      sage: from sage.cpython.getattr import AttributeErrorMessage
      sage: AttributeErrorMessage(int(1), 'bla')
      'int' object has no attribute 'bla'

  [Python]

      >>> from sage.all import *
      >>> from sage.cpython.getattr import AttributeErrorMessage
      >>> AttributeErrorMessage(int(Integer(1)), 'bla')
      'int' object has no attribute 'bla'

  AUTHOR:

  - Simon King (2011-05-21)

  cls
  name

sage.cpython.getattr.dir_with_other_class(self, *cls)

  Emulates `dir(self)`, as if `self` was also an instance `cls`, right after `caller_class` in the method resolution order (`self.__class__.mro()`)

  EXAMPLES:

  Sage

      sage: class A():
      ....:    a = 1
      ....:    b = 2
      ....:    c = 3
      sage: class B():
      ....:    b = 2
      ....:    c = 3
      ....:    d = 4
      sage: x = A()
      sage: x.c = 1; x.e = 1
      sage: from sage.cpython.getattr import dir_with_other_class
      sage: dir_with_other_class(x, B)
      [..., 'a', 'b', 'c', 'd', 'e']
      sage: class C():
      ....:    f = 6
      sage: dir_with_other_class(x, B, C)
      [..., 'a', 'b', 'c', 'd', 'e', 'f']

  Python

      >>> from sage.all import *
      >>> class A():
      ...    a = Integer(1)
      ...    b = Integer(2)
      ...    c = Integer(3)
      >>> class B():
      ...    b = Integer(2)
      ...    c = Integer(3)
      ...    d = Integer(4)
      >>> x = A()
      >>> x.c = Integer(1); x.e = Integer(1)
      >>> from sage.cpython.getattr import dir_with_other_class
      >>> dir_with_other_class(x, B)
      [..., 'a', 'b', 'c', 'd', 'e']
      >>> class C():
      ...    f = Integer(6)
      >>> dir_with_other_class(x, B, C)
      [..., 'a', 'b', 'c', 'd', 'e', 'f']

  Check that objects without dicts are well handled:

  Sage

      sage: # needs sage.misc.cython
      sage: cython("cdef class A:\n    cdef public int a")
      sage: cython("cdef class B:\n    cdef public int b")
      sage: x = A()
      sage: x.a = 1
      sage: hasattr(x,'__dict__')
      False
      sage: dir_with_other_class(x, B)
      [..., 'a', 'b']

  [Python]

      >>> from sage.all import *
      >>> # needs sage.misc.cython
      >>> cython("cdef class A:\n    cdef public int a")
      >>> cython("cdef class B:\n    cdef public int b")
      >>> x = A()
      >>> x.a = Integer(1)
      >>> hasattr(x,'__dict__')
      False
      >>> dir_with_other_class(x, B)
      [..., 'a', 'b']

sage.cpython.getattr.getattr_from_other_class(self, cls, name)

  Emulate `getattr(self, name)`, as if `self` was an instance of `cls`.

  INPUT:

  - `self` – some object

  - `cls` – a new-style class

  - `name` – string

  If `self` is an instance of cls, raises an `AttributeError`, to avoid a double lookup. This function is intended to be called from __getattr__, and so should not be called if name is an attribute of `self`.

  EXAMPLES:

  Sage

      sage: from sage.cpython.getattr import getattr_from_other_class
      sage: class A():
      ....:      def inc(self):
      ....:          return self + 1
      ....:
      ....:      @staticmethod
      ....:      def greeting():
      ....:          print("Hello World!")
      ....:
      ....:      @lazy_attribute
      ....:      def lazy_attribute(self):
      ....:          return repr(self)
      sage: getattr_from_other_class(1, A, "inc")
      <bound method A.inc of 1>
      sage: getattr_from_other_class(1, A, "inc")()
      2

  Python

      >>> from sage.all import *
      >>> from sage.cpython.getattr import getattr_from_other_class
      >>> class A():
      ...      def inc(self):
      ...          return self + Integer(1)
      ....:
      >>>      @staticmethod
      ...      def greeting():
      ...          print("Hello World!")
      ....:
      >>>      @lazy_attribute
      ...      def lazy_attribute(self):
      ...          return repr(self)
      >>> getattr_from_other_class(Integer(1), A, "inc")
      <bound method A.inc of 1>
      >>> getattr_from_other_class(Integer(1), A, "inc")()
      2

  Static methods work:

  Sage

      sage: getattr_from_other_class(1, A, "greeting")()
      Hello World!

  [Python]

      >>> from sage.all import *
      >>> getattr_from_other_class(Integer(1), A, "greeting")()
      Hello World!

  Caveat: lazy attributes work with extension types only if they allow attribute assignment or have a public attribute `_cached_methods` of type `<dict>`. This condition is satisfied, e.g., by any class that is derived from `Parent`:

  Sage

      sage: getattr_from_other_class(1, A, "lazy_attribute")
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute 'lazy_attribute'...

  Python

      >>> from sage.all import *
      >>> getattr_from_other_class(Integer(1), A, "lazy_attribute")
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute 'lazy_attribute'...

  The integer ring is a parent, so, lazy attributes work:

  Sage

      sage: getattr_from_other_class(ZZ, A, "lazy_attribute")
      'Integer Ring'
      sage: getattr_from_other_class(PolynomialRing(QQ, name='x', sparse=True).one(), A, "lazy_attribute")
      '1'
      sage: getattr_from_other_class(17, A, "lazy_attribute")
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute 'lazy_attribute'...

  [Python]

      >>> from sage.all import *
      >>> getattr_from_other_class(ZZ, A, "lazy_attribute")
      'Integer Ring'
      >>> getattr_from_other_class(PolynomialRing(QQ, name='x', sparse=True).one(), A, "lazy_attribute")
      '1'
      >>> getattr_from_other_class(Integer(17), A, "lazy_attribute")
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute 'lazy_attribute'...

  In general, descriptors are not yet well supported, because they often do not accept to be cheated with the type of their instance:

  Sage

      sage: A.__weakref__.__get__(1)
      Traceback (most recent call last):
      ...
      TypeError: descriptor '__weakref__' for 'A' objects doesn't apply
      to ...'sage.rings.integer.Integer' object

  Python

      >>> from sage.all import *
      >>> A.__weakref__.__get__(Integer(1))
      Traceback (most recent call last):
      ...
      TypeError: descriptor '__weakref__' for 'A' objects doesn't apply
      to ...'sage.rings.integer.Integer' object

  When this occurs, an `AttributeError` is raised:

  Sage

      sage: getattr_from_other_class(1, A, "__weakref__")
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute '__weakref__'...

  [Python]

      >>> from sage.all import *
      >>> getattr_from_other_class(Integer(1), A, "__weakref__")
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute '__weakref__'...

  This was caught by Issue #8296 for which we do a couple more tests:

  Sage

      sage: "__weakref__" in dir(A)
      True
      sage: 1.__weakref__
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute '__weakref__'...

      sage: n = 1
      sage: ip = get_ipython()                 # not tested: only works in interactive shell
      sage: ip.magic_psearch('n.N')            # not tested: only works in interactive shell
      n.N
      sage: ip.magic_psearch('n.__weakref__')  # not tested: only works in interactive shell

  Python

      >>> from sage.all import *
      >>> "__weakref__" in dir(A)
      True
      >>> Integer(1).__weakref__
      Traceback (most recent call last):
      ...
      AttributeError: 'sage.rings.integer.Integer' object has no attribute '__weakref__'...

      >>> n = Integer(1)
      >>> ip = get_ipython()                 # not tested: only works in interactive shell
      >>> ip.magic_psearch('n.N')            # not tested: only works in interactive shell
      n.N
      >>> ip.magic_psearch('n.__weakref__')  # not tested: only works in interactive shell

  Caveat: When __call__ is not defined for instances, using `A.__call__` yields the method `__call__` of the class. We use a workaround but there is no guarantee for robustness.

  > sage: getattr_from_other_class(1, A, “__call__”) Traceback (most recent call last): … AttributeError: ‘sage.rings.integer.Integer’ object has no attribute ‘__call__’…

sage.cpython.getattr.raw_getattr(obj, name)

  Like `getattr(obj, name)` but without invoking the binding behavior of descriptors under normal attribute access. This can be used to easily get unbound methods or other descriptors.

  This ignores `__getattribute__` hooks but it does support `__getattr__`.

  Note

  For Cython classes, `__getattr__` is actually implemented as `__getattribute__`, which means that it is not supported by `raw_getattr`.

  EXAMPLES:

  Sage

      sage: class X:
      ....:     @property
      ....:     def prop(self):
      ....:         return 42
      ....:     def method(self):
      ....:         pass
      ....:     def __getattr__(self, name):
      ....:         return "magic " + name
      sage: raw_getattr(X, "prop")
      <property object at ...>
      sage: raw_getattr(X, "method")
      <function ...method at ...>
      sage: raw_getattr(X, "attr")
      Traceback (most recent call last):
      ...
      AttributeError: '...' object has no attribute 'attr'...
      sage: x = X()
      sage: raw_getattr(x, "prop")
      <property object at ...>
      sage: raw_getattr(x, "method")
      <function ...method at ...>
      sage: raw_getattr(x, "attr")
      'magic attr'
      sage: x.__dict__["prop"] = 'no'
      sage: x.__dict__["method"] = 'yes'
      sage: x.__dict__["attr"] = 'ok'
      sage: raw_getattr(x, "prop")
      <property object at ...>
      sage: raw_getattr(x, "method")
      'yes'
      sage: raw_getattr(x, "attr")
      'ok'

  Python

      >>> from sage.all import *
      >>> class X:
      ...     @property
      ...     def prop(self):
      ...         return Integer(42)
      ...     def method(self):
      ...         pass
      ...     def __getattr__(self, name):
      ...         return "magic " + name
      >>> raw_getattr(X, "prop")
      <property object at ...>
      >>> raw_getattr(X, "method")
      <function ...method at ...>
      >>> raw_getattr(X, "attr")
      Traceback (most recent call last):
      ...
      AttributeError: '...' object has no attribute 'attr'...
      >>> x = X()
      >>> raw_getattr(x, "prop")
      <property object at ...>
      >>> raw_getattr(x, "method")
      <function ...method at ...>
      >>> raw_getattr(x, "attr")
      'magic attr'
      >>> x.__dict__["prop"] = 'no'
      >>> x.__dict__["method"] = 'yes'
      >>> x.__dict__["attr"] = 'ok'
      >>> raw_getattr(x, "prop")
      <property object at ...>
      >>> raw_getattr(x, "method")
      'yes'
      >>> raw_getattr(x, "attr")
      'ok'

  The same tests with an inherited new-style class:

  Sage

      sage: class Y(X, object):
      ....:     pass
      sage: raw_getattr(Y, "prop")
      <property object at ...>
      sage: raw_getattr(Y, "method")
      <function ...method at ...>
      sage: raw_getattr(Y, "attr")
      Traceback (most recent call last):
      ...
      AttributeError: '...' object has no attribute 'attr'...
      sage: y = Y()
      sage: raw_getattr(y, "prop")
      <property object at ...>
      sage: raw_getattr(y, "method")
      <function ...method at ...>
      sage: raw_getattr(y, "attr")
      'magic attr'
      sage: y.__dict__["prop"] = 'no'
      sage: y.__dict__["method"] = 'yes'
      sage: y.__dict__["attr"] = 'ok'
      sage: raw_getattr(y, "prop")
      <property object at ...>
      sage: raw_getattr(y, "method")
      'yes'
      sage: raw_getattr(y, "attr")
      'ok'

  [Python]

      >>> from sage.all import *
      >>> class Y(X, object):
      ...     pass
      >>> raw_getattr(Y, "prop")
      <property object at ...>
      >>> raw_getattr(Y, "method")
      <function ...method at ...>
      >>> raw_getattr(Y, "attr")
      Traceback (most recent call last):
      ...
      AttributeError: '...' object has no attribute 'attr'...
      >>> y = Y()
      >>> raw_getattr(y, "prop")
      <property object at ...>
      >>> raw_getattr(y, "method")
      <function ...method at ...>
      >>> raw_getattr(y, "attr")
      'magic attr'
      >>> y.__dict__["prop"] = 'no'
      >>> y.__dict__["method"] = 'yes'
      >>> y.__dict__["attr"] = 'ok'
      >>> raw_getattr(y, "prop")
      <property object at ...>
      >>> raw_getattr(y, "method")
      'yes'
      >>> raw_getattr(y, "attr")
      'ok'