求python一个类与对象的代码_Python 代码设计 Part1: 类,对象,继承

说实话,最初我只是打算是分享一个关于 BackTrader 踩过的坑的

代码的设计

首先,我们要问出第一个问题:我们为什么要设计?或者说,设计是解决什么问题的?这里也给出一个简单的回答:设计主要是解决代码的复用问题。

那什么是好的设计呢?好的设计就意味着以尽可能容易的方式,来帮助对方来使用我们的代码。所以做设计的三个层次是:个人的代码设计——我们用自己的代码,设计模式

团队的代码设计——他人用我们的代码,设计模式

系统之间的配合设计——架构

所以我们无论是想写好自己的代码给别人用也好,去理解他人的代码也好,还是说以后自己想成为一个架构师,不断磨练自己的设计功底是必经之路

设计模式系列计划

在这个计划里,我准备只涉及到代码设计,因为所有的技术都是为了业务服务的,刚才提的三个层次越前面就具体,他和我们在平时工作密切相关,人人都可能遇到,越到后面就越虚,当我们谈论架构时,就必不可少地要考虑业务方向,以至于组织架构,这就真的要具体问题具体分析了。

而我在写这些文章的时候,主要是面向希望深入理解 Python 这门语言的人,所以我会更多地写一些逻辑理解,而不仅仅是粘代码,希望能够以一个不同的角度来提供出来。当然我自己也在一个学习的过程中,如果发现错误还请指出。

这个系列会不断更新,所以这里先列一下能想到的计划:Part1:类,对象,继承

这就是本篇的内容,类、对象、继承是所有面向对象语言里的基础,所以放在一块儿,因为并不是所有的语言都有 “多重继承” “函数式编程” 等概念的,所以 Python 的额外的一些有关设计的东西,就放到其它几个部分

Part2:MixIn

MixIn 是 Python 有关的多重继承的实现方案,在这里我们会涉及到 MRO 等机制,更新好了之后我会放上链接

Part3:函数式编程

函数式编程是和面向对象编程相似的一种设计思路,我会单独来说

系列里随后的文章都不会有上面这么啰嗦的一大段了。好了,现在我们开始吧。

从零开始

假如我们现在有一个用户系统,它要提供改名的操作,在用户访问的时候还能输出一段问候语,那我们最简单清晰的代码大概会长这样:

name = None

def setName(newName):

global name

name = newName

def greeting():

print("Hi, I am "+name)

# 模拟别人在使用我们的代码

if __name__ == "__main__":

# 我叫 peter

setName("peter")

# 啊,刚才弄错了,我要改成大写字母打头的

setName("Peter")

# 好了,我来了

greeting()

因为我们要在 greeting 函数里使用之前设置好的名字,而且还不知道调用者的调用顺序,所以我们没办法把设置名字和输出问候语写在一个函数里面,因此 name 变量就需要放在全局的位置,这样才能让两个函数都能访问到这个变量,对吧?

好的,现在需求变化了:我们来了第二个用户,也需要设置名字和输出问候语。那么我们的代码可以改成这样:

name1 = None

name2 = None

def setName1(newName):

global name1

name1 = newName

def greeting1():

print("Hi, I am "+name1)

def setName2(newName):

global name2

name2 = newName

def greeting2():

print("Hi, I am "+name2)

if __name__ == "__main__":

setName1("Peter")

greeting1()

setName2("Cate")

greeting2()

很明显,这是一个没有复用的情况,相似的逻辑我们写了两次,如果来了10个用户那我们不得写10份代码了么,那我们要怎么改动呢?

假设这里我们增加一个字段 id,来区分不同的用户的话,我们就可以把上面这段代码改成复用的形式:

names = {}

def setName(id, newName):

global names

names[id] = newName

def greeting(id):

print("Hi, I am "+names[id])

if __name__ == "__main__":

setName(1, "Peter")

greeting(1)

setName(2, "Cate")

greeting(2)

我们用一个字典做存储,把所有名字都放在一块儿了,这样我们的两个函数也实现了复用,很棒!

那我们再想想,这种设计有什么问题么?

问题在于,names 这个命名太通用了,如果我写的代码里有存用户名然后就用了 names,同事的代码里存宠物名,也用了 names,那么两段代码放在一块儿的时候,就有可能“不小心”就访问到了对方的变量然后做出了不必要的修改,从而产生 bug 。单看自己的代码部分还特别难找出来!

所以我们在这时候要注意所有变量的作用域,作用域做的越小,就越不容易被其它的代码干扰。因此我们可以这样:

def createUser():

return {}

def setName(user, newName):

user['name'] = newName

def greeting(user):

print("Hi, I am "+user['name'])

if __name__ == "__main__":

user1 = createUser()

setName(user1, 'Peter')

greeting(user1)

user2 = createUser()

setName(user2, "Cate")

greeting(user2)

我们首先通过 createUser 方法创建出一个极小的“作用域”,然后随后的 setName 和 greeting 都围绕这一个小的作用域来操作。

能不能再给力点?

当然,我们刚才把变量的作用域缩小了,所以变量不会产生干扰了,但现在我们的函数名依旧是全局的,也有可能被其它人误操作,那我们就把函数也放在小的作用域里面:

def createUser():

user = {

"name": None,

}

def setName(newName):

user['name'] = newName

def greeting():

print("Hi, I am "+user['name'])

user['setName'] = setName

user['greeting'] = greeting

return user

if __name__ == "__main__":

user1 = createUser()

user1['setName']('Peter')

user1['greeting']()

user2 = createUser()

user2['setName']('Cate')

user2['greeting']()

现在我们居然只有一个函数了!别人要做宠物逻辑的就让他们叫 createPet 去吧!

其实在这个思考的过程中,我们就通过自己的方式来实现了一套迷你的面向对象的系统,而这里的 createUser 就是这个对象的构造函数,接下来我们看一下官方提供的面向对象系统吧:

Python 的类和对象

class User():

def __init__(self):

self.name = None

def setName(self, newName):

self.name = newName

def greeting(self):

print("Hi, I am "+self.name)

if __name__ == "__main__":

user1 = User()

user1.setName('Peter')

user1.greeting()

user2 = User()

user2.setName('Cate')

user2.greeting()

在这里 User 就是一个类,而 user1、user2 就是这个类具体实例化出来的对象,官方的面向对象系统和我们自己写的有一点点区别,但功能都是一样的,差别主要有__init__ 函数

User 后面的括号在这里不是函数的作用,它是下面说继承的时候要介绍的,所以在这里提供了 __init__ 函数作为构造函数

self 参数

刚才我们自己的代码里都通过 user[xxx] 来直接访问作用域的数据,而这里系统会通过 self 参数来让我们定义的函数来访问“自己”这个作用域。

到这里我们可以休息一下,希望我上面的东西能够让大家了解到为什么 Python 和其它的各种语言要提供类和对象的机制,它其实都是为了更好地实现系统隔离,让我们的代码远离其它代码的干扰,从而更好地与别人的代码互相配合。

所以我们可以看到,我们在这个系统里其实是付出了一定的“复杂度”换回来“可维护性”以及代码的稳定程度,有些东西确实比平铺直叙来的要复杂一些——有什么东西要比直接写一个全局变量加两个函数要更简单呢,但复杂不应该作为唯一的看待系统的指标,我们要看到这个复杂给我们带来了什么价值,我们要去思考在同样的价值下能否有没那么复杂的方案,或者在同等的复杂度情况下能否带来更多的价值,这个在大型系统设计里也是同样的。

继承

好了,接下来需求又要变化了,我们需要加入一类新的用户,VIP 用户,其它功能都和普通 User 一样,只是有一点区别是他的欢迎语是输出两句 1. “Hi, I am xxx”,2. “I am a VIP”,我们是否要写一个全新的类 VipUser 呢?

class VipUser():

def __init__(self):

self.name = None

def setName(self, newName):

self.name = newName

def greeting(self):

print("Hi, I am "+self.name)

print("I am a VIP")

这样明显我们又重复了自己的工作,把原来 User 的部分代码复制了一份,而在面向对象的语言里,解决多个“对象”的复用是通过“类”,而解决多个“类”的复用就是通过继承,在 Python 里使用继承的方式如下:

class VipUser(User):

def greeting(self):

print("Hi, I am "+self.name)

print("I am a VIP")

这里通过在 VipUser 类后面的括号里把它的父类写上就可以指明它继承的类了,是不是很方便?我们只把有差异的部分写出来就好了,其它都可以保留下来。

那我们能否更给力一点,进一步复用 User 类里的 greeting 方法,只把新增的语句写上呢?可以:

class VipUser(User):

def greeting(self):

super().greeting()

print("I am a VIP")

要注意,这个是 Python 3 里的写法,在 Python 2 里是这样 super(VipUser, self).greeting() 或者这样 super(self.__class__, self).greeting()

通过 super 我们可以拿到指代的父类,而 super() 就是父类的状态来指代的当前对象,最后就可以通过 .greeting() 来调用对应的父类上的方法了。我们其实也可以通过 Python 2 的方法看到 Python 在 super 关键字上演变的过程, super函数 其实是把当前对象 self 和当前类 VipUser 都传入进去,然后通过当前类的 MRO 链上找到父类上对应的方法,最后实现方法的调用,具体关于 MRO 的机制,我会放到下一篇里介绍。

然后这里我们还有一个办法可以调用到父类的方法:

class VipUser(User):

def greeting(self):

User.greeting(self)

print("I am a VIP")

这种方法可以实现但是它越过了 MRO 的机制,所以并不推荐,具体分析我也会放到下一篇里,同时这种写法也 hard code 了 User,如果代码特别长而哪天我们又想给 VipUser 换一个父类,那就很容易漏掉这里从而造成 bug。

所有代码片断我都放在了这里: 代码

我的博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值