65

If I have this:

class foo(object):
    @property
    def bar(self):
        return 0

f = foo()

How do I get a reference to f.bar without actually invoking the method, if this is even possible?

Edited to add: What I want to do is write a function that iterates over the members of f and does something with them (what is not important). Properties are tripping me up because merely naming them in getattr() invokes their __get__() method.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • The value of `f.bar` *is* 0. The fact that it's calling a function under the hood is a class implementation detail. I think the "what" is important here if you want to get an answer. – Glenn Maynard Sep 10 '10 at 00:00
  • Um, no. The value of `f.bar` is a function that returns 0. `f.bar is 0` is False. – Kirk Strauser Sep 10 '10 at 00:39
  • OK, fair enough on the "what." What I'm trying to do is copy certain members (functions, variables, etc.) from one object to another. If one of the members is a property, first of all I don't want to invoke the underlying \_\_get__() method because it may have side effects. (In fact, in my program, I have a property that loads a PDF the first time it's referenced.) Second, I want to preserve the property's _behavior_ in the target method, not just its value at the time the attribute is copied. – kindall Sep 10 '10 at 01:17
  • 2
    @Just Some Guy: You are mistaken. If you make a global variable ZERO = 0, return that from the bar function, and `assert f.bar is ZERO`, it will always pass. `is` is the identity operator, and two `0` literals may or may not have the same identity depending on the VM. `foo.bar`, on the other hand, refers to a descriptor-instance value which wraps the bar function. – cdleary Sep 10 '10 at 07:12
  • @kindall: Just an FYI, making high-latency or side-effecting functions look like attributes is considered bad practice. For example, `doodad.render_pdf()` is preferred to `doodad.pdf` -- the latter looks innocuous and not computationally intensive. – cdleary Sep 10 '10 at 07:15
  • 2
    @cdleary Bah, I'm on crack. How did I not notice that was a property, given that it was even in the subject? Ignore me. – Kirk Strauser Sep 10 '10 at 19:10

7 Answers7

48

get_dict_attr (below) looks up attr in a given object's __dict__, and returns the associated value if its there. If attr is not a key in that __dict__, the object's MRO's __dict__s are searched. If the key is not found, an AttributeError is raised.

def get_dict_attr(obj, attr):
    for obj in [obj] + obj.__class__.mro():
        if attr in obj.__dict__:
            return obj.__dict__[attr]
    raise AttributeError

For example,

class Foo(object):
    x=1
    def bar(self):
        pass
    @property
    def baz(self):
        return 0

foo=Foo()
print(get_dict_attr(foo,'x'))
# 1
print(get_dict_attr(foo,'bar'))
# <unbound method Foo.bar>
print(get_dict_attr(foo,'baz'))
# <property object at 0xb77c0dc4>
print(get_dict_attr(foo,'y'))
# AttributeError

Note that this is very different than the normal rules of attribute lookup. For one thing, data-descriptors in obj.__class__.__dict__ (descriptors with both __get__ and __set__ methods) normally have precedence over values in obj.__dict__. In get_dict_attr, obj.__dict__ has precedence.

get_dict_attr does not try calling __getattr__.

Finally, get_dict_attr will only work with objects obj which are instances of new-style classes.

Nevertheless, I hope it is of some help.


class Foo(object):
    @property
    def bar(self):
        return 0

f = Foo()

This references the property bar:

print(Foo.bar)
# <property object at 0xb76d1d9c>

You see bar is a key in Foo.__dict__:

print(Foo.__dict__['bar'])
# <property object at 0xb775dbbc>

All properties are descriptors, which implies it has a __get__ method:

print(Foo.bar.__get__)
# <method-wrapper '__get__' of property object at 0xb76d7d74>

You can call the method by passing the object f, and the class of f as arguments:

print(Foo.bar.__get__(f,Foo))
# 0

I am fond of the following diagram. Vertical lines show the relationship between an object and the object's class.

When you have this situation:

   Foo                                B
   | Foo.__dict__={'bar':b}           | B.__dict__={'__get__':...}
   |                      \           |      
   f                       `--------> b

f.bar causes b.__get__(f,Foo) to be called.

This is explained in detail here.

Delgan
  • 18,571
  • 11
  • 90
  • 141
unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677
  • I suppose I could check each attribute of the instance to see if it's in the class, and if it's there and it's a property, grab the reference from the class instead of the instance. I was hoping there would be an easier way, though. – kindall Sep 09 '10 at 23:45
  • The matter is properties are created exactly to work as if they where values instead of function calls. And they do that in the most transparent way possible. Maybe you'd be better off using ordinar methods instead of properties on this case? – jsbueno Sep 10 '10 at 02:48
  • Your `get_dict_attr` is defined oddly. What you do in theory (except for the issues you mentioned, because you go through the mro yourself) is `try: return getattr(type(obj), attr); except AttributeError: return getattr(obj, attr)` – Jochen Ritzel Sep 10 '10 at 18:39
  • @THC4k: My goal was to avoid using `getattr` entirely, since it invokes all the attribute lookup machinery that the OP is trying to avoid. – unutbu Sep 10 '10 at 19:04
  • @THC4k: Perhaps it's unlikely, but if `type(obj)` is a class whose metaclass has a data descriptor `attr`, then `getattr(type(obj),attr)` will call the property's `__get__` method, which I don't think the OP wants. – unutbu Sep 10 '10 at 19:17
6

Short answer:

Properties return its self when they called from class: MyClass.my_prop

Also, they have fields that contain a link to the actual methods: fget, fset and fdel.

Description:

So, my_class.my_prop (where my_class = MyClass()) returns the value, but MyClass.my_prop returns the property object and MyClass.my_prop.fget returns the getter method of this property. The self is not linked to it, so it should be populated during the call: MyClass.my_prop.fget(my_class)

Example:

class MyClass:
    my_prop = property(lambda self: 'get', lambda self, x: print('set', x))

setter = MyClass.my_prop.fset
getter = MyClass.my_prop.fget

my_class = MyClass()

setter(my_class, 5)     # equals my_class.my_prop = 5
print(getter(my_class)) # equals print(my_class.my_prop)
ADR
  • 1,255
  • 9
  • 20
  • 2
    While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. Please also try not to crowd your code with explanatory comments, this reduces the readability of both the code and the explanations! – Martin Tournoij Apr 14 '17 at 02:25
  • Agree with the above comment. However, using the answer above and below, this answer put the pieces together, for me. From inside the class: return self.__class__.prop_name_here.fset --> x --> x(class_instance, value) to set. – DonkeyKong Mar 31 '20 at 15:59
5

In your case,

class Foo(object):
    @property
    def bar(self):
        return 0
f = Foo()

You can access the property through the class Foo. These are all equivalent:

Foo.bar
Foo.__dict__['bar']
f.__class__.__dict__['bar']
=> <property at 0x4920c28>

As you can see, this is a property instance. On a property you have a method called fget which returns the 'getter' value.

isinstance(Foo.bar, property)
=> True
Foo.bar.fget
=> <function Foo.bar at 0x0000014DC0CF21F0>

You can call this function (an instance method) directly. Foo.bar.fget(f) => 0

Note: you have to supply the object f (the self argument) to fget yourself, because when accessed through the class, the class does not know the instance f yet. In other words, instance method Foo.bar.fget is not yet 'bound' to f.

florisla
  • 12,668
  • 6
  • 40
  • 47
5

I ran into a similar situation to this and none of the other answers helped me, so here's an alternate approach.

My situation was slightly different, as instead of just having a @property, I was mixing in a custom decorator that adds attributes to the wrapped method. So eg normally I'd have something like this:

class Foo:
    @my_decorator
    def bar(self):
        return do_stuff()  # Pretend this is non-trivial

My @my_decorator would then give me access to self.bar.my_attribute that had some data I need to access sometimes. This works great for functions and methods, but I hit a snag when trying to mix it with @property because the property hides the reference to the method (self.bar.my_attribute fails because self.bar returns the return value of the method, which doesn't have the my_attribute that I want).

I'd use @property as the outer decorator like so:

class Foo:
    @property
    @my_decorator
    def bar(self):
        return do_stuff()  # Pretend this is non-trivial

And then the solution is to access my_attribute as Foo.bar.fget.my_attribute (the important detail here is that accessing the property from the class returns the property object, whereas accessing the property from an instance just calls the method and returns the value, without access to the reference to the original method).

TL;DR: If you need a reference to the method that provides your @property, you need to use YourClassName.your_property.fget.

robru
  • 2,313
  • 2
  • 29
  • 38
1

One thing you should know is that data descriptors (i.e., properties) only work when they are applied to (new-style) classes (see http://docs.python.org/reference/datamodel.html#implementing-descriptors). Copying them to an object will not create the property on that object. You need to copy them to a (new-style) class to take effect.

Nathan Davis
  • 5,636
  • 27
  • 39
0

Why not just lambdify it?

bar_getter = lambda: f.bar

Done.

Ryan Johnson
  • 102
  • 7
-3

I'm going to say ignore the other answers because they are bad.

You can get a reference to the property simply by foo.bar

As for what you're trying to do iterating over the members of f, see below for specifics.

The long answer: what you have to understand are that methods in Python do not belong to instances, but are attributes of the class object. For example,

class Foo:
    def bar(self):
        pass
foo = Foo()

if you call foo.bar(), Python first checks to see if foo has bar (it doesn't), then it checks if Foo has bar, which it does. Python then "binds" foo.bar to Foo.bar(foo), and calls that.

The same is true for properties. I don't know the exact process (and I imagine it differs between implementations), but essentially Python looks for foo.bar, then Foo.bar, sees that it is a property, so evaluates its getter and returns that.

darkfeline
  • 9,404
  • 5
  • 31
  • 32
  • Your explanation is correct, but it does not answer the question. If `bar` is an attribute, accessing `foo.bar` returns `None`, while the question is "How do you get the the *attribute object* `foo.bar`, not the value it returns?". – florisla Apr 22 '15 at 13:24