飞道的博客

初窥门径——通过游戏人物建模来窥探python的数据模型

382人阅读  评论(0)

一、 Python进阶系列说明

我一直的观点就是像玩一样学习,能达到最高的效率。就像人只有与环境产生一种和解,一种高贵的顺从的姿态,才能活的最舒服。

我想让我的文章变得易读又不啰嗦,尽力写好这一专栏。一是希望帮助到其他Python开发者,二者也是为了自省。

在您阅读的过程中,无论是语法上的错误,还是专业上的错误。如您阅读时遇到,在你嘲笑笔者之后也请不吝指正。

为了阅读的简洁,如无特别的说明。我们所说的编程思想,编程术语都指的是python。

这一专栏文章的参考文献主要来自以下几本书籍,如您对python较为感兴趣也建议您直接阅读原书。

  1. 《Python学习手册》
  2. 《流畅的Python》
  3. 《利用Python进行数据分析》

二、游戏人物建模

我们这个年代的人大都接触过几款网络游戏,我们来思考一下以我们为第一视角的网络游戏,在建模时需要具备哪些特性。结合我的经验来说,我认为应有以下特性。

1.游戏主角具有自己特质

就拿某下城这款游戏来说,我们在进行创建人物的时候。除了需要给他起名之外,还需要选择人物的性别,人物的职业。一些其他的游戏可能还要选择人物的年龄(孩童或者成人),游戏的门派等等。

那么这个模型就是最经典对象了,它是有自己的特性的。这点很好实现,最直接的方式就是通过类来实现,当然我们也可以通过字典来实现。

class Character:

    def __init__(self, name, gender, occupation):
        self.name = name
        self.gender = gender
        self.occupation = occupation

2.游戏主角的性质是不可变的

在游戏人物创建之后,性质大都是不可变的。最起码主要的性质是不可变的,比如性别和职业这些。除非游戏特殊提供,否则你很那进行改变。那么这个时候我们就要求性质是不可变的,这也很好实现。我们只需要把这几个属性变为不可变就行,这个时候我们上述所说的字典在建模中可能就行不通了。
而在类中实现这些,我们把对应的属性变为私有属性即可。

class Character:

    def __init__(self, name, gender, occupation):
        self.__name = name
        self.__gender = gender
        self.__occupation = occupation

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, value):
        print("不允许的改变")


这样就实现了我们的需求,但是不够简洁。绕了一大圈实现了需求可就是感觉不够优雅。而事实上python早已提供了类似的数据结构。

from collections import namedtuple
Character = namedtuple("Character",['name', 'gender', 'occupation'])


这样的实现就做到了更精简了一些,上面我们用到了namedtuple类型。他的后缀的tuple,我们不难联想到元组tuple。

3.namedtuple 的用法

事实上namedtuple是tuple的手足兄弟。显然namedtuple要更强大一点,他可以通过属性值获取而不必通过索引。构造一个namedtuple需要两个参数,分别是tuple的名字和其中域的名字。

比如在上例中,tuple的名字是“Character”,它包括三个域,分别是“name”、“gender”和“occupation”。

三、人物集合管理

我们在上述中已经实现了人物模型类,为了更好的管理每一个人物模型,我们则需要一个集合管理类。假设我们要设计三国的游戏建模,我们把人物模型的属性值确定好。

1.属性范围确定

country: 魏国、蜀国、吴国
occupation: 军师、武将、谋臣、文官

我们暂定三国人物有这两个属性,实际游戏设计可能会有更多属性,官职、性别等等,这里不过多设计。

2.建立人物模型

 Character = namedtuple("Character", ['country', 'occupation'])

3.建立人物模型管理类

class Character_Collect:
    countrys = ["魏国", "吴国", "蜀国"]
    occupations = ["军师", "武将", "谋臣"]
    def __init__(self):
        self.characters = [Character(cou, occ) for cou in self.countrys
                                               for occ in self.occupations]


这样我们就有了人物模型的集合了,我们想要有更灵活的操作来操作他们。比如我们想知道一共有多少个模型。

4.len的用法

我们尝试直接获取长度。

这个时候当然不行,错误显示这个对象没有len方法,那么我们就来创建一个len方法。

 def __len__(self):
        return len(self.characters)


我们通过在类中创建__len__ 对象就能实现len方法的调用。实际上,len(object)的本质还是调用object中的__len__方法

但是实际使用中很少有人会使用object.__len__方法
这时候你可能会说的,我们使用len(list,tuple) 是因为list和tuple的数据结构中也定义的有__len__方法吗?

其实不是的。python的内置对象是C语言体,和我们写的对象相比。这些c语言体有ob_size属性,代表这些对象的长度。在len(list)中,并不是调用list中的__len__ 方法而是直接调用ob_size属性,这样速度也大大提升了。

5.__getitem__用法

我们虽然已经构造了人物模型集合,但是对这个集合的操作并不那么方便。我们想通过索引的方式,取到模型集合里某一个模型。

实现依然比较简单。

def __getitem__(self, postions):
        return self.characters[postions]

我们在对象中实现了__getitem__方法,并返回一个可迭代对象其实上就是实例化的模型列表。
这时候就可以方便的操作该类了。

我们可以使用python中的序列对象的一切来操作该对象,事实上我们甚至可以使用choice方法随机取一一个模型,因为我们的对象是可迭代的,我们也可以使用for循环来遍历该对象。

6.__setitem__用法

现在我们已经实现了模型集合的取用,但是管理起来好像还是不够全面。如果我们的某个人物在某个版本取消了我们应该如何管理了,这时候我们就需要第__setitem__ 方法了

def __setitem__(self, position, value): 
	self.characters[position] = value


这样管理起来就更方便一些

四、魔术方法

魔术方法(magic method)是特殊方法的昵称。在python中,我们可以通过魔术方法做很多事情。我们上面用到的__setitem__等都是魔术方法。为了显示它的便捷性,我们可以用它做更多事情。

1. repr 方法

如果我们的的模型集合写成了工具类,同组的其他开发者也可能用到该模块。这时候他想打印看一下。

这样的交互就很不友好了。我们如何使这个模型类看起来更饱满一些呢?

其实也比较简单。

  def __repr__(self):
        return "这是Character_Collect(一个游戏人物模型的集合类)中的__repr__方法"


这个时候我们就可以打印出的的相关必要信息了

2.str 方法

除了__repr__ 方法外 str 方法同样也是十分常用的。

 def __str__(self):
        return "这是Character_Collect(一个游戏人物模型的集合类)中的__str__方法"


这时候就出现了一个问题,这个时候为什么打印的不再是__repr__ 返回的内容了呢,其实print函数输出的默认会从__str__函数返回的结果里去寻找,只有找不到该函数时才会从__repr__里寻找。

3.str 方法和 repr 方法的区别

其实__str__ 方法和__repr__方法对应的是str函数和repr函数,这两个函数在使用的时候没有太大的区别。但是既然名字称呼不同使用起来的区别还是有的。

区别一:
在没有__str__函数时如果使用print方法,或者str方法时。会优先去找repr函数,这样也就是说。如果方法里没有__str__方法,我们通过__repr__函数既可以实现repr()函数的内容,又可以打印__str__的内容。这样来用有利于实现输出的一致性。

区别二:
我们可以来看一下例子

当我们反复str的时候输出的始终是一个字符串,这点是正常的。也符合我们的认知。可是如果循环的使用repr则会略有不同,他会不断的在结果的外侧加字符串。

你可能说你这个没用,其实这个是有用的。

五、bool 之谜

你在使用Python的时候有没有过这样的困惑?

if "达达很帅气":
    print("这是对的")

这句话的执行结果是什么呢。

我们都知道if后面应该跟的是条件,可是跟了"达达"很帅气这一段字符串,就能走到条件体里呢,
难道真的是我比较帅吗?对也不对!

对是因为事实上我确实长比较帅气。

但是python却不知道。判断的却不是我是否真的比较帅,而是任意的非空字符串都会判断为真。

其实我们在正常开发过程中,经常会这样用。判断字符串是否非空,可是为何会如此呢。

原来这句话的布尔值为True,所以就能走到条件体内。

1.__bool__方法

回到我们上面的模型集合中
我们在类中加一实例方法

  def __bool__(self):
       return True


原来只要在对象中实现__bool__方法,这个对象就有了bool属性。那么我们上述中的字符串都实现了bool方法了吗?
并不一定,我们删除bool方法。这个时候为了显示方便,不如写一个新例子。

class verifi_bool:
    def __len__(self):
        return 1


这就奇怪了,这个类没有bool方法,为什么bool值还为True呢

2.python中bool的巧妙结合

class verifi_bool:
    def __len__(self):
        return 0


原来在使用bool函数时,函数会优先调用对象中的__bool__方法,如果实力中没有bool则会去调用__len__方法。
如果len大于0bool则为True否则为False

不得不说这是一个非常优雅的结合,这样对象之间的属性不再孤立,而是有机结合起来。为甚么要这样设计呢,因为Python 最好的品质之一是一致性。

就像美妙的自然界一样,大多事物是那么诙谐统一。

六、后记

这篇文章并没有一个明确的主题或技能点,而是对python数据模型的一个大体介绍。说python之禅的话可能点空了,但语言使用者的我们。

应当热爱语言,语言就像是我们的伙伴。我们必须懂语言,这是我们能成长为大牛或者伴随语言进步的基础。

这种懂并不是单独会调api、会使用一些内置函数与类库。

而是在我们已渐渐熟悉了如何用python解决问题,去工作外。我们也应当慢慢去理解python为什么这样去工作,合理理解其机制。

对语言有一种温情与语言的开源者心存一些敬意,而不是单纯的拿来主义。


转载:https://blog.csdn.net/weixin_44706915/article/details/110132698
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场