『社会』也会是一个 类 (class)
,它应该是由很多 Human
组成的。
在一个 society
中,不同的 human
之间会不断发生 交锋
,并最终能统计出结果。
在全局的变量中,我们设定了 MILESTORE_TIMES
,表示一个 human
一生中与他人交锋的次数 (微观)。但在实施的过程中,则是另外的逻辑,认为整个社会要发生 MILESTORE_TIMES & 人类总数
两者相乘的交锋总数 (宏观),虽然并不严谨,但这样在代码的实现上会简单很多。
我们准备了 3 个列表,来区分 humans,分别为 all_humans、living_humans、dead_humans
,而且逻辑上 living_humans + dead_humans = all_humans
。另外,还有一个叫 init_humans
的列表,用于保留最开始人类的情况,它与 all_humans
是不同的,all_humans
中的 human
会因为交锋行为的存在,而改变自我属性、成员发生增减,init_humans
则是没有变化的。
Human
会实例化 2 次,各自添加到 init_humans
与 all_humans
中。有些初学者会很自然地只实例化 1 次,同时添加到了 init_humans
、all_humans
,而在最终统计信息的时候,却发现 init_humans
最初添加的 human
通通发生了变化,失去了记住初始状态的价值。具体的原因,可以温故知新,再去翻看《FirstWeb》在基础知识介绍中对 变量 的说明。
class Society(object):
def __init__(self, init_human_number=INIT_HUMAN_NUMBER,
pigeon_score_range=DEFAULT_PIGEON_SCORE_RANGE):
self.init_humans = []
self.all_humans = []
self.living_humans = []
self.dead_humans = []
for i in range(init_human_number):
min_score, max_score = pigeon_score_range
score = random.randint(min_score*1000, max_score*1000)/1000.
human = Human(pigeon_score=score)
inited_human = Human(pigeon_score=score)
self.living_humans.append(human)
self.all_humans.append(human)
self.init_humans.append(inited_human)
def check_dead_humans(self, *humans):
for human in humans:
if human.is_dead:
self.dead_humans.append(human)
try: self.living_humans.remove(human)
except: pass
def do_humans_meeting(self, meeting_times=None,
life_meeting_times=MILESTORE_TIMES):
t1 = time.time()
if meeting_times is None:
meeting_times = life_meeting_times * len(self.living_humans)
for i in range(meeting_times):
if len(self.living_humans) < 2:
print('humans<2, meeting_times %s/%s' % (i+1, meeting_times))
break
two_humans = random.sample(self.living_humans, 2)
one, two = two_humans
one.meet(two)
self.check_dead_humans(*two_humans)
t2 = time.time()
print('program costed seconds: %s' % (t2 - t1))
@staticmethod
def get_humans_info(humans):
pigeon_humans = []
pigeon_money = 0
eagle_money = 0
for human in humans:
if human.is_pigeon:
pigeon_humans.append(human)
if human.money > 0: pigeon_money += human.money
else:
if human.money > 0: eagle_money += human.money
total = len(humans)
pigeon_percent = round(len(pigeon_humans)/float(total), 3)
pigeon_percent *= 100
info = 'total {0}, {1}% are pigeon; money: pigeons {2}, eagles {3}'
info = info.format(total, pigeon_percent, pigeon_money, eagle_money)
return info
@staticmethod
def get_humans_death_info(humans):
all_count = float(len(humans)) or 0.000001
all_dead_count = 0.000001
all_pigeon_count = 0.0
dead_pigeon_count = 0.000001
for human in humans:
if human.is_pigeon:
all_pigeon_count += 1
if human.is_dead:
all_dead_count += 1
if human.is_pigeon: dead_pigeon_count += 1
dead_percent = round(all_dead_count/all_count*100)
dead_pigeon_percent = round(dead_pigeon_count/all_dead_count*100)
info = '{0} humans, {1}% dead, {2}% are pigeon'
info = info.format(all_count, dead_percent, dead_pigeon_percent)
return info
def print_info(self):
print('init status: %s' % self.get_humans_info(self.init_humans))
print('last status: %s' % self.get_humans_info(self.living_humans))
print('death: %s' % self.get_humans_death_info(self.all_humans))
PEP 的全称为 Python Enhancement Proposals,相当于 Python 的代码规范。
我的意见是: 可以遵循,也可以不遵循;也可以只遵循一部分,其它就按照自己的心意来。但具体采取什么样的策略,要视情况而言,有些时候还要看自己所在的 (开发) 团队。
比如下面的代码是不符合 PEP 的:
for human in humans:
if human.is_pigeon:
if human.money > 0: pigeon_money += human.money
要写成如下才可以 (if 应该要单独一行):
for human in humans:
if human.is_pigeon:
if human.money > 0:
pigeon_money += human.money
而我个人的偏好则会考虑如果当前 if
是独立的,不需要 elif
、 else
的补充,而且逻辑简单的,就直接写在一行之内,这样处理的两个好处:
一个值的返回,我们应该要清晰它的类型,虽然 Python 作为动态语言,并不强制声明类型。
random.randint(min_score*1000, max_score*1000)/1000. # 随机整数
round(len(pigeon_humans)/float(total), 3) # 四舍五入
random.randint
返回整数,而我们需要一个小于 1 的浮点数 (小数),于是通过上面示例代码的方式获得。
round
则是一个四舍五入用的函数,上面代码中,保留了 3 位小数点,这样在屏幕输出的时候,看起来友好一些。
try、except
是 Python 中常用的语法,try
某个代码段,如果出错了,则被 except
捕获进行再处理。
在本文的源码中,有下面这一段代码:
try: self.living_humans.remove(human)
except: pass
实际也就是:
try:
self.living_humans.remove(human)
except:
pass
这是一个防御性写法,如果 human
已经不在 living_humans
中了,再去 remove
就会抛出错误 (异常) 导致整个程序无法运行下去。此时,如果真的出现这个错误,并不需要特殊处理,直接忽略掉就可以了,所以用了 except: pass
这样空代码的逻辑。
有些人认为在 Python 中,应该避免错误(异常)的出现,并且尽可能针对异常做对应;也有人认为如果有异常,不用去处理,让它抛出来,刚好可以进行修正,说明代码内没有处理好。
都有一定的道理。以后,你或许会接触到更多公说公有理、婆说婆有理的其它讨论。其实,即使作为初学者,也可以形成自己的看法。大多数别人的看法,在具体到某一行代码的时候,都是有局限性的。
对了,防御性写法有什么用吗?
这是一种冗余的逻辑,在某个局部我们很清楚代码的逻辑,但一个项目的代码变得复杂的时候,牵一发而动全身,可能就会产生各种细小的错误。这些错误本身无伤大雅,一旦抛出异常可能又会导致整个程序卡住、退出。所以,时间久了,你也会很自然地在某些地方,顺手写一些看起来画蛇添足的代码,作用是为了避免未来潜在的、无价值的异常排查。
一个列表,比如是 my_list = ['a', 'b']
,那么,它的第一个元素是 a
。
我们在代码中,是不是用 my_list[1] == 'a'
(list 用 [?] 来取指定位置的元素) 来表示 my_list
的第一个元素呢?
如果以前从未接触过代码的同学,会觉得这样取第几个元素的逻辑,是非常自然的,第 1 个元素就是 [1]
,第二个则是 [2]
,如此类推。但这是错误的。
第一个元素的序号不是 1
,而是 0
。
my_list[0] == 'a'
以及 my_list[1] == 'b'
才是正确的, my_list[2]
则会抛出异常,它代表第 3 个元素的意思,而整个 my_list
总共才 2 个元素。
在本文的源代码中,有这么一段:
info = 'total {0}, {1}% are pigeon; money: pigeons {2}, eagles {3}'
info = info.format(total, pigeon_percent, pigeon_money, eagle_money)
这是字符串
类型常用的格式化(format) 方法,表示将原始内容中的一些特殊的字符替换为指定的参数,比如 {0}
代表第一个参数、{1}
则表示第二个参数。
不但可以是第几个参数
,还可以指定参数的名称,比如:
>>> template = '{0} {my_var}'
# 这是错误的,因为没有给出 my_var
>>> template.format('hello', 'world')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'my_var'
>>> template.format('hello', my_var='world')
'hello world'
format
这个函数其实接受的是 动态参数,定义它的逻辑,类似如下:
def format(self, *args, **kwargs):
pass
一般我们 def
(定义) 一个函数的时候,具体的参数名称都已指定。另外,在 Python 中,以 *
开头的,表示接受一个动态的列表
,而 **
则表示一个动态的字典
。下面这个代码示范,可能更容易理解:
>>> def test(*args, **kwargs):
... print('args: %s, kwargs: %s' % (args, kwargs))
...
>>> test(1, 2, 3, hello='world', quan='duan')
args: (1, 2, 3), kwargs: {'quan': 'duan', 'hello': 'world'}
>>>