我一直的观点就是像玩一样学习,能达到最高的效率。就像人只有与环境产生一种和解,一种高贵的顺从的姿态,才能活的最舒服。
我想让我的文章变得易读又不啰嗦,尽力写好这一专栏。一是希望帮助到其他Python开发者,二者也是为了自省。
在您阅读的过程中,无论是语法上的错误,还是专业上的错误。如您阅读时遇到,在你嘲笑笔者之后也请不吝指正。
为了阅读的简洁,如无特别的说明。我们所说的编程思想,编程术语都指的是python。
这一专栏文章的参考文献主要来自以下几本书籍,如您对python较为感兴趣也建议您直接阅读原书。
我们这个年代的人大都接触过几款网络游戏,我们来思考一下以我们为第一视角的网络游戏,在建模时需要具备哪些特性。结合我的经验来说,我认为应有以下特性。
就拿某下城这款游戏来说,我们在进行创建人物的时候。除了需要给他起名之外,还需要选择人物的性别,人物的职业。一些其他的游戏可能还要选择人物的年龄(孩童或者成人),游戏的门派等等。
那么这个模型就是最经典对象了,它是有自己的特性的。这点很好实现,最直接的方式就是通过类来实现,当然我们也可以通过字典来实现。
class Character:
def __init__(self, name, gender, occupation):
self.name = name
self.gender = gender
self.occupation = occupation
在游戏人物创建之后,性质大都是不可变的。最起码主要的性质是不可变的,比如性别和职业这些。除非游戏特殊提供,否则你很那进行改变。那么这个时候我们就要求性质是不可变的,这也很好实现。我们只需要把这几个属性变为不可变就行,这个时候我们上述所说的字典在建模中可能就行不通了。
而在类中实现这些,我们把对应的属性变为私有属性即可。
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。
namedtuple的学名叫具名元组。是通过工厂函数collections.namedtuple创建的。
事实上namedtuple是tuple的手足兄弟。显然namedtuple要更强大一点,他可以通过属性值获取而不必通过索引。构造一个namedtuple需要两个参数,分别是tuple的名字和其中域的名字。
比如在上例中,tuple的名字是“Character”,它包括三个域,分别是“name”、“gender”和“occupation”。
我们在上述中已经实现了人物模型类,为了更好的管理每一个人物模型,我们则需要一个集合管理类。假设我们要设计三国的游戏建模,我们把人物模型的属性值确定好。
country: 魏国、蜀国、吴国
occupation: 军师、武将、谋臣、文官
我们暂定三国人物有这两个属性,实际游戏设计可能会有更多属性,官职、性别等等,这里不过多设计。
Character = namedtuple("Character", ['country', 'occupation'])
class Character_Collect:
countrys = ["魏国", "吴国", "蜀国"]
occupations = ["军师", "武将", "谋臣"]
def __init__(self):
self.characters = [Character(cou, occ) for cou in self.countrys
for occ in self.occupations]
这样我们就有了人物模型的集合了,我们想要有更灵活的操作来操作他们。比如我们想知道一共有多少个模型。
我们尝试直接获取长度。
这个时候当然不行,错误显示这个对象没有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属性,这样速度也大大提升了。
我们虽然已经构造了人物模型集合,但是对这个集合的操作并不那么方便。我们想通过索引的方式,取到模型集合里某一个模型。
实现依然比较简单。
def __getitem__(self, postions):
return self.characters[postions]
我们在对象中实现了__getitem__方法,并返回一个可迭代对象其实上就是实例化的模型列表。
这时候就可以方便的操作该类了。
我们可以使用python中的序列对象的一切来操作该对象,事实上我们甚至可以使用choice方法随机取一一个模型,因为我们的对象是可迭代的,我们也可以使用for循环来遍历该对象。
现在我们已经实现了模型集合的取用,但是管理起来好像还是不够全面。如果我们的某个人物在某个版本取消了我们应该如何管理了,这时候我们就需要第__setitem__ 方法了
def __setitem__(self, position, value):
self.characters[position] = value
魔术方法(magic method)是特殊方法的昵称。在python中,我们可以通过魔术方法做很多事情。我们上面用到的__setitem__等都是魔术方法。为了显示它的便捷性,我们可以用它做更多事情。
如果我们的的模型集合写成了工具类,同组的其他开发者也可能用到该模块。这时候他想打印看一下。
这样的交互就很不友好了。我们如何使这个模型类看起来更饱满一些呢?
其实也比较简单。
def __repr__(self):
return "这是Character_Collect(一个游戏人物模型的集合类)中的__repr__方法"
除了__repr__ 方法外 str 方法同样也是十分常用的。
def __str__(self):
return "这是Character_Collect(一个游戏人物模型的集合类)中的__str__方法"
这时候就出现了一个问题,这个时候为什么打印的不再是__repr__ 返回的内容了呢,其实print函数输出的默认会从__str__函数返回的结果里去寻找,只有找不到该函数时才会从__repr__里寻找。
其实__str__ 方法和__repr__方法对应的是str函数和repr函数,这两个函数在使用的时候没有太大的区别。但是既然名字称呼不同使用起来的区别还是有的。
区别一:
在没有__str__函数时如果使用print方法,或者str方法时。会优先去找repr函数,这样也就是说。如果方法里没有__str__方法,我们通过__repr__函数既可以实现repr()函数的内容,又可以打印__str__的内容。这样来用有利于实现输出的一致性。
区别二:
我们可以来看一下例子
当我们反复str的时候输出的始终是一个字符串,这点是正常的。也符合我们的认知。可是如果循环的使用repr
则会略有不同,他会不断的在结果的外侧加字符串。
你可能说你这个没用,其实这个是有用的。
你在使用Python的时候有没有过这样的困惑?
if "达达很帅气":
print("这是对的")
这句话的执行结果是什么呢。
我们都知道if后面应该跟的是条件,可是跟了"达达"很帅气这一段字符串,就能走到条件体里呢,
难道真的是我比较帅吗?对也不对!
对是因为事实上我确实长比较帅气。
但是python却不知道。判断的却不是我是否真的比较帅,而是任意的非空字符串都会判断为真。
其实我们在正常开发过程中,经常会这样用。判断字符串是否非空,可是为何会如此呢。
原来这句话的布尔值为True,所以就能走到条件体内。
回到我们上面的模型集合中
我们在类中加一实例方法
def __bool__(self):
return True
原来只要在对象中实现__bool__方法,这个对象就有了bool
属性。那么我们上述中的字符串都实现了bool方法了吗?
并不一定,我们删除bool方法。这个时候为了显示方便,不如写一个新例子。
class verifi_bool:
def __len__(self):
return 1
这就奇怪了,这个类没有bool方法,为什么bool值还为True呢
class verifi_bool:
def __len__(self):
return 0
原来在使用bool函数时,函数会优先调用对象中的__bool__方法,如果实力中没有bool则会去调用__len__方法。
如果len大于0bool则为True否则为False
不得不说这是一个非常优雅的结合,这样对象之间的属性不再孤立,而是有机结合起来。为甚么要这样设计呢,因为Python 最好的品质之一是一致性。
就像美妙的自然界一样,大多事物是那么诙谐统一。
这篇文章并没有一个明确的主题或技能点,而是对python数据模型的一个大体介绍。说python之禅的话可能点空了,但语言使用者的我们。
应当热爱语言,语言就像是我们的伙伴。我们必须懂语言,这是我们能成长为大牛或者伴随语言进步的基础。
这种懂并不是单独会调api、会使用一些内置函数与类库。
而是在我们已渐渐熟悉了如何用python解决问题,去工作之后。我们也应当慢慢去理解python为什么这样去工作,对其机制与核心理念略有了解。
否则不能说是掌握一门语言,而只是了解一门语言的api写法而已。
对语言有一种温情与语言的开源者心存一些敬意,而不是单纯的拿来主义。