定义『社会』

如何定义『社会』

『社会』也会是一个 类 (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_humansall_humans 中。有些初学者会很自然地只实例化 1 次,同时添加到了 init_humansall_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))

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

PEP

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 是独立的,不需要 elifelse 的补充,而且逻辑简单的,就直接写在一行之内,这样处理的两个好处:

  1. 增加单行的可读、连贯性;
  2. 减少不必要的缩进层次。

变量类型

一个的返回,我们应该要清晰它的类型,虽然 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 中,应该避免错误(异常)的出现,并且尽可能针对异常做对应;也有人认为如果有异常,不用去处理,让它抛出来,刚好可以进行修正,说明代码内没有处理好。
都有一定的道理。以后,你或许会接触到更多公说公有理、婆说婆有理的其它讨论。其实,即使作为初学者,也可以形成自己的看法。大多数别人的看法,在具体到某一行代码的时候,都是有局限性的。

对了,防御性写法有什么用吗?
这是一种冗余的逻辑,在某个局部我们很清楚代码的逻辑,但一个项目的代码变得复杂的时候,牵一发而动全身,可能就会产生各种细小的错误。这些错误本身无伤大雅,一旦抛出异常可能又会导致整个程序卡住、退出。所以,时间久了,你也会很自然地在某些地方,顺手写一些看起来画蛇添足的代码,作用是为了避免未来潜在的、无价值的异常排查。

0 是第一个元素

一个列表,比如是 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'}
>>>