__set__ method is not called for class attribute access

Discussion in 'Python' started by Ryan, Aug 5, 2011.

  1. Ryan

    Ryan Guest

    In the context of descriptors, the __set__ method is not called for
    class attribute access. __set__ is only
    called to set the attribute on an instance instance of the owner class
    to a new value, value. WHY? Is there some other mechanism for
    accomplishing this outcome. This subtle difference from __get__cost me
    some time to track down. Might think about pointing that out the

    class RevealAccess(object):
    """A data descriptor that sets and returns values
    normally and prints a message logging their access.

    def __init__(self, initval=None, name='var'):
    self.val = initval
    self.name = name

    def __get__(self, obj, objtype):
    print 'Retrieving', self.name
    return self.val

    def __set__(self, obj, val):
    print 'Updating' , self.name
    self.val = val

    class MyClass(object):
    x = RevealAccess(10, 'var "x"')
    y = 5

    print MyClass.x
    MyClass.x = 20
    print MyClass.x
    MyClass.x = 30
    print MyClass.x

    Retrieving var "x"

    I am at a lost on how to intercept class attribute sets. Can anyone
    help :-/

    Ryan, Aug 5, 2011
    1. Advertisements

  2. Ryan

    Peter Otten Guest

    A class is just an instance of its metaclass, so you could move the
    descriptor into the metaclass to see the expected behaviour in the class:
    .... class __metaclass__(type):
    .... x = RevealAccess(10, 'var "x"')
    ....Retrieving var "x"

    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    AttributeError: 'A' object has no attribute 'x'

    So you'd need two descriptors if you want to intercept variable access on
    both the instance and class level.
    Peter Otten, Aug 5, 2011
    1. Advertisements

  3. Ryan

    Peter Otten Guest

    Unlike normal class attributes a descriptor is not shaded by an instance
    .... def set_x(self, value): self.__dict__["x"] = value/2.0
    .... def get_x(self): return self.__dict__["x"] * 2.0
    .... x = property(get_x, set_x)
    Peter Otten, Aug 5, 2011
  4. Ryan

    Fuzzyman Guest

    It's an unfortunate asymmetry in the descriptor protocol. You can
    write "class descriptors" with behaviour on get, but not on set.

    As others point out, metaclasses are an ugly way round this
    (particularly given that *basically* a class can only have one
    metaclass - so if you're inheriting from a class that already has a
    custom metaclass you can't use this technique).

    Michael Foord
    Fuzzyman, Aug 10, 2011
  5. Ryan

    Fuzzyman Guest

    That's not true. Properties, for example, can be got or set even when
    they shadow an instance attribute. You're (probably) thinking of
    __getattr__ which isn't invoked when an instance attribute exists.

    Also, the descriptor protocol *is* invoked for getting an attribute
    from a class - but not when setting a class attribute. An unfortunate
    asymmetry. It just wasn't considered as a use case when the descriptor
    protocol was implemented.

    Fuzzyman, Aug 10, 2011
  6. Ryan

    Eric Snow Guest

    Keep in mind that you can still do something like this:

    class XMeta(Base.__class__):
    "Customize here"

    class X(Base, metaclass=XMeta):
    "Do your stuff."

    They you would put your descriptor hacking in XMeta and still take
    advantage of the original metaclass.

    Eric Snow, Aug 10, 2011
  7. Ryan

    Fuzzyman Guest

    Yeah, the way round the "more than one metaclass problem" is to have
    your new metaclass inherit from the first one. That's not a general
    purpose solution though.

    Fuzzyman, Aug 10, 2011
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.