Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
310 views
in Technique[技术] by (71.8m points)

python - How to chain multiple arguments? i.e. add(1)(2)(3) = 6

I'm trying to create a function that chains results from multiple arguments.

def hi(string):
   print(string)<p>
   return hi

Calling hi("Hello")("World") works and becomes Hello World as expected.

the problem is when I want to append the result as a single string, but return string + hi produces an error since hi is a function.

I've tried using __str__ and __repr__ to change how hi behaves when it has not input. But this only creates a different problem elsewhere.

hi("Hello")("World") = "Hello"("World") -> Naturally produces an error.

I understand why the program cannot solve it, but I cannot find a solution to it.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

You're running into difficulty here because the result of each call to the function must itself be callable (so you can chain another function call), while at the same time also being a legitimate string (in case you don't chain another function call and just use the return value as-is).

Fortunately Python has you covered: any type can be made to be callable like a function by defining a __call__ method on it. Built-in types like str don't have such a method, but you can define a subclass of str that does.

class hi(str):
    def __call__(self, string):
        return hi(self + '
' + string)

This isn't very pretty and is sorta fragile (i.e. you will end up with regular str objects when you do almost any operation with your special string, unless you override all methods of str to return hi instances instead) and so isn't considered very Pythonic.

In this particular case it wouldn't much matter if you end up with regular str instances when you start using the result, because at that point you're done chaining function calls, or should be in any sane world. However, this is often an issue in the general case where you're adding functionality to a built-in type via subclassing.

To a first approximation, the question in your title can be answered similarly:

class add(int):    # could also subclass float
    def __call__(self, value):
        return add(self + value)

To really do add() right, though, you want to be able to return a callable subclass of the result type, whatever type it may be; it could be something besides int or float. Rather than trying to catalog these types and manually write the necessary subclasses, we can dynamically create them based on the result type. Here's a quick-and-dirty version:

class AddMixIn(object):
     def __call__(self, value):
         return add(self + value)

def add(value, _classes={}):
    t = type(value)
    if t not in _classes:
        _classes[t] = type("add_" + t.__name__, (t, AddMixIn), {})
    return _classes[t](value)

Happily, this implementation works fine for strings, since they can be concatenated using +.

Once you've started down this path, you'll probably want to do this for other operations too. It's a drag copying and pasting basically the same code for every operation, so let's write a function that writes the functions for you! Just specify a function that actually does the work, i.e., takes two values and does something to them, and it gives you back a function that does all the class munging for you. You can specify the operation with a lambda (anonymous function) or a predefined function, such as one from the operator module. Since it's a function that takes a function and returns a function (well, a callable object), it can also be used as a decorator!

 def chainable(operation):

     class CallMixIn(object):
         def __call__(self, value):
             return do(operation(self, value))

     def do(value, _classes={}):
         t = type(value)
         if t not in _classes:
             _classes[t] = type(t.__name__, (t, CallMixIn), {})
         return _classes[t](value)

     return do

 add = chainable(lambda a, b: a + b)

 # or...
 import operator
 add = chainable(operator.add)

 # or as a decorator...
 @chainable
 def add(a, b): return a + b

In the end it's still not very pretty and is still sorta fragile and still wouldn't be considered very Pythonic.

If you're willing to use an additional (empty) call to signal the end of the chain, things get a lot simpler, because you just need to return functions until you're called with no argument:

def add(x):
    return lambda y=None: x if y is None else add(x+y)

You call it like this:

add(3)(4)(5)()    # 12

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...