Python API - pattern for creating optional parenthesis?
I am designing the API for a Python library, and have run into a situation where I think my imagination might have overtaken Python's considerable abilities.
(I want to apologize to any Pythonistas who may get offended by the Ruby-ness of my design)
Basically, there is a class with a few methods on it, each of which returns self
, so that they can be chained together. So:
>>> a = MyKlass()
>>> b = a.method_one()
>>> c = b.method_two()
would be the same as
>>> a = MyKlass()
>>> c = a.method_one().method_two()
My question is whether it is possible to make the parenthesis optional. I have learned a bit about using __getattr__
and __call__
to manipulate callable objects, but haven't been able to fully implement this. Right now, I have the class's __getattr__
checking for the attribute, and then determining whether the name
is a method or attribute (or more accurately, whether it is callable or not).
My problem is that if it is a callable, I need __getattr__
to do something like this:
>>> class MyKlass(object):
>>> def __getattr__(self, name):
>>> # pseudocode, you get the idea...
>>> if name is callable:
>>> def callme(*args, **kwds):
>>> # do stuff
>>> return callme
>>> else:
>>> # just return the attribute
So it returns a callable object. This means, though, that I couldn't make the parenthesis optional, since the callable object would be returned, but never called:
>>> a = MyKlass()
>>> c = a.method_one.method_two # AttributeError, since a bound function object does not have attribute "method_two"
The only way I thought of to do this would be to be able to "look ahead" in the code and determine whether the name
is followed by parenthesis or not. Then, if name
was callable but not called, I could return the results of calling name
without arguments ins开发者_JAVA技巧tead of returning a callable object.
I am thinking that this is probably not doable, but I thought that I would ask the gurus anyway.
I think you're nuts for wanting to do this. What reason do you have? What about methods that take arguments? (See example below.)
It seems to work for methods without arguments if you use __getattribute__
instead of __getattr__
.
Working example:
class Test(object):
def __getattribute__(self, name):
attr = super(Test, self).__getattribute__(name)
if callable(attr):
return attr()
return attr
def f(self):
print 'f'
return self
def g(self):
print 'g'
return self
def h(self, x):
print x
return self
t = Test()
t.f.g # Works fine
t.f().g # Fails - the parens aren't optional, they must be left off now
t.f.g.h(1) # Fails - unable to provide an argument
To get around the argument issue, you could inspect the function and only call it if it doesn't take any arguments (other than self). See: http://docs.python.org/library/inspect.html
Do yourself a favor and stop going down this path.
Without the parentheses, you have a reference to the function, with them you have the result of its invocation. This is a fundamental part of Python's syntax (this consistency is a good thing!).
You can always use Python's property
function (or the associated decorator) to make a method work under the hood as an alias for a property which will achieve what you want. However, this suggests you are writing very side-effect-heavy code which is a Bad Idea.
Chaining like this is popular in jQuery because jQuery's sole purpose is to cause side effects in the DOM. Typically this isn't a sensible elsewhere because it makes your application very difficult to debug and test.
精彩评论