How python attribute lookup process works? - python

When i say "python attribute lookup proccess" i mean: what python does when you write x.foo??
Searching the web i didn't found to much docs about this, one of the best papers i found resumed the proccess to the following steps (you can see the full article here)
If attrname is a special (i.e. Python-provided) attribute for objectname, return it.
Check objectname.__class__.__dict__ for attrname. If it exists and is a data-descriptor, return the descriptor result. Search all bases of objectname.__class__ for the same case.
Check objectname.__dict__ for attrname, and return if found. If objectname is a class, search its bases too. If it is a class and a descriptor exists in it or its bases, return the descriptor result.
Check objectname.__class__.__dict__ for attrname. If it exists and is a non-data descriptor, return the descriptor result. If it exists, and is not a descriptor, just return it. If it exists and is a data descriptor, we shouldn't be here because we would have returned at point 2. Search all bases of objectname.__class__ for same case.
Raise AttributeError.
At first this might seem right, but the attribute lookup process is a little bit more complicated, for example for x.foo, it doesn't behave the same if x is a class or an instance.
I have a found some samples that can't be explained by this way. Consider the following python code:
class Meta(type):
def __getattribute__(self, name):
print("Metaclass getattribute invoked:", self)
return type.__getattribute__(self, name)
def __getattr__(self, item):
print('Metaclass getattr invoked: ', item)
return None
class C(object, metaclass=Meta):
def __getattribute__(self, name):
print("Class getattribute invoked:", args)
return object.__getattribute__(self, name)
c=C()
Now check the following lines with the corresponding output:
>> C.__new__
Metaclass getattribute invoked: <class '__main__.C'>
<built-in method __new__ of type object at 0x1E1B80B0>
>> C.__getattribute__
Metaclass getattribute invoked: <class '__main__.C'>
<function __getattribute__ at 0x01457F18>
>> C.xyz
Metaclass getattribute invoked: <class '__main__.C'>
Metaclass getattr invoked: xyz
None
>> c.__new__
Class getattribute invoked: (<__main__.C object at 0x013E7550>, '__new__')
<built-in method __new__ of type object at 0x1E1B80B0>
>> c.__getattribute__
Class getattribute invoked: (<__main__.C object at 0x01438DB0>, '__getattribute__')
Metaclass getattribute invoked: <class '__main__.C'>
<bound method C.__getattribute__ of <__main__.C object at 0x01438DB0>>
>>
The conclusions i have been are (considering we're searching for x.foo):
__getattribute__ is different for instances of < type 'type' > and < type 'object' >. For C.foo(), 'foo' is searched first on C.__dict__ and returned if found (instead of searching type(C)) and for x.foo() 'foo' is searched on type(x).__dict__ and on x.__dict__.
__getattribute__ method is always resolved on type(x), what i don't understand here is the last case: c.__getattribute__, isn't object contains a method __getattribute__ (and C inherits from object), so why does metaclass getattribute method gets called.
Can someone explain this please?? or at less tell me where can i find some documentation about this, thanks.

If you added print("Metaclass getattribute invoked:", self, name) you'd see:
>>> c.__getattribute__
Class getattribute invoked: <__main__.C object at 0x2acdbb1430d0> __getattribute__
Metaclass getattribute invoked: <class '__main__.C'> __name__
<bound method C.__getattribute__ of <__main__.C object at 0x2acdbb1430d0>>
The metaclass __getattribute__ is getting invoked in order to build the repr of the expression c.__getattribute__, so that it can print C's __name__.
btw, __getattribute__ works the same for classes and metaclasses; the attribute is looked up first on the instance then on the instance's type.
>>> Meta.foo = 1
>>> C.foo
('Metaclass getattribute invoked:', <class '__main__.C'>, 'foo')
1
>>> c.foo
('Class getattribute invoked:', <__main__.C object at 0x2acdbb1430d0>, 'foo')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in __getattribute__
AttributeError: 'C' object has no attribute 'foo'
>>> C.bar = 2
>>> c.bar
('Class getattribute invoked:', <__main__.C object at 0x2acdbb1430d0>, 'bar')
2

Related

Python base class can be object without __mro_entries__

Today I have discovered that python object without __mro_entries__ can be used as a base class.
Example:
class Base:
def __init__(self, *args):
self.args = args
def __repr__(self):
return f'{type(self).__name__}(*{self.args!r})'
class Delivered(Base):
pass
b = Base()
d = Delivered()
class Foo(b, d):
pass
print(type(Foo) is Delivered)
print(Foo)
True
Delivered(*('Foo', (Base(*()), Delivered(*())), {'__module__': '__main__', '__qualname__': 'Foo'}))
As a result Foo will be instance of a Delivered class and it's not a valid type.
I do understand use case of __mro_entries__ but what use case of using object without __mro_entries__ as a base class. Is it a bug at python?
TL;DR Not a bug, but an extreme abuse of the class statement.
A class statement is equivalent to a call to a metaclass. Lacking an explicit metaclass keyword argument, the metaclass has to be inferred from the base class(es). Here, the "metaclass" of the "class" b is Base, while the metaclass of d is Delivered. Since each is a non-strict subclass of a common metaclass (Base), Delivered is chosen as the more specific metaclass.
>>> Delivered('Foo', (b, d), {})
Delivered(*('Foo', (Base(*()), Delivered(*())), {}))
Delivered can be used as a metaclass because it accepts the same arguments that the class statement expects a metaclass to accept: a string for the name of the type, a sequence of parent classes, and a mapping to use as the namespace. In this case, Delivered doesn't use them to create a type; it simply prints the arguments.
As a result, Foo is bound to an instance of Delivered, not a type. So Foo is a class only in the sense that it was produced by a class statement: it is decidedly not a type.
>>> issubclass(Foo, Delivered)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: issubclass() arg 1 must be a class
>>> Foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'Delivered' object is not callable

type hints for method annotated with #property

How do you access type hints for methods annotated with the #property decorator?
Normally, this is very straightforward:
>>> class Foo:
... def bar(self) -> str:
... pass
...
>>> import typing
>>> typing.get_type_hints(Foo.bar)
{'return': <class 'str'>}
But once bar is annotated with #property and made into a property object, it's not obvious:
>>> class Foo:
... #property
... def bar(self) -> str:
... pass
...
>>> import typing
>>> typing.get_type_hints(Foo.bar)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/local/Cellar/python3/3.6.4_2/Frameworks/Python.framework/Versions/3.6/lib/python3.6/typing.py", line 1527, in get_type_hints
'or function.'.format(obj))
TypeError: <property object at 0x1050fcc28> is not a module, class, method, or function.
>>> typing.get_type_hints(Foo.bar.__get__)
{}
The __get__ isn't the actual function, but a wrapper around it:
>>> Foo.bar.__get__
<method-wrapper '__get__' of property object at 0x11d6f3b88>
To access the function, you use property.fget:
>>> Foo.bar.fget
<function __main__.Foo.bar>
And of course that has the annotations:
>>> typing.get_type_hints(Foo.bar.fget)
{'return': str}
That isn't exactly obvious or discoverable. IIRC, this came up at some point on python-ideas—it's relatively easy for Mypy or another outside static analyzer to get the annotations for a property, but not for code inside the program—but I assume nobody came up with a good answer, or it would have been implemented (or at least discussed in PEP 526).
The function you defined is Foo.bar.fget, not Foo.bar.__get__:
typing.get_type_hints(Foo.bar.fget)

Strange Nuance of self in Method Argument List

I've encountered a pythonic curiosity whose meaning eludes me.
I've found that method dispatch using a dictionary in a class appears to work differently, depending on whether the dispatch is done in __init__(). The difference is whether the selected method is invoked with or without the self argument.
Code illustration:
#!/usr/bin/python
class strange(object):
def _eek(): # no self argument
print "Hi!\n"
dsp_dict = {"Get_eek" : _eek}
noideek = dsp_dict["Get_eek"]
def __init__(self):
self.ideek = self.dsp_dict["Get_eek"]
self.ideek2 = self._eek
self.ideek3 = self.noideek
def call_ideek(self):
try:
self.ideek()
except TypeError:
print "Alas!\n"
def call_ideek2(self):
try:
self.ideek2()
except TypeError:
print "Alas!\n"
def call_ideek3(self):
try:
self.ideek3()
except TypeError:
print "Alas!\n"
def call_noideek(self):
try:
self.noideek()
except TypeError:
print "Alas!\n"
x=strange()
print "Method routed through __init__() using the dictionary:"
x.call_ideek()
print "Method routed through __init__() directly:"
x.call_ideek2()
print "Method routed through __init__() using attribute set from dictionary:"
x.call_ideek3()
print "Method not routed through __init__():"
x.call_noideek()
Running this, I see:
I, kazoo > ./curio.py
Method routed through __init__() using the dictionary:
Hi!
Method routed through __init__() directly:
Alas!
Method routed through __init__() using attribute set from dictionary:
Alas!
Method not routed through __init__():
Alas!
The try-except clauses are catching this sort of thing:
Traceback (most recent call last):
File "./curio.py", line 19, in <module>
x.call_noideek()
TypeError: call_noideek() takes no arguments (1 given)
That is, if the indirection is accomplished in __init__ by reference to the dictionary, the resulting method is not called with the implicit self argument.
But if the indirection is accomplished either in __init__ by direct reference to _eek(), or by creating a new attribute (noideek) and setting it from the dictionary, or even in __init__ by reference to the attribute originally set from the dictionary, then the self argument is in the call list.
I can work with this, but I don't understand it. Why the difference in call signature?
Have a Look at this
>>> x.ideek
<function _eek at 0x036AB130>
>>> x.ideek2
<bound method strange._eek of <__main__.strange object at 0x03562C30>>
>>> x.ideek3
<bound method strange._eek of <__main__.strange object at 0x03562C30>>
>>> x.noideek
<bound method strange._eek of <__main__.strange object at 0x03562C30>>
>>> x.dsp_dict
{'Get_eek': <function _eek at 0x036AB130>}
>>> x._eek
<bound method strange._eek of <__main__.strange object at 0x03562C30>>
You can see the difference between static methods and class methods here.
When you store the class method in that dict, it loses the information about it's enclosing class and is treated as a function (see output of x.dsp_dict).
Only if you assign that function to noideek in the class context, it will then become a class method again.
Whereas when referencing the dict from the init method, python threats it as a static method ("function") not changing anything and omnitts the self parameter. (ideek)
ideek2 and ideek3 can be seen as "aliases" where that class method is only re-referenced.

Setting attributes on __func__

In the documentation on instance methods it states that:
Methods also support accessing (but not setting) the arbitrary function attributes on the underlying function object.
But I can't seem to be able to verify that restriction. I tried setting both an arbitrary value and one of the "Special Attributes" of functions:
class cls:
def foo(self):
f = self.foo.__func__
f.a = "some value" # arbitrary value
f.__doc__ = "Documentation"
print(f.a, f.__doc__)
When executed, no errors are produced and the output is as expected:
cls().foo() # prints out f.a, f.__doc__
What is it that I'm misunderstanding with the documentation?
You are misunderstanding what is being said. It says that you can access but not set the attributes of the underlying function object from the method!
>>> class Foo:
... def foo(self):
... self.foo.__func__.a = 1
... print(self.foo.a)
... self.foo.a = 2
...
>>> Foo().foo()
1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in foo
AttributeError: 'method' object has no attribute 'a'
Note how foo.a is updated when you set it on the __func__ value, but you cannot set it directly using self.foo.a = value.
So the function object can be modified as you please, the method wrapper only provides read-only access to the attributes on the underlying function.

single argument super() - what's it for? [duplicate]

I wonder when to use what flavour of Python 3 super().
Help on class super in module builtins:
class super(object)
| super() -> same as super(__class__, <first argument>)
| super(type) -> unbound super object
| super(type, obj) -> bound super object; requires isinstance(obj, type)
| super(type, type2) -> bound super object; requires issubclass(type2, type)
Until now I've used super() only without arguments and it worked as expected (by a Java developer).
Questions:
What does "bound" mean in this context?
What is the difference between bound and unbound super object?
When to use super(type, obj) and when super(type, type2)?
Would it be better to name the super class like in Mother.__init__(...)?
Let's use the following classes for demonstration:
class A(object):
def m(self):
print('m')
class B(A): pass
Unbound super object doesn't dispatch attribute access to class, you have to use descriptor protocol:
>>> super(B).m
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'super' object has no attribute 'm'
>>> super(B).__get__(B(), B)
<super: <class 'B'>, <B object>>
super object bound to instance gives bound methods:
>>> super(B, B()).m
<bound method B.m of <__main__.B object at 0xb765dacc>>
>>> super(B, B()).m()
m
super object bound to class gives function (unbound methods in terms of Python 2):
>>> super(B, B).m
<function m at 0xb761482c>
>>> super(B, B).m()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: m() takes exactly 1 positional argument (0 given)
>>> super(B, B).m(B())
m
See Michele Simionato's "Things to Know About Python Super" blog posts series (1, 2, 3) for more information
A quick note, the new usage of super is outlined in PEP3135 New Super which was implemented in python 3.0. Of particular relevance;
super().foo(1, 2)
to replace the old:
super(Foo, self).foo(1, 2)

Categories

Resources