instance/class methods: having my cake and eating it too

Thomas Heller theller at python.net
Thu Aug 29 08:51:15 EDT 2002


"Donnal Walter" <donnal at donnal.net> wrote in message news:918bc22f.0208290328.30e8b2d1 at posting.google.com...
> To enforce 'encapsulation' of a class attribute, I would like to
> define a class method that sets that class attribute (format):
>
> class Decimal:
>
>     format = '%.0f'   # default format is for integer printing
>
>     def GetFormat(self):
>         return self.format
>
>     def SeeFormat(self):
>         print self.GetFormat()
>
>     def SetDigits(cls, digits):
>         digits = min(digits, 5)
>         cls.format = "%%.%uf" % digits
>
>     SetDigits = classmethod(SetDigits)
>
> class Weight(Decimal):
>
>     Decimal.SetDigits(3)
>
> >>> x = Weight()
> >>> x.SeeFormat()
> %.3f
>
> But I also want to be able to override this class attribute with an
> instance attribute that is set using an instance method:
>
>     def InstanceSetDigits(self, digits):
>         digits = min(digits, 5)
>         self.format = "%%.%uf" % digits
>
> class MyContainer:
>
>     def __init__(self):
>         self.wt = Weight()
>         self.wt.InstanceSetDigits(2)
>
> >>> y = MyContainer()
> >>> y.wt.SeeFormat()
> %.2f
>
> This works, but having both SetDigits and InstanceSetDigits is
> redundant code and makes for a confusing API. Is there any way I can
> define ONE method that works like a class method when called from the
> base class object and like an instance method when called from an
> instance?

Sure.
Classes deriving from object can completely customize
the binding process by implementing a __get__ method:

class _BoundMethod:
    # Helper class.
    def __init__(self, func, first):
        self.func = func
        self.first = first

    def __call__(self, *args):
        return self.func(self.first, *args)

class unimethod(object):
    # universal method: binds to either a class or an instance
    def __init__(self, func):
        self.func = func

    def __get__(self, inst, type=None):
        if inst is None:
            # bind to the class
            return _BoundMethod(self.func, type)
        else:
            # bind to the instance
            return _BoundMethod(self.func, inst)

Then you can do:

class Decimal:
    ...
    def SetDigits(cls_or_inst, ...):
        ....

    SetDigits = unimethod(SetDigits)

-----

Thomas





More information about the Python-list mailing list