Reputation: 2764
Is there any way to determine the difference between a method and an attribute call using __getattr__?
I.e. in:
class Bar(object):
def __getattr__(self, name):
if THIS_IS_A_METHOD_CALL:
# Handle method call
def method(**kwargs):
return 'foo'
return method
else:
# Handle attribute call
return 'bar'
foo=Bar()
print(foo.test_method()) # foo
print(foo.test_attribute) # bar
The methods are not local so it's not possible to determine it using getattr/callable. I also understand that methods are attributes, and that there might not be a solution. Just hoping there is one.
Upvotes: 16
Views: 3398
Reputation: 5243
To extend Martijn's answer a little bit:
This won't work in every situation, but you could return another instance of itself in order to have a bit more control over what happens afterwards.
This also allows attributes and calls to be chained.
And you can implement more methods than __str__
in order to provide different representations.
class CallableValue():
name = None
called = False
args = []
kwargs = {}
def __init__(self, name = None, called=False, args=[], kwargs={}):
self.name = name or "default"
self.called = called
self.args = args
self.kwargs = kwargs
def __call__(self, *args, **kwargs):
return CallableValue(self.name, True, args, kwargs)
def __getattr__(self, name):
return CallableValue(name)
def __str__(self):
return self.name if not self.called else f"called {self.name} with {self.args} and {self.kwargs}"
t = CallableValue()
print(t)
print(t.attr)
print(t.something)
print(t.something1.something2.something3)
print(t.method())
print(t.other_method("value", key="value"))
print(t.something.method2("value"))
print(t.method1().method2().method3(key="value"))
Result:
default
attr
something
something3
called method with () and {}
called other_method with ('value',) and {'key': 'value'}
called method2 with ('value',) and {}
called method3 with () and {'key': 'value'}
Upvotes: 0
Reputation: 663
It is not possible to know whether an attribute is subsequently called from __getattr__
, which Martijn Pieters explains.
Although it does not determine whether an attribute is called, it is possible to know whether an attribute can be called with callable
. Another way is to use type
to keep track of the various objects, or make a list of attribute names.
class Foo(object):
bar_attribute = 'callable'
def __getattr__(self, name):
instanceOrValue = getattr(self, "bar_%s" %name)
if callable(instanceOrValue):
# Handle object that can be called
def wrap(**kwargs):
return "is %s" %instanceOrValue(**kwargs)
return wrap
# Handle object that can not be called
return 'not %s' %instanceOrValue
def bar_method(self, **kwargs):
return 'callable';
foo=Foo()
print(foo.method()) # is callable
print(foo.attribute) # not callable
__getattr__
can only keep track of certain things, but it can be the right solution in many situations because change calling (__call__
) has an impact on calling in all situations and not only when the Foo class is used.
Difference between calling a method and accessing an attribute
Python __call__ special method practical example
Upvotes: 3
Reputation: 88977
In short, no, there is no reliable way - the issue is that a method is an attribute in Python - there is no distinction made. It just happens to be an attribute that is a bound method.
You can check if the attribute is a method, but there is no guarantee that means it will be called, e.g:
class Test:
def test(self):
...
Test().test # This accesses the method, but doesn't call it!
There is no way for the call accessing the function to know if it's going to be called when it is returned - that's a future event that hasn't yet been processed.
If you are willing to assume that a method being accessed is a method being called, you can determine that it is a method being accessed with a check like this:
hasattr(value, "__self__") and value.__self__ is self
Where value
is the attribute you want to check to see if it is a method or some other attribute, and self
is the instance you want to see if it's a method for.
If you need something to happen when it is called, you could use this moment to decorate the function.
An solid code example of this can be found here.
Upvotes: 0
Reputation: 1121148
You cannot tell how an object is going to used in the __getattr__
hook, at all. You can access methods without calling them, store them in a variable, and later call them, for example.
Return an object with a __call__
method, it'll be invoked when called:
class CallableValue(object):
def __init__(self, name):
self.name = name
def __call__(self, *args, **kwargs):
print "Lo, {} was called!".format(self.name)
class Bar(object):
def __getattr__(self, name):
return CallableValue(name)
but instances of this will not be the same thing as a string or a list at the same time.
Demo:
>>> class CallableValue(object):
... def __init__(self, name):
... self.name = name
... def __call__(self, *args, **kwargs):
... print "Lo, {} was called!".format(self.name)
...
>>> class Bar(object):
... def __getattr__(self, name):
... return CallableValue(name)
...
>>> b = Bar()
>>> something = b.test_method
>>> something
<__main__.CallableValue object at 0x10ac3c290>
>>> something()
Lo, test_method was called!
Upvotes: 14