定义『人类』

如何构建『人类』

首先,『人类』应该是一个 (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

这些变量的说明:

  • INIT_HUMAN_NUMBER,初始的人类数量
  • DEFAULT_PIGEON_SCORE_RANGE,默认的鸽派指数区间
  • INIT_MONEY,每个人初始化时候的钱
  • PER_HUMAN_COST_BASE,每轮交锋的 金钱基数
  • MILESTORE_TIMES,里程碑总次数,相当于每个人一生中与他人交锋的次数
  • PIGEON_SCORE_PER_ROUND,每轮交锋后,产生的鸽派指数的变化量
  • DOUBLE_WIN_AWARD,如果达到双赢时候的奖励

整体的思路

注意: 不用尝试去吃透下面的每一个规则 (本就比较简陋),只要阅读的过程中,判断某个规则是否大体符合 鹰鸽博弈 就可以了。 本章的内容,我们的重点在于 读代码,并非是 重新实现,就像是读一个故事、一本小说,能读下来就可以,并不需要读完之后,能够复述出来。

基本的规则

  1. 每个人,有自己的鸽派指数 (0~1 之间) 以及财富 (money)。
  2. 1 减去 鸽派指数 就是他的 武力值 (鹰派指数),比如无合作精神的,鸽派指数为 0,那么它的 武力值 就是满分 1,当两只鹰碰到一起的时候,武力值高的,获得利益更高。
  3. 判断一个人是否是鸽派的,在于其 鸽派指数 是否大于 0.5。
  4. 如果一个人的财富已经归零,就认为是 死亡 的状态,不再参与跟别人的交锋。
  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)

如上 Python 代码衍生的一些基础知识

类 (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,此处不再介绍,未来如果有看到这个用法 (比较少用),可以自己搜索引擎具体了解。