hasattr telling lies? (AttributeError: 'method' object has no attribute '__annotations__') - python-3.x

The following code
class Foo:
def bar(self) -> None:
pass
foo = Foo()
if hasattr(foo.bar, '__annotations__'):
foo.bar.__annotations__ = 'hi'
crashes with
AttributeError: 'method' object has no attribute '__annotations__'
How can this happen?

The attribute error here is raised because you can't set any attribute on a method object:
>>> foo.bar.baz = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'method' object has no attribute 'baz'
The exception here is perhaps confusing because method objects wrap a function object and proxy attribute read access to that underlying function object. So when attributes on the function exist, then hasattr() on the method will return True:
>>> hasattr(foo.bar, 'baz')
False
>>> foo.bar.__func__.baz = 42
>>> hasattr(foo.bar, 'baz')
True
>>> foo.bar.baz
42
However, you still can't set those attributes via the method, regardless:
>>> hasattr(foo.bar, 'baz')
True
>>> foo.bar.baz = 42
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'method' object has no attribute 'baz'
So, just because the attribute can be read doesn't mean you can set it. hasattr() is speaking the truth, you just interpreted it to mean something different.
Now, if you tried to set the __annotations__ attribute directly on the underlying function object you'd get another error message:
>>> foo.bar.__func__.__annotations__ = 'hi'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __annotations__ must be set to a dict object
You would want to use a dictionary object here:
>>> foo.bar.__func__.__annotations__ = {'return': 'hi'}
>>> foo.bar.__annotations__
{'return': 'hi'}
However, because __annotations__ is a mutable dictionary, it is just easier to directly manipulate the keys and values to that object, which is perfectly feasible to do via the method wrapper:
>>> foo.bar.__annotations__['return'] = 'int'
>>> foo.bar.__annotations__
{'return': 'int'}
Now, if you were hoping to set per instance annotations, you can't get away with setting attributes on method objects, because method objects are ephemeral, they are created just for the call, then usually discarded right after.
You would have to use custom method descriptor objects via a metaclass and re-create the __annotations__ attribute for those each time, or you could instead pre-bind methods with a new function object that would be given their own attributes. You then have to pay a larger memory price:
import functools
foo.bar = lambda *args, **kwargs: Foo.bar(foo, *args, **kwargs)
functools.update_wrapper(foo.bar, Foo.bar) # copy everything over to the new wrapper
foo.bar.__annotations__['return'] = 'hi'
Either way you completely kill important speed optimisations made in Python 3.7 this way.
And tools that operate on the most important use case for __annatotions__, type hints, do not actually execute code, they read code statically and would completely miss these runtime alterations.

You're getting an error. because __annotations__ is a dictionary. If you want to change values you'll have to do it like this:
if hasattr(foo.bar, '__annotations__'):
foo.bar.__annotations__['return'] = 'hi'
This will make the return value of your foo.bar be hi instead of None. The only thing I'm not sure about is how the __annotations__ are protected, not allowing you to change them from a dict to string, but I suppose it's some internal check in the source.
UPDATE
For more control over the signature you can use the inspect module and get the Signature object of your class(or method) and edit it from there. For example
import inspect
sig = inspect.signature(foo.bar)
sig.return_annotation # prints None (before modifying)
sig.replace(return_annotation="anything you want")
More on that here

Related

Why calling method for two times result in TypeError: object is not callable

Define two class based on BaseHandler as below:
class BaseHandler:
def successor(self, successor):
self.successor = successor
class ScoreHandler1(BaseHandler):
pass
class ScoreHandler2(BaseHandler):
pass
Initialize two instances:
h1 = ScoreHandler1()
h2 = ScoreHandler2()
Call successor method first time :
h1.successor(h2)
Now call it second time:
h1.successor(h2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'ScoreHandler2' object is not callable
Why can't call the method more times?
callable(ScoreHandler2)
True
The first time you call h1.successor(h2), you're calling the method named "successor". Inside this method, you set the attribute named "successor" to the object h2.
The second time you call h1.successor(h2), you're calling the attribute named "successor", which you defined previously to h2. Since ScoreHandler2 does not implement __call__, it'll raise an error.
To fix this, avoid naming attributes with the same name as methods.

Get method's name using __getattribute__ without type error

I'm trying to print method's name using __getattribute__
but I get typeerror everytime I called the method, and the method is not excuted, is there anyway to get rid of the type error and have the method excuted ?
class Person(object):
def __init__(self):
super()
def test(self):
print(1)
def __getattribute__(self, attr):
print(attr)
p = Person()
p.test()
The above code gives the error
test
Traceback (most recent call last):
File "test1.py", line 15, in <module>
p.test()
TypeError: 'NoneType' object is not callable
Is there anyway to print the method's name only without giving the error ?
I tried to catch typeError inside __getattribute__ method, but it doesn't work
Another question is, why it says None Type object is not callable here
Thank you !
Ps. I know I can catch the error when I call the method, I mean is there anyway to deal this error inside __getattribute method? since my goal is to print method's name everytime a method is called
Answering your second question first, why is it saying NoneType not callable.
When you call p.test() Python tries to lookup the test attribute of the p instance. It calls the __getattribute__ method that you've overridden which prints 'test' and then returns. Because you're not returning anything it implicitly returns None. p.test is therefore None and calling it gives the error you get.
So how do we fix this? Once you've printed the attribute name, you need to return the attribute you're after. You can't just call getattr(self, attr) or you'll end up in an infinite loop so you have to call the method that would have been called if you hadn't overridden it.
def __getattribute__(self, attr):
print(attr)
return super().__getattribute__(attr) # calls the original method

Why setting attributes of class object instances results in AttributeError?

Why the following code produces an error
>>> object().foo = 'bar'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'object' object has no attribute 'foo'
while the next one works fine?
class A():
pass
A().foo = 'bar'
What is the exact difference between A and object? I believe o().foo = 'bar' leads to setattr('o', 'foo', 'bar') and this in turn results in o.__setattr__('foo', 'bar'). One would expect them to have identic __setattr__ methods since no overriding happens. Yet the outputs are different. Please explain why. What happens behind the scenes?
A similar pattern can be noticed for built-in functions and user-defined ones. I can't set my own attributes for let's say dict but it's perfectly ok to write (lambda:None).foo = 'bar'. What's going on here?

Call function, object attributes, or modules using a list of strings

I have a list of module names:
md = ['md1','md2','md3']
Each modules has a class named object with attribute name:
class object:
name = 'obj'
In the other script, I would like to output all the attribute values of class object from all modules (assumes I had imported it):
for x in md:
print(x.object.name)
What I'm expected is:
# Attribute values from class 'object' from all modules
obj
obj
obj
But when I try this, it gives me this error:
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
AttributeError: 'str' object has no attribute 'name'
format comes under str.
This will work.
('{}'.format(module_name)).split)
It's quite ambiguous what you are trying to achieve. If you are trying to use attribute of the function you need to import it. .format() is used for string str formatting.
import re
re.search('(?<=abc)def', 'abcdef')
This will give a function object.

str object in Python 2.7 doesn't have __iter__, yet it act like iterable. Why?

I was checking out str objects in Python, and I realized that str object in Python 2.7 doesn't have either __iter__() method nor next() method, while in Python 3.0 str objects have __iter__() method, and thus they are iterable. However, I can still use str objects as if they are iterable's in Python 2.7. For example, I can use them in for loops. How does this work?
Simple answer: because iter(s) returns an iterable object.
Longer answer: iter() looks for an __iter__() method, but if it doesn't find one it tries to construct and iterator itself. Any object that supports __getitem__() with integer indices starting at 0 can be used to create a simple iterator. __getitem__() is the function behind list/string indexing operations, eg s[0].
>>> "abc".__iter__()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'str' object has no attribute '__iter__'
>>> iter("abc")
<iterator object at 0x1004ad790>
>>> iter("abc").next()
'a'
See here for details.

Resources