代码之家  ›  专栏  ›  技术社区  ›  Chris B. Behrens

如何在python中用可选的返回值签名对函数进行编程?(用于可选迭代器的next())

  •  0
  • Chris B. Behrens  · 技术社区  · 5 年前

    例如,这样这两种方法都能奏效——有可能吗?

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

    在哪里? 瓦尔 非元组

    例如,我希望这些可以用于我的自定义对象 某物

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

    我认为我需要以两种不同的方式实现next()函数…

    编辑: 从下面的答案来看,这不应该真正做到。

    8 回复  |  直到 15 年前
        1
  •  6
  •   Peter S. Housel    15 年前

    如果您的意思是,根据调用方期望的返回类型,函数的行为是否会有所不同,那么答案是“否”(除了严重讨厌的字节码检查)。在这种情况下,应该在对象上提供两个不同的迭代器,并编写如下内容:

    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)
    

    参见字典对象,它使用这个模式。 for key in mydict 迭代字典键。 for k,v in mydict.iteritems() 迭代(键、值)对。

    [编辑] 为了防止有人想知道我所说的“严重讨厌的字节码检查”是什么意思,下面是一个快速的实现:

    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
    

    有用的,比如:

    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
    

    免责声明: 这是一个令人难以置信的黑客行为,非常糟糕的做法,以及 如果其他程序员在真正的代码中看到它,他们会追捕你并杀死你。只对cpython有效(如果那样的话)。 从未 在生产代码中使用此代码(或者就此而言,可能是任何代码)。

        2
  •  4
  •   nosklo    15 年前

    你试过了吗?它起作用了。

    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
    

    实际上,没有所谓的“返回签名”。所有函数都返回一个对象。看起来您返回了不止一个,但实际上您将它们包装到一个容器元组对象中。

        3
  •  2
  •   e-Jah    15 年前

    是的,这是可行的:

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

    结果:

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

    我想后面的事情是要小心,当你将使用返回的值…

        4
  •  2
  •   Mark Lutton    15 年前
    >>> def func(a,b):
          return (a,b)
    
    >>> x = func(1,2)
    >>> x
    (1, 2)
    >>> (y,z) = func(1,2)
    >>> y
    1
    >>> z
    2
    

    这并不能真正回答你的问题。真正的答案是,赋值的左侧不会影响函数的返回类型,也不能用于区分具有不同返回类型的函数。如其他答案所述,函数可以从不同的返回语句返回不同的类型,但它不知道等号的另一侧是什么。

    对于这个函数,它返回一个元组。如果将它赋给x,x就具有元组的值。(y,z)在任务的左侧是“tuple解包”。func()返回的元组被解包到y和z中。

        5
  •  2
  •   Federico Builes    15 年前

    更新:

    对于这个示例用例,我将编写不同的生成器来处理这些用例:

    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
    

    您可以返回一个元组:

    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)
    
        6
  •  1
  •   Buddy    15 年前

    是的,两者都可以。在第一个例子中,val1和val2有两个值。在第二个例子中,val有一个元组。您可以在python解释器中尝试:

    >>> def foo():
    ...   return ( 1, 2 )
    ...
    >>> x = foo()
    >>> (y,z) = foo()
    >>> x
    (1, 2)
    >>> y
    1
    >>> z
    2
    
        7
  •  1
  •   Bob Dizzle    15 年前

    只有当你很高兴 val 为2项元组(或如果 args 两种情况下不必相同)。前者是如果函数以类似于 return 23, 45 . 下面是后一种想法的一个例子:

    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'
    
        8
  •  0
  •   Roger Pate    15 年前

    这要求有重大的混乱。相反,你可以跟着 dict 使用单独的键、值、项等方法,或者您可以使用一种约定,用一个下划线来命名未使用的变量。实例:

    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)
    

    如果这不适合您的函数,您应该将其重构为两个或多个函数,因为它显然试图实现两个或多个不同的目标。