Getattr vs. inspect.getmembers

You can capture the attribute of an object using either getattr (obj, attr) or inspect.getmembers (obj) and then filtering by name:

import inspect class Foo(object): def __init__(self): self.a = 100 def method(self): pass foo = Foo() method_by_getattr = getattr(foo, 'method') foo_members = inspect.getmembers(foo) method_by_inspect = [member[1] for member in foo_members if member[0] == "method"][0] print (id(method_by_getattr), method_by_getattr, type(method_by_getattr)) print (id(method_by_inspect), method_by_inspect, type(method_by_inspect)) a_by_getattr = getattr(foo, "a") a_by_inspect = [member[1] for member in foo_members if member[0] == "a"][0] print (id(a_by_getattr), a_by_getattr, type(a_by_getattr)) print (id(a_by_inspect), a_by_inspect, type(a_by_inspect)) # (38842160L, <bound method Foo.method of <__main__.Foo object at 0x00000000025EF390>>, <type 'instancemethod'>) # (39673576L, <bound method Foo.method of <__main__.Foo object at 0x00000000025EF390>>, <type 'instancemethod'>) # (34072832L, 100, <type 'int'>) # (34072832L, 100, <type 'int'>) 

For the 'a' attribute, getattr and inspect.getmembers return the same object. But for the method "method" they return different objects (as can be seen from the scattered identifiers).

Why is this so?

+4
source share
2 answers

I modified your example a bit to better illustrate the options that I will use to explain the behavior

With temporary variables

 import inspect def print_id(obj): print "{} => {}".format(id(obj), obj) def getmember(obj, name): #members = dict(inspect.getmembers(obj)) #return members[name] return [member for _name, member in inspect.getmembers(obj) if name == _name][0] class Foo(object): def bar(self): pass foo = Foo() m1 = foo.bar m2 = getattr(foo, 'bar') m3 = getmember(foo, 'bar') print_id(m1) print_id(m2) print_id(m3) 

However, if you are checking objects in REPL, the basic structure of your code will probably look like this:

No temporary variables

 #... foo = Foo() print_id(foo.bar) print_id(getattr(foo, 'bar')) print_id(getmember(foo, 'bar')) 

The id() function basically returns the memory address of the object. That is, it is not an identity that is unique to all objects created during the entire program execution time. It is unique only to all objects that exist in the process at any given time.

The explanation, which corresponds to the difference between the two examples, is that resolving foo.bar any of the three ways gives you a new object each time . In the first example, these objects are stored in temporary variables, so all three must be located at different memory addresses.

In the second example, the associated method object is no longer needed after it is printed; Python reference count will free up its memory. This means that the next time the object of the associated method is created, it creates a new object created at the same memory address as the previous one. This is why it might seem that you are getting the same object multiple times.

The fact that you always get a new object of the associated method can be shown trivially:

 >>> foo.bar == foo.bar True >>> foo.bar is foo.bar False 
+4
source

millimoose receives a green check, but I thought I would add a little.

tl; dr

The objects of related objects are temporary. That is, they are recreated every time you capture them.

 class Foo(object): def bar(object): pass foo = Foo() m1 = foo.bar m2 = foo.bar print (id(m1)) print (id(m2)) # 38121264 # 38952752 

Read more: Descriptor Protocol

Objects of related objects are recreated every time you capture them, because the function object stored in the class implements the descriptor protocol .

The bar function is stored in the dict class:

 class Foo(object): def bar(object): pass print (Foo.__dict__['bar']) # <function bar at 0x00000000025F2208> 

When an instance of Foo tries to access the bar, it does not have it. So he looks over his class and finds it in the dict class. The function object implements the __ get __ method in accordance with the descriptor protocol. So in fact, calling the bound method is as follows:

 print(Foo.__dict__['bar'].__get__(foo, Foo)) # <bound method Foo.bar of <__main__.Foo object at 0x00000000025719B0>> 

Which is equivalent:

 print (foo.bar) # <bound method Foo.bar of <__main__.Foo object at 0x00000000025719B0>> 

You can get the original function as follows:

 print (foo.bar.im_func) # Python 2.x # <function bar at 0x00000000025F2208> print (foo.bar.__func__) # Python 3.x # <function bar at 0x00000000025F2208> 
+7
source

Source: https://habr.com/ru/post/1439254/


All Articles