Why property works only for objects?

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • Michal Kwiatkowski

    #1

    Why property works only for objects?

    Hi,

    Code below shows that property() works only if you use it within a class.

    ------------------------------------------------
    class A(object):
    pass

    a = A()
    a.y = 7

    def method_get(self ):
    return self.y

    a.x = property(method _get)
    print a.x # => <property object at 0xb7dc1c84>

    A.x = property(method _get)
    print a.x # => 7
    ------------------------------------------------

    Is there any method of making descriptors on per-object basis? I would
    like to customize x access in different objects of class A. Is this
    possible and how?

    mk
    --
    . o . >> http://joker.linuxstuff.pl <<
    . . o It's easier to get forgiveness for being wrong
    o o o than forgiveness for being right.
  • Steven Bethard

    #2
    Re: Why property works only for objects?

    Michal Kwiatkowski wrote:[color=blue]
    > Code below shows that property() works only if you use it within a class.[/color]

    Yes, descriptors are only applied at the class level (that is, only
    class objects call the __get__ methods).
    [color=blue]
    > Is there any method of making descriptors on per-object basis?[/color]

    I'm still not convinced that you actually want to, but you can write
    your own descriptor to dispatch to the instance object (instead of the
    type):
    [color=blue][color=green][color=darkred]
    >>> class InstancePropert y(object):[/color][/color][/color]
    .... def __init__(self, func_name):
    .... self.func_name = func_name
    .... def __get__(self, obj, type=None):
    .... if obj is None:
    .... return self
    .... return getattr(obj, self.func_name) (obj)
    ....[color=blue][color=green][color=darkred]
    >>> class C(object):[/color][/color][/color]
    .... x = InstancePropert y('_x')
    ....[color=blue][color=green][color=darkred]
    >>> c = C()
    >>> c.x[/color][/color][/color]
    Traceback (most recent call last):
    File "<interacti ve input>", line 1, in ?
    File "<interacti ve input>", line 7, in __get__
    AttributeError: 'C' object has no attribute '_x'[color=blue][color=green][color=darkred]
    >>> def getx(self):[/color][/color][/color]
    .... return 42
    ....[color=blue][color=green][color=darkred]
    >>> c._x = getx
    >>> c.x[/color][/color][/color]
    42


    STeVe

    Comment

    • Michal Kwiatkowski

      #3
      Re: Why property works only for objects?

      Steven Bethard napisa³(a):[color=blue][color=green]
      >> Is there any method of making descriptors on per-object basis?[/color]
      >
      > I'm still not convinced that you actually want to, but you can write
      > your own descriptor to dispatch to the instance object (instead of the
      > type):[/color]

      Ok, this works for attributes I know a name of at class definition. What
      about other situations? I could possibly want to have different sets of
      attributes for instances of a class. Is it still possible to do? I don't
      also like current solution, as it wraps things around, creating another
      attribute ('_x') in the process. Aren't there any cleaner solutions?

      The problem is I have an instance of a given class (say BaseClass) and I
      want it to implement some attribute accesses as method calls. I'm not a
      creator of this object, so changing definition of BaseClass or
      subclassing it is not an option. Code below doesn't work, but shows my
      intention:

      # obj is instance of BaseClass
      def get_x(self):
      # ...
      def set_x(self, value):
      # ...
      obj.x = property(get_x, set_x)

      Changing __setattr__/__getattr__ of an object also doesn't work for the
      same reason: dictionary lookup is made only in class attributes,
      ommiting object attributes. It's confusing, because writting obj.x and
      obj.__getattrib ute__('x') gives a different results. There also seems
      not to be any clear way to define methods for objects. Something like:

      class C(object):
      pass

      def method(self):
      return self.x

      c = c.x
      c.method = method
      c.method() # => AttributeError

      So another question arise. Is it possible to make function a method (so
      it will receive calling object as first argument)?

      mk
      --
      . o . >> http://joker.linuxstuff.pl <<
      . . o It's easier to get forgiveness for being wrong
      o o o than forgiveness for being right.

      Comment

      • Alex Martelli

        #4
        Re: Why property works only for objects?

        Michal Kwiatkowski <ruby@no.spam > wrote:
        ...[color=blue]
        > The problem is I have an instance of a given class (say BaseClass) and I
        > want it to implement some attribute accesses as method calls. I'm not a
        > creator of this object, so changing definition of BaseClass or
        > subclassing it is not an option.[/color]

        Wrong! Of _course_ it's an option -- why do you think it matters at all
        whether you're the creator of this object?!
        [color=blue]
        > Code below doesn't work, but shows my
        > intention:
        >
        > # obj is instance of BaseClass
        > def get_x(self):
        > # ...
        > def set_x(self, value):
        > # ...
        > obj.x = property(get_x, set_x)[/color]

        def insert_property (obj, name, getter, setter):
        class sub(obj.__class __): pass
        setattr(sub, name, property(getter , setter))
        obj.__class__ = sub

        See? Of COURSE you can subclass -- not hard at all, really.


        Alex

        Comment

        • Alex Martelli

          #5
          Re: Why property works only for objects?

          Michal Kwiatkowski <ruby@no.spam > wrote:
          [color=blue]
          > So another question arise. Is it possible to make function a method (so
          > it will receive calling object as first argument)?[/color]

          Sure, impor types then call types.MethodTyp e:

          f = types.MethodTyp e(f, obj, someclass)

          (f.__get__ is also fine for Python-coded functions) -- make sure that
          someclass is obj's class or some ancestor of it, of course.


          Alex


          Comment

          • Michal Kwiatkowski

            #6
            Re: Why property works only for objects?

            Alex Martelli napisa³(a):[color=blue]
            > Wrong! Of _course_ it's an option -- why do you think it matters at all
            > whether you're the creator of this object?![/color]

            Statically typed languages background. Sorry. ;)
            [color=blue][color=green]
            >> Code below doesn't work, but shows my
            >> intention:
            >>
            >> # obj is instance of BaseClass
            >> def get_x(self):
            >> # ...
            >> def set_x(self, value):
            >> # ...
            >> obj.x = property(get_x, set_x)[/color]
            >
            > def insert_property (obj, name, getter, setter):
            > class sub(obj.__class __): pass
            > setattr(sub, name, property(getter , setter))
            > obj.__class__ = sub
            >
            > See? Of COURSE you can subclass -- not hard at all, really.[/color]

            Let me understand it clearly. If I change __class__ of an object,
            existing attributes (so methods as well) of an object are still
            accessible the same way and don't change its values. Only resolution of
            attributes/methods not found in object is changed, as it uses new
            version of __class__ to lookup names. Is this right?

            mk
            --
            . o . >> http://joker.linuxstuff.pl <<
            . . o It's easier to get forgiveness for being wrong
            o o o than forgiveness for being right.

            Comment

            • Michal Kwiatkowski

              #7
              Re: Why property works only for objects?

              Alex Martelli napisa³(a):[color=blue][color=green]
              >> So another question arise. Is it possible to make function a method (so
              >> it will receive calling object as first argument)?[/color]
              >
              > Sure, impor types then call types.MethodTyp e:
              >
              > f = types.MethodTyp e(f, obj, someclass)
              >
              > (f.__get__ is also fine for Python-coded functions) -- make sure that
              > someclass is obj's class or some ancestor of it, of course.[/color]

              I wasn't aware of types module. Thanks for your reply.

              mk
              --
              . o . >> http://joker.linuxstuff.pl <<
              . . o It's easier to get forgiveness for being wrong
              o o o than forgiveness for being right.

              Comment

              • Bruno Desthuilliers

                #8
                Re: Why property works only for objects?

                Michal Kwiatkowski a écrit :[color=blue]
                > Steven Bethard napisa³(a):
                >[color=green][color=darkred]
                >>>Is there any method of making descriptors on per-object basis?[/color]
                >>
                >>I'm still not convinced that you actually want to, but you can write
                >>your own descriptor to dispatch to the instance object (instead of the
                >>type):[/color]
                >
                >
                > Ok, this works for attributes I know a name of at class definition. What
                > about other situations? I could possibly want to have different sets of
                > attributes for instances of a class.[/color]

                Then you want a different class for each different set of attributes.
                [color=blue]
                > Is it still possible to do? I don't
                > also like current solution, as it wraps things around, creating another
                > attribute ('_x') in the process. Aren't there any cleaner solutions?
                >
                > The problem is I have an instance of a given class (say BaseClass) and I
                > want it to implement some attribute accesses as method calls. I'm not a
                > creator of this object, so changing definition of BaseClass or
                > subclassing it is not an option.[/color]

                What you want is delegation. Which is pretty easy with Python, look at
                __getattr__ and __setattr__.
                [color=blue]
                > Code below doesn't work, but shows my
                > intention:
                >
                > # obj is instance of BaseClass
                > def get_x(self):
                > # ...
                > def set_x(self, value):
                > # ...
                > obj.x = property(get_x, set_x)[/color]

                class ObjWrapper(obje ct):
                def __init__(self, obj):
                self.obj = obj

                # special case x
                def _get_x(self):
                return self.obj.x

                def _set_x(self, value):
                self.obj.x = value

                x = property(fget=_ get_x, fset=_set_x)

                # default lookup, directly delegate to obj
                def __getattr__(sel f, name):
                return getattr(self.ob j, name)

                def __setattr__(sel f, name, value):
                # this one is a bit more tricky
                # and I don't remember it right now,
                # but it's in the fine manual anyway

                obj = ObjWrapper(obj)
                obj.x # calls ObjWrapper._get _x()

                [color=blue]
                > Changing __setattr__/__getattr__ of an object also doesn't work for the
                > same reason: dictionary lookup is made only in class attributes,
                > ommiting object attributes. It's confusing, because writting obj.x and
                > obj.__getattrib ute__('x') gives a different results. There also seems
                > not to be any clear way to define methods for objects. Something like:
                >
                > class C(object):
                > pass
                >
                > def method(self):
                > return self.x
                >
                > c = c.x
                > c.method = method
                > c.method() # => AttributeError[/color]

                import types
                c.method = types.MethodTyp e(method, c, c.__class__)
                [color=blue]
                > So another question arise. Is it possible to make function a method (so
                > it will receive calling object as first argument)?[/color]

                Yeps, just wrap it in a Method object (cf above)

                Comment

                • Bruno Desthuilliers

                  #9
                  Re: Why property works only for objects?

                  Michal Kwiatkowski a écrit :[color=blue]
                  > Alex Martelli napisa³(a):
                  >[color=green]
                  >>Wrong! Of _course_ it's an option -- why do you think it matters at all
                  >>whether you're the creator of this object?![/color]
                  >[/color]
                  (snip)[color=blue][color=green]
                  >>
                  >>def insert_property (obj, name, getter, setter):
                  >> class sub(obj.__class __): pass
                  >> setattr(sub, name, property(getter , setter))
                  >> obj.__class__ = sub
                  >>
                  >>See? Of COURSE you can subclass -- not hard at all, really.[/color]
                  >
                  >
                  > Let me understand it clearly. If I change __class__ of an object,
                  > existing attributes (so methods as well)
                  > of an object are still
                  > accessible the same way and don't change its values. Only resolution of
                  > attributes/methods not found in object is changed, as it uses new
                  > version of __class__ to lookup names. Is this right?[/color]

                  Attributes, yes. Not methods. Methods are looked up in the class. But in
                  the example above (brillant, as usual), Alex dynamically creates a
                  subclass of obj.__class__, so inheritence takes care of methods lookup.

                  [color=blue]
                  > mk[/color]

                  Comment

                  • Michal Kwiatkowski

                    #10
                    Re: Why property works only for objects?

                    Bruno Desthuilliers napisa³(a):[color=blue][color=green]
                    >> Let me understand it clearly. If I change __class__ of an object,
                    >> existing attributes (so methods as well) of an object are still
                    >> accessible the same way and don't change its values. Only resolution of
                    >> attributes/methods not found in object is changed, as it uses new
                    >> version of __class__ to lookup names. Is this right?[/color]
                    >
                    > Attributes, yes. Not methods. Methods are looked up in the class.[/color]

                    My experience shows exactly the opposite. Any attribute/method you try
                    to access is first looked up in object dictionary, then inside class
                    definition.

                    import types

                    class C(object):
                    def f(self):
                    print "old method f()"

                    obj = C()

                    def f(self):
                    print "new method f()"

                    obj.f = types.MethodTyp e(f, C)

                    obj.f() # => "new method f()"

                    Since that works, intuitively for me would be to assign object's
                    descriptors like that:

                    obj.x = property(types. MethodType(lamb da self: 42, C))

                    But I just get a property object. So, it seems descriptors have little
                    bit of magic, as they don't work identically for classes and objects.

                    The same goes for special methods and attributes (written as __*__). So
                    I cannot change __getattr__/__setattr__/__metaclass__ or any other
                    attribute that starts with __ for a single object. It's not so bad
                    except for situations were class of an object defines its own
                    __getattribute_ _ method, which takes control of an object from us. To
                    get/set any attribute of an object we must use object type methods:

                    class C(object):
                    def __getattribute_ _(self, name):
                    return 42

                    obj = C()

                    obj.a = 5

                    print obj.a # => 42
                    print object.__getatt ribute__(obj, 'a') # => 5

                    I gets even more strange when you try to modify, say __len__ or
                    __repr__. Docstring for object.__repr__ says:
                    "x.__repr__ () <==> repr(x)" which doesn't seem to be always true:

                    class C(object):
                    def __repr__(self):
                    return "class repr"

                    obj = C()
                    obj.__repr__ = types.MethodTyp e(lambda self: "instance repr", C)

                    print repr(obj) # => class repr
                    print obj.__repr__() # => instance repr

                    Maybe the manual should say "x.__class__.__ repr__() <==> repr(x)" instead?

                    I'm trying to understand attributes lookups made by Python, having
                    properties and special methods in mind. So far I've come up with kind of
                    reasoning I've coded below. I would appreciate any comments and
                    suggestions.

                    def lookup_name(obj , name):
                    get = lambda obj, name: object.__getatt ribute__(obj, name)
                    has = lambda obj, name: name in get(obj, '__dict__')

                    # assume C is a new style class
                    C = get(obj, '__class__')

                    # 1) use class' __getattribute_ _ method
                    try:
                    if has(C, '__getattribute __'):
                    return get(C, '__getattribute __')(obj, name)
                    except AttributeError: pass

                    # 2) lookup in object's dictionary
                    try:
                    if has(obj, name):
                    return get(obj, name)
                    except AttributeError: pass

                    # 3) lookup in classes
                    for c in obj.__class__.m ro():
                    try:
                    if has(c, name):
                    desc = get(c, name)
                    # 3a) handle descriptors
                    try:
                    return get(desc, '__get__')(obj)
                    except: pass
                    # 3b) no descriptors -> use value
                    return desc
                    except AttributeError: pass

                    raise AttributeError, "Not found!"

                    mk
                    --
                    . o . >> http://joker.linuxstuff.pl <<
                    . . o It's easier to get forgiveness for being wrong
                    o o o than forgiveness for being right.

                    Comment

                    • Alex Martelli

                      #11
                      Re: Why property works only for objects?

                      Michal Kwiatkowski <ruby@no.spam > wrote:
                      ...[color=blue]
                      > Let me understand it clearly. If I change __class__ of an object,
                      > existing attributes (so methods as well) of an object are still
                      > accessible the same way and don't change its values. Only resolution of
                      > attributes/methods not found in object is changed, as it uses new
                      > version of __class__ to lookup names. Is this right?[/color]

                      Attributes of the _instance_ are unchanged. Anything that would
                      normally be looked up in the OLD __class__, such as methods, is gone:
                      which is why you normally set as the NEW __class__ some subclass of the
                      old one, so that nothing is really gone;-).


                      Alex

                      Comment

                      • Alex Martelli

                        #12
                        Re: Why property works only for objects?

                        Michal Kwiatkowski <ruby@no.spam > wrote:
                        ...[color=blue]
                        > I'm trying to understand attributes lookups made by Python, having
                        > properties and special methods in mind. So far I've come up with kind of
                        > reasoning I've coded below. I would appreciate any comments and
                        > suggestions.[/color]

                        First, let's forget legacy-style classes, existing only for backwards
                        compatibility, and focus on new-style ones exclusively -- never use
                        legacy classes if you can avoid that.

                        Descriptors come in two varieties: overriding and non-overriding. Both
                        kinds have a __get__ method (that's what makes them descriptors).
                        Overriding ones also have a __set__, non-overriding ones don't. (Until
                        pretty recently, the two kinds were known as data and non-data, but the
                        new terminology of overriding and non-overriding is much clearer).

                        I covered descriptors in one of my talks at Pycon '05 and elsewhere; you
                        can find just about all of my talks by starting at www.aleax.it (mostly
                        just PDF forms of the slides in my presentations). I cover them in quite
                        a central role in the forthcoming 2nd edition of Python in a Nutshell,
                        too. But, summarizing:

                        overriding descriptors are FIRST looked up in the class (class overrides
                        instance); non-overriding descriptors, first in the instance (class does
                        not override). Functions are NON-overriding descriptors.

                        Special methods IMPLICITLY looked up by Python ALWAYS go to the class
                        ONLY -- adding an x.__getitem__ per-class attribute that happens to be
                        callable doesn't mean that attribute is gonna handle z=x[y] and the like
                        (in legacy-style classes the rule was different, but that led to a host
                        of complications that we're much better off without!).


                        Alex

                        Comment

                        • Bruno Desthuilliers

                          #13
                          Re: Why property works only for objects?

                          Michal Kwiatkowski a écrit :[color=blue]
                          > Bruno Desthuilliers napisa³(a):
                          >[color=green][color=darkred]
                          >>>Let me understand it clearly. If I change __class__ of an object,
                          >>>existing attributes (so methods as well) of an object are still
                          >>>accessible the same way and don't change its values. Only resolution of
                          >>>attributes/methods not found in object is changed, as it uses new
                          >>>version of __class__ to lookup names. Is this right?[/color]
                          >>
                          >>Attributes, yes. Not methods. Methods are looked up in the class.[/color]
                          >
                          >
                          > My experience shows exactly the opposite. Any attribute/method you try
                          > to access is first looked up in object dictionary, then inside class
                          > definition.[/color]

                          Yes, that's what I said.

                          You wrote:
                          """
                          existing attributes (so methods as well) of an object are still
                          accessible the same way and don't change its values
                          """

                          Attributes (I mean instance attributes) living in the object's dict,
                          they aren't impacted by the runtime class change. Methods being in the
                          most common case (I'd say > 99.9 %) defined *in the class*, if you
                          change the class of an object, this is very likely to impact resolution
                          of methods lookup.

                          Now I agree that the statement "methods are looked up in the class" is
                          wrong. Methods are of course first looked up in the object, then in the
                          class. But the case of a method living in the object's dict is not that
                          common...

                          Comment

                          • Michal Kwiatkowski

                            #14
                            Re: Why property works only for objects?

                            Alex Martelli napisa³(a):[color=blue]
                            > First, let's forget legacy-style classes, existing only for backwards
                            > compatibility, and focus on new-style ones exclusively -- never use
                            > legacy classes if you can avoid that.[/color]

                            Ok, let's cover only new-style classes in our discussion.

                            I've read your comments and am on a way of reading your articles. Still,
                            with my current knowledge I'm trying to write pure python attributes
                            lookup function. I've failed for example given below:

                            class C(object):
                            __dict__ = {}

                            obj = C()
                            obj.a = 7
                            obj.__dict__ = {}
                            print object.__getatt ribute__(obj, '__dict__')
                            print object.__getatt ribute__(C, '__dict__')
                            print obj.a # => 7 !!!

                            First print returns "{}" and the second returns

                            {'__dict__': {},
                            '__module__': '__main__',
                            '__weakref__': <attribute '__weakref__' of 'C' objects>,
                            '__doc__': None}

                            Neither of them have "a" attribute. How come obj.a doesn't raise an
                            exception? Where obj.a is kept?

                            mk
                            --
                            . o . >> http://joker.linuxstuff.pl <<
                            . . o It's easier to get forgiveness for being wrong
                            o o o than forgiveness for being right.

                            Comment

                            • Alex Martelli

                              #15
                              Re: Why property works only for objects?

                              Michal Kwiatkowski <ruby@no.spam > wrote:
                              [color=blue]
                              > class C(object):
                              > __dict__ = {}
                              >
                              > obj = C()
                              > obj.a = 7
                              > obj.__dict__ = {}
                              > print object.__getatt ribute__(obj, '__dict__')
                              > print object.__getatt ribute__(C, '__dict__')
                              > print obj.a # => 7 !!!
                              >
                              > First print returns "{}" and the second returns
                              >
                              > {'__dict__': {},
                              > '__module__': '__main__',
                              > '__weakref__': <attribute '__weakref__' of 'C' objects>,
                              > '__doc__': None}
                              >
                              > Neither of them have "a" attribute. How come obj.a doesn't raise an
                              > exception? Where obj.a is kept?[/color]

                              It's easier to trace if you use a unique value rather than 7 ...:
                              [color=blue][color=green][color=darkred]
                              >>> class C(object):[/color][/color][/color]
                              .... __dict__ = {}
                              ....[color=blue][color=green][color=darkred]
                              >>> obj = C()
                              >>> obj.a = object()
                              >>> import gc
                              >>> gc.get_referrer s(obj.a)[/color][/color][/color]
                              [{'a': <object object at 0x3d438>}]

                              so, at this point, you know that obj.a is kept in a dictionary where
                              it's the only value. That's the dictionary you would USUALLY be able to
                              get to as obj.__dict__, but...:
                              [color=blue][color=green][color=darkred]
                              >>> obj.__dict__[/color][/color][/color]
                              {}

                              ....the presence of '__dict__' as an entry in C is confusing the issue,
                              because that's what you get in this case as obj.__dict__.

                              C.__dict__ gives you a dictproxy, not a real dict, by the way:
                              [color=blue][color=green][color=darkred]
                              >>> obj.__dict__ is C.__dict__['__dict__'][/color][/color][/color]
                              True

                              The funny thing is that builtins like var, which should know better,
                              also get fooled...:
                              [color=blue][color=green][color=darkred]
                              >>> vars(obj)[/color][/color][/color]
                              {}[color=blue][color=green][color=darkred]
                              >>> vars(obj) is C.__dict__['__dict__'][/color][/color][/color]
                              True

                              ....and so does the assignment to obj.__dict__... :
                              [color=blue][color=green][color=darkred]
                              >>> obj.__dict__ = {}
                              >>> gc.get_referrer s(obj.a)[/color][/color][/color]
                              [{'a': <object object at 0x3d438>, '__dict__': {}}]

                              Now, both obj.a and obj.__dict__ are entries in a dictionary where
                              they're the only two entries -- exactly the dictionary that would
                              NORMALLY be obj.__dict__.

                              I think a fair case can be made that you've found a bug in Python here:
                              the existence of that __dict__ in C's class body is clearly causing
                              unintended anomalies. Fortunately, getattr and friends don't in fact
                              get confused, but vars does, as does assignment to obj.__dict__...


                              Alex

                              Comment

                              Working...