我需要获得连续数字,而输入数字不会改变。
所以我得到给(5) - > 1,给(5) - > 2,依此类推,然后:再次给(6) - > 1,开始计数。
到目前为止,我用迭代器函数count()和函数给(num)解决了这个问题:
def count(start=1):
n=start
while True:
yield n
n +=1
def give(num):
global last
global a
if num==last:
ret=a.next()
else:
a=count()
ret=a.next()
last=num
return ret
它有效,但它的丑陋:我有两个全局变量,在调用give(num)之前必须设置它们。我希望能够调用give(num)而不先设置'a = count()'和'last = 999'变量。我很肯定有更好的方法来做到这一点......
编辑:所有人都非常快速和多样化的回应,我在这里学习很多......
答案 0 :(得分:3)
显而易见的事情是将give
变为对象而不是函数。*通过定义__call__
方法可以使任何对象可调用。
虽然我们正在使用它,但您的代码可以简化一些,所以让我们这样做。
class Giver(object):
def __init__(self):
self.last, self.a = object(), count()
def __call__(self, num):
if num != self.last:
self.a = count(1)
self.last = num
return self.a.next()
give = Giver()
所以:
>>> give(5)
1
>>> give(5)
2
>>> give(6)
1
>>> give(5)
1
如果您有任何需要,还可以创建多个独立的提供者,每个提供者都有自己独立的当前状态。
如果要使用更多状态扩展它,状态只会进入实例变量。例如,您可以将last
和a
替换为将先前看到的值映射到计数器的字典:
class Giver(object):
def __init__(self):
self.counters = defaultdict(count)
def __call__(self, num):
return next(self.counters[num])
现在:
>>> give(5)
1
>>> give(5)
2
>>> give(6)
1
>>> give(5)
3
*我在这里跳了一步。你总是可以通过在函数或其他范围内放置变量和使用它们的所有东西(可能只是一个函数)来删除全局变量,因此它们最终成为函数闭包中的自由变量。但在你的情况下,我认为这只会使你的代码看起来“更丑”(在同样意义上你认为它很难看)。但要记住,对象和闭包在它们能做的事情上实际上是等价的,但它们看起来有所不同 - 所以当一个看起来非常丑陋时,试试另一个。
答案 1 :(得分:2)
只需跟踪每个输入的最后返回值。你可以用一个普通的词典做到这一点:
_counter = {}
def give(n):
_counter[n] = _counter.get(n, 0) + 1
return _counter[n]
标准库有一个Counter
class,可以让事情变得更简单:
import collections
_counter = collections.Counter()
def give(n):
_counter[n] += 1
return _counter[n]
collections.defaultdict(int)
也有效。
答案 2 :(得分:2)
你可以通过以下方式实现这一目标:
def count(start=1):
n = start
while True:
yield n
n += 1
def give(num):
if num not in give.memo:
give.memo[num] = count()
return next(give.memo[num])
give.memo = {}
产生:
>>> give(5)
1
>>> give(5)
2
>>> give(5)
3
>>> give(6)
1
>>> give(5)
4
>>>
这两个关键点是使用dict同时跟踪多个迭代器,并在函数本身上设置变量。你可以这样做,因为函数本身就是python中的对象。这相当于C中的静态局部变量。
答案 3 :(得分:2)
基本上,您可以通过defaultdict
和itertools.count
from collections import defaultdict
from itertools import count
_counters = defaultdict(count)
next(_counters[5])
Out[116]: 0
next(_counters[5])
Out[117]: 1
next(_counters[5])
Out[118]: 2
next(_counters[5])
Out[119]: 3
next(_counters[6])
Out[120]: 0
next(_counters[6])
Out[121]: 1
next(_counters[6])
Out[122]: 2
如果您需要从一个计数器开始,您可以通过functools.partial
:
from functools import partial
_counters = defaultdict(partial(count,1))
next(_counters[5])
Out[125]: 1
next(_counters[5])
Out[126]: 2
next(_counters[5])
Out[127]: 3
next(_counters[6])
Out[128]: 1
答案 4 :(得分:1)
添加第二个答案,因为这与我的第一个答案截然不同。
你基本上要完成的是 coroutine - 一个保留状态的生成器,在任意时间,可以发送值。 PEP 342为我们提供了一种使用" yield表达式"来实现这一目标的方法。我会直接看看它的样子:
from collections import defaultdict
from itertools import count
from functools import partial
def gen(x):
_counters = defaultdict(partial(count,1))
while True:
out = next(_counters[x])
sent = yield out
if sent:
x = sent
如果_counters
行令人困惑,请参阅我的其他答案。
使用协程,您可以将数据发送到生成器。所以你可以做以下的事情:
g = gen(5)
next(g)
Out[159]: 1
next(g)
Out[160]: 2
g.send(6)
Out[161]: 1
next(g)
Out[162]: 2
next(g)
Out[163]: 3
next(g)
Out[164]: 4
g.send(5)
Out[165]: 3
注意发生器如何保持状态并可以随意在计数器之间切换。
答案 5 :(得分:1)
在我的第一个回答中,我建议一种解决方案是将闭包转换为对象。但我跳过了一步 - 你使用全局变量,而不是闭包,这是你不喜欢它的一部分。
这是将任何全局状态转换为封装状态的简单方法:
def make_give():
last, a = None, None
def give(num):
nonlocal last
nonlocal a
if num != last:
a = count()
last=num
return a.next()
return give
give = make_give()
或者,调整Giver
的最终版本:
def make_giver():
counters = defaultdict(count)
def give(self, num):
return next(counters[num])
return give
如果你很好奇这是如何运作的:
>>> give.__closure__
(<cell at 0x10f0e2398: NoneType object at 0x10b40fc50>, <cell at 0x10f0e23d0: NoneType object at 0x10b40fc50>)
>>> give.__code__.co_freevars
('a', 'last')
这些cell
对象实际上是对创建make_give
函数的give
调用的堆栈帧的引用。
这在Python 2.x和3.x中并不总是相同。虽然闭包单元的工作方式相同,但是如果在函数体内分配一个变量并且没有global
或nonlocal
语句,它会自动变为本地变量,而Python 2没有nonlocal
语句。所以,第二个版本运行正常,但对于第一个版本,您必须执行state = {'a': None, 'last': None}
之类的操作,然后编写state['a'] = count
而不是a = count
。
这个技巧 - 创建一个隐藏局部变量的闭包 - 在其他一些语言中非常常见,比如JavaScript。在Python中(部分原因是因为没有nonlocal
语句的历史很长,部分原因是因为Python有其他语言没有的替代方案),所以它不太常见。将状态存储在可变的默认参数值或函数的属性中,或者,如果有合理的类使函数成为类的实例的属性,则通常更惯用。有很多情况下闭包是 pythonic,这通常不是其中之一。