tags:

views:

244

answers:

8

e.g. so that these would both work - is it possible?

(val,VAL2) = func(args) 
val = func(args)

Where val is not a tuple

For example I'd like these to work for my custom object something

for item in something:
    do_item(item) #where again item - is not a tuple

for (item,key) in something:
    do_more(key,item)

I thought that I need to implement next() function in two different ways...

edit: as follows from the answers below, this should not really be done.

+4  A: 

Have you tried that? It works.

def myfunction(data):
    datalen = len(data)
    result1 = data[:datalen/2]
    result2 = data[datalen/2:]
    return result1, result2


a, b = myfunction('stuff')
print a
print b

c = myfunction('other stuff')
print c

In fact there is no such thing as "return signature". All functions return a single object. It seems that you are returning more than one, but in fact you wrap them into a container tuple object.

nosklo
thanks for the answer!
Evgeny
tried to clarify my question, i think it's still open. c is a tuple in your answer. Maybe I should rephrase the title itself, but what I really want - is be able to iterate through keys or key,value pairs. I thought that non-exising "return signature" is the solution.
Evgeny
+1  A: 

It's possible only if you're happy for val to be a 2-item tuple (or if args need not be the same in the two cases). The former is what would happen if the function just ended with something like return 23, 45. Here's an example of the latter idea:

def weirdfunc(how_many_returns):
  assert 1 <= how_many_returns <= 4
  return 'fee fie foo fum'.split()[:how_many_returns]

var1, var2 = weirdfunc(2)  # var1 gets 'fee', var2 gets 'fie'

var, = weirdfunc(1)  # var gets 'fee'
Alex Martelli
+2  A: 

Update:

Given the example use case, I'd write different generators to handle the cases:

class Something(object): 
   def __init__(self): 
       self.d = {'a' : 1, 
               'b' : 2, 
               'c' : 3} 

   def items(self): 
       for i in self.d.values(): 
           yield i 

  def items_keys(self): 
      for k,i in self.d.items(): 
          yield i,k 

something = Something()

for item in something.items():
....:     print item
....: 
1
3
2

for item,key in something.items_keys():
....:     print key, " : ", item
....: 
a  :  1
b  :  2
c  :  3

Or

You can return a tuple:

In [1]: def func(n):
   ...:     return (n, n+1)
   ...: 

In [2]: a,b = func(1)

In [3]: a
Out[3]: 1

In [4]: b
Out[4]: 2

In [5]: x = func(1)

In [6]: x
Out[6]: (1, 2)
Doug
+1  A: 

Yes, both would work. In the first example, val1 and val2 would have the two values. In the second example, val would have a tuple. You can try this in your python interpreter:

>>> def foo():
...   return ( 1, 2 )
...
>>> x = foo()
>>> (y,z) = foo()
>>> x
(1, 2)
>>> y
1
>>> z
2
Buddy
+1  A: 

Yes it's doable:

def a(b):
if b < 5:
 return ("o", "k")
else:
 return "ko"

and the result:

>>> b = a(4)
>>> b
('o', 'k')
>>> b = a(6)
>>> b
'ko'

I think the thing after is to be careful when you will use the values returned...

e-Jah
+2  A: 
>>> def func(a,b):
      return (a,b)

>>> x = func(1,2)
>>> x
(1, 2)
>>> (y,z) = func(1,2)
>>> y
1
>>> z
2

That doesn't really answer your question. The real answer is that the left side of the assignment doesn't affect the returned type of the function and can't be used to distinguish between functions with different return types. As noted in other answers, the function can return different types from different return statements but it doesn't know what's on the other side of the equals sign.

In the case of this function, it returns a tuple. If you assign it to x, x has the value of the tuple. (y, z) on the left side of the assignment is "tuple unpacking". The tuple returned by func() is unpacked into y and z.

Mark Lutton
thanks your answer sounds good, I guess I should have been more clear when asking. - I dont't want a tuple to be returned to x=f(), but first item
Evgeny
This can be done to some extent in C++ by overloading the = operator. You define operator=() in such a way that if x is an int and y is a certain class, it converts y in some way to an int. This works because x must be declared of some type. In Python there is no way to declare that x should be of some type or other. You could wrap a call to f() in a function that returns f() if it's a scalar and f()[0] otherwise.
Mark Lutton
maybe it's a good thing that return values are not typed in python. thanks.
Evgeny
A: 

This is asking for major confusion. Instead you can follow dict with separate keys, values, items, etc. methods, or you can use a convention of naming unused variables with a single underscore. Examples:

for k in mydict.keys(): pass
for k, v in mydict.items(): pass

for a, b in myobj.foo(): pass
for a, _ in myobj.foo(): pass
for _, b in myobj.foo(): pass

for _, _, _, d in [("even", "multiple", "underscores", "works")]:
    print(d)

for item in something: # or something.keys(), etc.
    do_item(item)

for item, key in something.items():
    do_more(key, item)

If this doesn't fit your function, you should refactor it as two or more functions, because it's clearly trying to fulfill two or more different goals.

Roger Pate
+5  A: 

If you mean, can the function act differently based on the return types the caller is expecting, the answer is no (bar seriously nasty bytecode inspection). In this case, you should provide two different iterators on your object, and write something like:

for item in something:  # Default iterator: returns non-tuple objects
    do_something(item)

for (item,key) in something.iter_pairs(): # iter_pairs returns different iterator
    do_something_else(item, key)

eg. see the dictionary object, which uses this pattern. for key in mydict iterates over the dictionary keys. for k,v in mydict.iteritems() iterates over (key, value) pairs.

[Edit] Just in case anyone wants to see what I mean by "seriously nasty bytecode inspection", here's a quick implementation:

import inspect, opcode

def num_expected_results():
    """Return the number of items the caller is expecting in a tuple.

    Returns None if a single value is expected, rather than a tuple.
    """
    f = inspect.currentframe(2)
    code = map(ord, f.f_code.co_code)
    pos = f.f_lasti
    if code[pos] == opcode.opmap['GET_ITER']: pos += 1 # Skip this and the FOR_ITER
    if code[pos] > opcode.EXTENDED_ARG: pos +=5
    elif code[pos] > opcode.HAVE_ARGUMENT: pos +=3
    else: pos += 1
    if code[pos] == opcode.opmap['UNPACK_SEQUENCE']:
        return code[pos+1] + (code[pos+2] << 8)
    return None

Usable something like:

class MagicDict(dict):
    def __iter__(self):
        if num_expected_results() == 2:
            for k,v in self.iteritems():
                yield k,v
        else:
            for k in self.iterkeys(): 
                yield k

d=MagicDict(foo=1, bar=2)

print "Keys:"
for key in d:
    print "   ", key
print "Values"    
for k,v in d:
    print "   ",k,v

Disclaimer: This is incredibly hacky, insanely bad practice, and will cause other programmers to hunt you down and kill you if they ever see it in real code. Only works on cpython (if that). Never use this in production code (or for that matter, probably any code).

Brian
"bar seriously nasty bytecode inspection": Is it wrong that this phrase immediately makes me want to put together a proof of concept? :)
Miles
Do it do it do it do it do it do it!
Mark Lutton
@Miles: I have to admit, I have the same thought. I'm pretty sure it is indeed wrong to want to do so, but there's something neat about a really nasty hack. Hence, see above.
Brian