[Tutor] Borg di borg di borg (or: Swedish chef)

eryksun eryksun at gmail.com
Wed Sep 26 15:46:37 CEST 2012


On Wed, Sep 26, 2012 at 3:56 AM, Albert-Jan Roskam <fomcl at yahoo.com> wrote:
>
> I've never really gotten the hang of decorators, but property() seems
> useful.


property is a built-in type that can work as a decorator.

    property(fget=None, fset=None, fdel=None, doc=None)

When used as a decorator, it assigns the decorated function and its
docstring to fget and doc. The property itself has two decorators,
setter and deleter, for assigning fset and fdel:

    @property
    def prop(self): "the prop"

    @prop.setter
    def prop(self, value): pass

    @prop.deleter
    def prop(self): pass

All three functions have the same name to keep the class namespace uncluttered.


> Decorators always seem so Python-specific; I think I intuitively
> prefer a solution that is more language-agnostic.


A decorator is Python-specific 'syntactic sugar' for wrapping a
function around another function:

    def f():
        pass

    f = decorator(f)

Other decorators:

staticmethod, classmethod
abc.abstractmethod, abc.abstractproperty
functools.wraps, functools.total_ordering (classes)

The really Python-specific paradigm is the descriptor protocol at the
heart of methods and properties in Python's object system. Here's
Raymond Hettinger's intro to descriptors:

http://users.rcn.com/python/download/Descriptor.htm


> Regarding the Borg: doesn't this version (a dictionary with another
> dictionary as its key) defeat its purpose? Isn't it "we are one" instead
> of "we are several" (one of each subclass). Btw, I subclassed from Borg
> to make it more explicit I was using the Borg pattern.


It's the instances that "are one" in the Borg collective, in contrast
to the singleton/Highlander for which "there can be only one"
instance. It would be a mess to have multiple Borg subclasses with all
instances of all classes sharing the same dict.

With the property example I gave, you can get the dict for Generic
instances via Generic._all_state[Generic] or Borg._all_state[Generic].
This is awkward. Using a property was just an example, and not a good
one at that.

I could have just set _state as a class attribute in __init__ without
using a property at all:


    class Borg(object):

        def __init__(self):
            if "_state" not in self.__class__.__dict__:
                self.__class__._state = {}
            self.__dict__ = self._state


This has to directly check the class __dict__ instead of using
"hasattr" because the latter would also search the base classes.

Another method is to use a metaclass to set _state when the class
object itself gets created:


    class MetaBorg(type):
        def __new__(mcls, name, bases, dikt):
            dikt["_state"] = {}
            return type.__new__(mcls, name, bases, dikt)

    class Borg(object):
        __metaclass__ = MetaBorg

        def __init__(self):
            self.__dict__ = self._state


New-style class objects are instances of a metaclass. The base
metaclass is 'type'. Ponder this. 'object' is an instance of 'object';
'type' is an instance of 'type'; 'object' is an instance of 'type';
'type' is an instance of 'object'; and 'type' is a subclass of
'object'.  New-style classes are subclasses of 'object' and instances
of both 'type' and 'object'. Metaclasses are subclasses of 'type'.

When a class is defined, first the body of the class is executed to
define the namespace in the dict I've called "dikt". Then Python calls
the metaclass (it's either 'type' or __metaclass__ if defined). The
arguments are the name of the class (a string), its bases as a tuple
(e.g. (object,)), and the dict. If you override type.__new__ in a
custom metaclass you can hack the returned class object almost anyway
you want. You can also override type.__call__ to intercept arguments
when the class object is called:


    class Meta(type):

        def __call__(cls, *args, **kwds):
            print "__call__"
            return type.__call__(cls, *args, **kwds)

    class Test(object):
        __metaclass__ = Meta

    >>> Test()
    __call__
    <__main__.Test object at 0xb732ef8c>


On the downside, there's the problem that now you have two class
definitions to maintain. Also, it complicates cooperative inheritance
(e.g. mixin classes). The metaclass of the derived class has to be a
non-strict subclass of the metaclass of all base classes. Plus it's
quite possibly confusing...


More information about the Tutor mailing list