首先,『人类』应该是一个 类 (class),而不是一个 函数。
类 像是一个整理用的空间
(NameSpace),可以有自己的子函数
、子属性
。其实,子函数
本身也是一个子属性
,记得我们在 《FirstWeb》中反复强调的,函数也是一种变量。
暂停下:我们要构建一个『人类』的类
,回顾下 鹰鸽博弈,如果是你,会如何设计这个 类
呢?
本章节的内容,并不是 How to
的性质,我们的目的只是为了阅读代码,从而去理解『普通人的自然逻辑思维,如何在程序的世界中得以呈现』。
你也应该在这个过程中保持思考:如果是自己实现这个博弈模型,具体怎么处理会比较好?
我们需要给一些固定的变量,这样后续程序进行调整的时候,只要改变这些变量就可以了。
INIT_HUMAN_NUMBER = 100
DEFAULT_PIGEON_SCORE_RANGE = (0.4, 0.6)
INIT_MONEY = 5
PER_HUMAN_COST_BASE = 10
MILESTORE_TIMES = 100000
PIGEON_SCORE_PER_ROUND = 0.1
DOUBLE_WIN_AWARD = 1.5
在量化一个人的鸽性
,我们会给它一个指数,叫鸽派指数
,如果大于 0.5 就是合作倾向的,如果小于 0.5 就是掠夺倾向的,如果等于 0.5 就刚好与世无争了。
自然,0.2 系数的肯定比 0.45 的野蛮很多,而 0.9 系数的肯定比 0.55 的更加倾向于合作。
为了让一个人有资源
可以被掠夺,所以,每个人都有一个属性,叫money
。
这些变量的说明:
注意: 不用尝试去吃透下面的每一个规则 (本就比较简陋),只要阅读的过程中,判断某个规则是否大体符合 鹰鸽博弈 就可以了。 本章的内容,我们的重点在于 读代码,并非是 重新实现,就像是读一个故事、一本小说,能读下来就可以,并不需要读完之后,能够复述出来。
鸽派指数
(0~1 之间) 以及财富
(money)。
1 减去 鸽派指数
就是他的 武力值
(鹰派指数),比如无合作精神的,鸽派指数为 0,那么它的 武力值
就是满分 1,当两只鹰碰到一起的时候,武力值高的,获得利益更高。
鸽派
的,在于其 鸽派指数
是否大于 0.5。
死亡
的状态,不再参与跟别人的交锋。
鸽派指数
,比如 鸽派
遇到 鹰派
,鸽
输,鹰
赢了,那么 鸽
和 鹰
都会降低自己的 鸽派指数
,鸽
为了下次不会再输,而 鹰
为了下次赢更多。
PER_HUMAN_COST_BASE
是每轮交锋的 金钱基数
,比如是 10
,那么每一轮交锋的开始,两个人都需要拿出 PER_HUMAN_COST_BASE * 0.5 / 2
的押金,也就是每人需要拿出 2.5
(10 为例),两个人就是 2.5+2.5=5
;同时,每一轮都会产生新的财富
,结合每人拿出来的押金,最后重新分配。
新增的财富
的计算方式: 先算平均鸽派指数
再 PER_HUMAN_COST_BASE * (average_pigeon_score-0.5)
。比如两只系数为 0
(毫无合作精神) 的鹰,每轮新的财富是 负5;两只 0.5
(佛系) 的,每轮新的财富是 0
;而两只都是1
(超高合作意识)的鸽,则产生最高值的新财富 5
。这样设置有个巧合:两只 0 的鹰两败俱伤,两只 1 的鸽满载而归,全佛系的情况下不增不减。
最后分配财富后,如果一个人,获得高于最先拿出的押金,那么,这一局,他就赢
了。
相信人类基因中有利他性
的存在,所以,在双赢
的情况下 (也就是两只鸽子遇到了),获得的财富会有个系数加成。
每一轮可分配的财富,是两个人的押金+新增的财富
。再分配的过程, 谁的武力值
高,谁能多分一些。
这里有很微妙的平衡,比如两个鸽,鸽派指数高的,其收益反而会低,但是这轮的结束,两人都是财富增加的状态,并且鸽派指数也会增加。如果是低鸽指数,但又全是鸽子的社会,随着时间的推演,每个人都会变成高指数的鸽子。
人类,是非常多变的。
一个全鸽的社会,一旦有暴力蔓延,它的文明不论多高,都会显得脆弱。人类历史上有太多野蛮铁蹄踏破文明的实例。
所以,我们增加了一个变异的情况,发生的概率为 5%
。
原本明明双赢的两只鸽,因为对方突然鹰
起来,那么,你就会连押金都亏掉,而对方拿走本轮所有的钱;原本明明是双输的两只鹰,突然良心发现或者天降甘露,每人会额外补贴一次押金,从而进入双赢的状态,两人的 鸽派指数
增加了……
class Human(object):
def __init__(self, money=INIT_MONEY, pigeon_score=0.6):
self.pigeon_score = pigeon_score
self.money = money
@property
def force_power(self): # 武力值
power = (1 - self.pigeon_score) or 0.00001
if power < 0:
power = 0.00001
return float(power) # 浮点类型 (带小数点)
@property
def is_pigeon(self):
return self.pigeon_score > 0.5
@property
def is_dead(self):
return self.money < 0
@staticmethod
def when_money_changed(human, paid, got):
if paid > got: # 付出 大于 所得,输
if human.is_pigeon:
human.pigeon_score -= PIGEON_SCORE_PER_ROUND
else:
human.pigeon_score += PIGEON_SCORE_PER_ROUND
elif got > paid: # 所得 大于 付出,赢
if human.is_pigeon:
human.pigeon_score += PIGEON_SCORE_PER_ROUND
else:
human.pigeon_score -= PIGEON_SCORE_PER_ROUND
def meet(self, other_human):
total_pigeon_score = self.pigeon_score + other_human.pigeon_score
average_pigeon_score = total_pigeon_score/2.
cost_per_human = PER_HUMAN_COST_BASE*0.5/2.
self.money -= cost_per_human
other_human.money -= cost_per_human
new_money = PER_HUMAN_COST_BASE * (average_pigeon_score-0.5)
round_money = cost_per_human * 2 + new_money
# 进行财富的再分派
total_force_power = self.force_power + other_human.force_power
i_got = self.force_power/total_force_power * round_money
other_got = other_human.force_power/total_force_power * round_money
# 5% 的概率异变
wow = random.random() < 0.05
double_win = i_got>cost_per_human and other_got>cost_per_human
double_loss = i_got<cost_per_human and other_got<cost_per_human
if double_win:
if wow:
other_got += i_got
i_got = 0
else:
i_got *= DOUBLE_WIN_AWARD
other_got *= DOUBLE_WIN_AWARD
if double_loss and wow:
other_got += cost_per_human
i_got += cost_per_human
self.money += i_got
other_human.money += other_got
self.when_money_changed(self, paid=cost_per_human, got=i_got)
self.when_money_changed(other_human, cost_per_human, other_got)
类 (class)
相当于收纳、整理
的空间,我们完成一个 class 的定义后,一般情况下,不会直接拿来用,而是有个实例 (instance) 化 的处理。
比如我们定义了 Human
,那么直接 Human.money
是不行的,而是要如下:
human1 = Human()
human2 = Human()
在这个示例中,human1 与 human2 有相同的 class,但两者又是不同的对象 (object),可以查看 id(human1)
以及 id(human2)
,也就是说,一个 class 可以实例化出很多对象,对象之间又彼此各自独立。
但并不绝对,也是可以做到一个 class 不断实例化的过程中,只产生一个 id 唯一的 实例
(instance),但此处不做解释,估计多数人永远不会用到这种场景。
函数
与 属性
,都是一个对象所拥有的,比如 human.money
是一个普通的属性 (面向对象的说法是 property),而human.when_money_changed
是一个函数 (面向对象的说法是 method)。
属性
可以直接拿来用,比如 human.moeny += 1
(相当于 human.money = human.money + 1),而 函数
则需要 调用,比如 human.when_money_changed(other_human, cost_per_human, other_got)
。
其实,在 Python 里,对一个 instance (由某个 class 实例化后的对象) 而言,其子函数,也算是它的一个子属性。为了方便理解,就暂时把 函数
和 属性
当做两类事物。
我们看到上面代码中, 有单独一行的 @property
,这是 Python 修饰器
的用法,本课程后续会有具体的介绍,在此处的用法,就是让一个 子函数
变为 子属性
。如果没有 @property
,则 human.is_dead
返回的是一个函数,而有了 @property
,则返回 True(真)
或者 False(假)
这些具体的值。
@staticmethod
是另外一个针对 class 性质的 修饰器
,class 实例化后,我们看到上面的子函数中的第一个变量是 self
,也就是当前实例本身。 staticmethod 的第一个变量不是 self
,相当于一个与当前实例无直接关系的函数,但为了方便代码的规整,而放于同一个 class 之内的。类似的还有 @classmethod
,此处不再介绍,未来如果有看到这个用法 (比较少用),可以自己搜索引擎具体了解。