【问题标题】:Mysterious infinite loop in Python 2.7.9 on Mac OS X 10.10.3Mac OS X 10.10.3 上 Python 2.7.9 中的神秘无限循环
【发布时间】:2015-07-02 20:53:11
【问题描述】:

我写了一个 Python 脚本来解决"Fox, goose and bag of beans puzzle"。我在 ABM(基于代理的模型)中编写了代码。每件需要携带过河的东西都是一个Passenger对象。河边的两块土地也是太空物体。

代码运行良好以解决原始问题。但是,一旦我尝试初始化对象(例如农夫 2、狐狸 2),就会发生无限循环。我的意思是我只是初始化。我从来没有把它们放在实际的模拟中。因此,如果取消注释 num 170(#fox2 = Passenger('fox', 'rooster')) 行,就会发生无限循环。有趣的是,您可以初始化额外的谷物或公鸡,但不能初始化农夫或狐狸。我认为这可能是由于随机模块,所以我尝试用

设置种子
random.seed(some_int)

但它没有解决任何问题。

这是有趣的部分;该代码在 Windows 10 Python 2.7.4 上运行良好。我已经尝试过使用另一台 Mac,但它也会有无限循环。 是 Mac 问题还是 Python 问题?我的代码有什么问题?

没有错误的代码

from sets import Set
import random
from itertools import *

class Passenger(object):
  """ Anything that gets on board on the boat.
  Assumed that there could be multiple captains """
  def __init__(self, species, food=None, is_captain=False):
    self.species = species
    self.food = food
    self.is_captain = is_captain

  def eat(self, something):
    return self.food == something.species

  def __str__(self):
    return "I am %s" % self.species

class Space(object):
  """docstring for """
  def __init__(self, name, residents=[]):
    self.name = name
    self.residents = residents
    self.captains = self.update_captains()

  def num_residents(self):
    return len(self.residents)

  ## e.g. send_off([traveller1, traveller2])
  def send_off(self, passengers):
    ''' Remove the passengers who left for the other land.
    It means that the number of captains in the land is changed. '''
    self.residents = list(Set(self.residents) - Set(passengers))
    self.captains = self.update_captains()

  ## e.g. welcome([sailing_captain, traveller])
  def welcome(self, passengers):
    ''' Append newcomers '''
    self.residents += passengers
    self.captains = self.update_captains()

  def update_captains(self):
    return [r for r in self.residents if r.is_captain]

  def pick_a_captain(self):
    ''' Pick a captain randomly '''
    return random.choice(self.captains)

  def print_resident_species(self):
    ''' Simply print out every species in the land.
    For debug purpose '''
    for r in self.residents:
      print r.species

  def get_resident_species(self):
    ''' e.g. Returns "fox, grain,"
    "fox, grain, peasant" '''
    species = [r.species for r in self.residents]
    return ', '.join(species)

  def __str__(self):
    return self.name + ": " + self.get_resident_species()


''' Stand-alone functions '''
def get_captains(residents):
  return [r for r in residents if r.is_captain]

def is_peaceful_pair(pair):
  ''' e.g. is_peaceful_pair([fox, rooster]) => False '''
  p1 = pair[0]
  p2 = pair[1]
  return not p1.eat(p2) and not p2.eat(p1)

def is_peaceful(residents):
  ''' e.g. is_peaceful([fox, rooster, grain]) => False '''
  for pair in list(permutations(residents, r=2)):
    if not is_peaceful_pair(pair):
      return False
  return True

def select_traveller(from_):
  for t in from_.residents:
      ## Figure out if the rest of the residents will get along
      if is_peaceful(list(Set(from_.residents) - Set([t]))):
        from_.send_off([t])
        return t
  return None

def get_sailing_captain(from_):
  sailing_captain = from_.pick_a_captain()
  from_.send_off([sailing_captain])
  return sailing_captain

## e.g. travel_to_destination(korea, japan)
## If succeeds, return passengers. If not, return None(stop the simulation)
def travel_to_destination(from_, to):
  '''
  Randomly pick one traveller and figures out whether the rest will be safe.
  Loop until find one and if not, this simulation should end.
  '''
  if len(from_.captains) == 0:
    ## No captain, no simulation
    print "There is no captain who can sail a boat :("
    return None
  sailing_captain = get_sailing_captain(from_)

  ## Shuffle the residents list so that you always get a random traveller
  random.shuffle(from_.residents)
  traveller = select_traveller(from_)
  if traveller != None:
    passengers = [sailing_captain, traveller]
    to.welcome(passengers)
    return passengers
  else:
    return None

## e.g. travel_back(japan, korea):
##
def travel_back(from_, to):
  sailing_captain = get_sailing_captain(from_)
  ## Shuffle the residents list so that you always get a random traveller
  if is_peaceful(from_.residents):
    to.welcome([sailing_captain])
    return [sailing_captain]
  else:
    traveller = select_traveller(from_)
    passengers = [sailing_captain, traveller]
    to.welcome(passengers)
    return passengers

def get_passenger_name(passengers):
  return tuple(p.species for p in passengers)

def print_land_info(lands):
  for l in lands:
    print l

peasant = Passenger('human', is_captain=True)
''' IF I UNCOMMENT THE NEXT LINE OUT, THE INFINITE LOOP HAPPENS!!! '''
#fox2 = Passenger('fox', 'rooster')
fox = Passenger('fox', 'rooster')
rooster = Passenger('rooster', 'grain')
#rooster2 = Passenger('rooster', 'grain')
grain = Passenger('grain')
#grain2 = Passenger('grain')


korea = Space('Korea', [peasant, fox, rooster, grain])
japan = Space('Japan')

POPULATION = korea.num_residents()
CAPTAIN = get_captains(korea.residents)
i = 1
while True:
  print "Loop", i
  passengers = travel_to_destination(korea, japan)
  if passengers == None:
    print "The journey can't be continued"
    break
  if japan.num_residents() == POPULATION:
    print "Everyone has crossed the river safely!"
    print_land_info([japan, korea])
    break
  else:
    print "Korea ---> Japan", get_passenger_name(passengers)
    print_land_info([japan, korea])
    passengers = travel_back(japan, korea)
    print "Japan ---> Korea", get_passenger_name(passengers)
    print_land_info([japan, korea])
    print "========================"
  i += 1

无限循环代码

from sets import Set
import random
from itertools import *

class Passenger(object):
  """ Anything that gets on board on the boat.
  Assumed that there could be multiple captains """
  def __init__(self, species, food=None, is_captain=False):
    self.species = species
    self.food = food
    self.is_captain = is_captain

  def eat(self, something):
    return self.food == something.species

  def __str__(self):
    return "I am %s" % self.species

class Space(object):
  """docstring for """
  def __init__(self, name, residents=[]):
    self.name = name
    self.residents = residents
    self.captains = self.update_captains()

  def num_residents(self):
    return len(self.residents)

  ## e.g. send_off([traveller1, traveller2])
  def send_off(self, passengers):
    ''' Remove the passengers who left for the other land.
    It means that the number of captains in the land is changed. '''
    self.residents = list(Set(self.residents) - Set(passengers))
    self.captains = self.update_captains()

  ## e.g. welcome([sailing_captain, traveller])
  def welcome(self, passengers):
    ''' Append newcomers '''
    self.residents += passengers
    self.captains = self.update_captains()

  def update_captains(self):
    return [r for r in self.residents if r.is_captain]

  def pick_a_captain(self):
    ''' Pick a captain randomly '''
    return random.choice(self.captains)

  def print_resident_species(self):
    ''' Simply print out every species in the land.
    For debug purpose '''
    for r in self.residents:
      print r.species

  def get_resident_species(self):
    ''' e.g. Returns "fox, grain,"
    "fox, grain, peasant" '''
    species = [r.species for r in self.residents]
    return ', '.join(species)

  def __str__(self):
    return self.name + ": " + self.get_resident_species()


''' Stand-alone functions '''
def get_captains(residents):
  return [r for r in residents if r.is_captain]

def is_peaceful_pair(pair):
  ''' e.g. is_peaceful_pair([fox, rooster]) => False '''
  p1 = pair[0]
  p2 = pair[1]
  return not p1.eat(p2) and not p2.eat(p1)

def is_peaceful(residents):
  ''' e.g. is_peaceful([fox, rooster, grain]) => False '''
  for pair in list(permutations(residents, r=2)):
    if not is_peaceful_pair(pair):
      return False
  return True

def select_traveller(from_):
  for t in from_.residents:
      ## Figure out if the rest of the residents will get along
      if is_peaceful(list(Set(from_.residents) - Set([t]))):
        from_.send_off([t])
        return t
  return None

def get_sailing_captain(from_):
  sailing_captain = from_.pick_a_captain()
  from_.send_off([sailing_captain])
  return sailing_captain

## e.g. travel_to_destination(korea, japan)
## If succeeds, return passengers. If not, return None(stop the simulation)
def travel_to_destination(from_, to):
  '''
  Randomly pick one traveller and figures out whether the rest will be safe.
  Loop until find one and if not, this simulation should end.
  '''
  if len(from_.captains) == 0:
    ## No captain, no simulation
    print "There is no captain who can sail a boat :("
    return None
  sailing_captain = get_sailing_captain(from_)

  ## Shuffle the residents list so that you always get a random traveller
  random.shuffle(from_.residents)
  traveller = select_traveller(from_)
  if traveller != None:
    passengers = [sailing_captain, traveller]
    to.welcome(passengers)
    return passengers
  else:
    return None

## e.g. travel_back(japan, korea):
##
def travel_back(from_, to):
  sailing_captain = get_sailing_captain(from_)
  ## Shuffle the residents list so that you always get a random traveller
  if is_peaceful(from_.residents):
    to.welcome([sailing_captain])
    return [sailing_captain]
  else:
    traveller = select_traveller(from_)
    passengers = [sailing_captain, traveller]
    to.welcome(passengers)
    return passengers

def get_passenger_name(passengers):
  return tuple(p.species for p in passengers)

def print_land_info(lands):
  for l in lands:
    print l


peasant = Passenger('human', is_captain=True)
peasant2 = Passenger('human', is_captain=True)
''' IF I UNCOMMENT THE NEXT LINE OUT, THE INFINITE LOOP HAPPENS!!! '''
fox2 = Passenger('fox', 'rooster')
fox = Passenger('fox', 'rooster')
rooster = Passenger('rooster', 'grain')
#rooster2 = Passenger('rooster', 'grain')
grain = Passenger('grain')
#grain2 = Passenger('grain')


korea = Space('Korea', [peasant, fox, rooster, grain])
japan = Space('Japan')

POPULATION = korea.num_residents()
CAPTAIN = get_captains(korea.residents)
i = 1
while True:
  print "Loop", i
  passengers = travel_to_destination(korea, japan)
  if passengers == None:
    print "The journey can't be continued"
    break
  if japan.num_residents() == POPULATION:
    print "Everyone has crossed the river safely!"
    print_land_info([japan, korea])
    break
  else:
    print "Korea ---> Japan", get_passenger_name(passengers)
    print_land_info([japan, korea])
    passengers = travel_back(japan, korea)
    print "Japan ---> Korea", get_passenger_name(passengers)
    print_land_info([japan, korea])
    print "========================"
  i += 1

编辑: 我根据@hamstergene 的建议updated code。我修复了

中的错误
travel_back(...)

并添加

__eq__ and __hash__

到乘客()。但是我不确定问题是否已经完全解决。

【问题讨论】:

  • 刚刚在 OSX 上运行,一切正常...... Python 2.7.6 / OSX 10.10.3
  • 是的,无法复制。
  • 感谢 cmets!你们取消注释第 170 行 #fox2 = Passenger('fox', 'rooster') 了吗?你们也可以在第 169 行添加peasant2 = Passenger('human', is_captain=True) 并再试一次吗?
  • OS X 10.10.3 - Python 2.7.6 = 无限循环(当它到达 2111099 时我终于停止了它) - 甚至不需要取消注释该行
  • 我也得到了一个无限循环,但只有当我插入 peasant2 = ... 行时。这对我来说有点神秘,对 SO 来说是一个有价值的问题。 @YOUNG,通过发布一个生成无限循环的代码示例,您可能会在这里获得更多运气。

标签: python macos infinite-loop


【解决方案1】:

无限循环的原因是您的算法中的一个错误:travel_back 没有进行随机洗牌,而是选择了第一个不安全的乘客。如果那恰好是刚刚到达的那个,它就变成了无操作,它会无限期地重复。如果你在那里添加随机洗牌,程序将永远终止:

def travel_back(from_, to):
  sailing_captain = get_sailing_captain(from_)
  ## Shuffle the residents list so that you always get a random traveller
  if is_peaceful(from_.residents):
    to.welcome([sailing_captain])
    return [sailing_captain]
  else:
    random.shuffle(from_.residents)  # <---
    # ....

对创建额外对象的“神秘”依赖的原因是集合和字典依赖于 __hash____eq__ 操作,其默认实现(在自定义类中)仅使用对象的内存地址。

在您的情况下,分配一个额外的对象会更改后续分配的内存地址,这反过来会改变对象在send_off 中的list(Set(...)-Set(...)) 操作之后最终排序的方式,并影响travel_back 将选择哪个乘客。如果不改组,它将始终是同一个对象:要么是好的选择(无循环),要么是坏的选择,具体取决于它们的内存地址。

添加哈希/相等运算符将消除是否有一个额外对象的神秘依赖性,并使程序的行为更具确定性:它要么总是卡在无限循环中(如果你还没有修复 travel_back ),或者永远不会:

class Passenger(object):
  # [...skipped...]
  def __eq__(self, other):
    return self.species == other.species
  def __hash__(self):
    return self.species.__hash__()

【讨论】:

  • 您对travel_back(...) 中的错误是正确的。但是,在这种情况下,内存地址似乎不是问题。我打印出了我在一段时间内创建的所有对象的地址,它们从未改变过。所以散列应该不是问题。此外,即使我自己制作了__hash____eq__,“有时”也会发生无限循环。
  • @YOUNG 你的意思是说即使在travel_back 被修复之后,无限循环仍然会发生?
  • @hamstergene8 现在我已经升级到 10.10.4,即使没有自定义 __eq____hash__travel_back(....) 固定,我也不会得到任何无限循环。奇怪.....:\
  • @hamstergene8 我创建了一个example code 来查看内存地址何时可能发生变化而没有任何变化。你能详细说明什么时候可能会改变吗?
  • @YOUNG 活体的内存地址永远不会改变。在这个程序中,地址的数值本身会影响对象按集合排序的方式,这会产生半随机行为(总是循环或总是终止),具体取决于这些数字恰好是哪些数字(取决于平台、Python 版本)等)
猜你喜欢
  • 2013-09-16
  • 2015-08-29
  • 1970-01-01
  • 2015-06-26
  • 2014-10-23
  • 2015-06-29
  • 2015-07-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多