Acquisition

Copyright © 1996-1998, Digital Creations.

Acquisition [1] is a mechanism that allows objects to obtain attributes from their environment. It is similar to inheritence, except that, rather than traversing an inheritence hierarchy to obtain attributes, a containment hierarchy is traversed.

The ExtensionClass. release includes mix-in extension base classes that can be used to add acquisition as a feature to extension subclasses. These mix-in classes use the context-wrapping feature of ExtensionClasses to implement acquisition. Consider the following example:

    import ExtensionClass, Acquisition

    class C(ExtensionClass.Base):
      color='red'

    class A(Acquisition.Implicit):

      def report(self):
        print self.color

    a=A()
    c=C()
    c.a=A()

    c.a.report() # prints 'red'

    d=C()
    d.color='green'
    d.a=a

    d.a.report() # prints 'green'

    a.report() # raises an attribute error

The class A inherits acquisition behavior from Acquisition.Implicit. The object, a, "has" the color of objects c and d when it is accessed through them, but it has no color by itself. The object a obtains attributes from it's environment, where it's environment is defined by the access path used to reach a.

Acquisition wrappers

When an object that supports acquisition is accessed through an extension class instance, a special object, called an acquisition wrapper, is returned. In the example above, the expression c.a returns an acquisition wrapper that contains references to both c and a. It is this wrapper that performs attribute lookup in c when an attribute cannot be found in a.

Aquisition wrappers provide access to the wrapped objects through the attributes aq_parent, aq_self, aq_base. In the example above, the expressions:

       'c.a.aq_parent is c'

and:
       'c.a.aq_self is a'

both evaluate to true, but the expression:
       'c.a is a'

evaluates to false, because the expression c.a evaluates to an acquisition wrapper around c and a, not a itself.

The attribute aq_base is similar to aq_self. Wrappers may be nested and aq_self may be a wrapped object. The aq_base attribute is the underlying object with all wrappers removed.

Acquisition Control

Two styles of acquisition are supported in the current ExtensionClass release, implicit and explicit aquisition.

Implicit acquisition

Implicit acquisition is so named because it searches for attributes from the environment automatically whenever an attribute cannot be obtained directly from an object or through inheritence.

An attribute may be implicitly acquired if it's name does not begin with an underscore, _.

To support implicit acquisition, an object should inherit from the mix-in class Acquisition.Implicit.

Explicit Acquisition

When explicit acquisition is used, attributes are not automatically obtained from the environment. Instead, the method aq_aquire must be used, as in:
        print c.a.aq_acquire('color')

To support explicit acquisition, an object should inherit from the mix-in class Acquisition.Explicit.

Controlled Acquisition

A class (or instance) can provide attribute by attribute control over acquisition. This is done by:

Filtered Acquisition

The acquisition method, aq_acquire, accepts two optional arguments. The first of the additional arguments is a "filtering" function that is used when considering whether to acquire an object. The second of the additional arguments is an object that is passed as extra data when calling the filtering function and which defaults to None.

The filter function is called with five arguments:

If the filter returns a true object that the object found is returned, otherwise, the acquisition search continues.

For example, in:

        from Acquisition import Explicit

        class HandyForTesting:
            def __init__(self, name): self.name=name
            def __str__(self):
                return "%s(%s)" % (self.name, self.__class__.__name__)
            __repr__=__str__

        class E(Explicit, HandyForTesting): pass

        class Nice(HandyForTesting):
            isNice=1
            def __str__(self):
                return HandyForTesting.__str__(self)+' and I am nice!'
            __repr__=__str__

        a=E('a')
        a.b=E('b')
        a.b.c=E('c')
        a.p=Nice('spam')
        a.b.p=E('p')

        def find_nice(self, ancestor, name, object, extra):
            return hasattr(object,'isNice') and object.isNice

        print a.b.c.aq_acquire('p', find_nice)

The filtered acquisition in the last line skips over the first attribute it finds with the name p, because the attribute doesn't satisfy the condition given in the filter. The output of the last line is:
        spam(Nice) and I am nice!

Acquisition and methods

Python methods of objects that support acquisition can use acquired attributes as in the report method of the first example above. When a Python method is called on an object that is wrapped by an acquisition wrapper, the wrapper is passed to the method as the first argument. This rule also applies to user-defined method types and to C methods defined in pure mix-in classes.

Unfortunately, C methods defined in extension base classes that define their own data structures, cannot use aquired attributes at this time. This is because wrapper objects do not conform to the data structures expected by these methods.

Acquiring Acquiring objects

Consider the following example:
      from Acquisition import Implicit

      class C(Implicit):
          def __init__(self, name): self.name=name
          def __str__(self):
              return "%s(%s)" % (self.name, self.__class__.__name__)
          __repr__=__str__

      a=C("a")
      a.b=C("b")
      a.b.pref="spam"
      a.b.c=C("c")
      a.b.c.color="red"
      a.b.c.pref="eggs"
      a.x=C("x")

      o=a.b.c.x

The expression o.color might be expected to return "red". In earlier versions of ExtensionClass, however, this expression failed. Acquired acquiring objects did not acquire from the environment they were accessed in, because objects were only wrapped when they were first found, and were not rewrapped as they were passed down the acquisition tree.

In the current release of ExtensionClass, the expression "o.color" does indeed return "red".

When searching for an attribute in o, objects are searched in the order x, a, b, c. So, for example, the expression, o.pref returns "spam", not "eggs". In earlier releases of ExtensionClass, the attempt to get the pref attribute from o would have failed.

If desired, the current rules for looking up attributes in complex expressions can best be understood through repeated application of the __of__ method:

a.x


x.__of__(a)

a.b


b.__of__(a)

a.b.x


x.__of__(a).__of__(b.__of__(a))

a.b.c


c.__of__(b.__of__(a))

a.b.c.x


x.__of__(a).__of__(b.__of__(a)).__of__(c.__of__(b.__of__(a)))

and by keeping in mind that attribute lookup in a wrapper is done by trying to lookup the attribute in the wrapped object first and then in the parent object. In the expressions above involving the __of__ method, lookup proceeds from left to right.

Note that heuristics are used to avoid most of the repeated lookups. For example, in the expression: a.b.c.x.foo, the object a is searched no more than once, even though it is wrapped three times.

[1] Gil, J., Lorenz, D., Environmental Acquisition--A New Inheritance-Like Abstraction Mechanism OOPSLA '96 Proceedings, ACM SIG-PLAN, October, 1996