前言
师门中每逢成人礼就要下山历练一番,小掌门今年成人礼,下山也有一段时日。可恰逢年底,今年百姓收成都不是很好,各大店铺也很少招人,再加上学艺不精,小掌门无事可做就只能饿肚子了。后来小掌门饿的实在不行,只好沿街乞讨。其时惨状如图:
初识面向对象
就这样每天风吹日晒地乞讨,后来小掌门发现自己每天所获得的馒头都比其他同行的小乞丐少好多,他发现其他小乞丐都有自己的广告语,然后小掌门稍一改进,便成了如下模样
每次要饭前小掌门都在地上写上几个字
def beg():#定义一个要饭函数 print('求各位给个馒头吃') if __name__ == '__main__': beg()
就这样,小掌门的馒头比以往多了起来,时不时也分点馒头给其他乞丐。
一天,过来一个老叫花对小掌门说:“孩子啊,你这样每天要饭前还得找个树枝在地上写一遍字,其实你可以自己做个招牌啊,就跟那人一样”,老叫花指了指对面走过来的另一个乞讨的年轻人。
小掌门一拍脑门,是啊,我做一个招牌就不用每次都写一遍了,小掌门找了一块破木板,在上面好好提了几笔
class Begger: def __init__(self,name,age,money): self.name=name self.age=age self.money=money def beg(self): # 定义一个要饭函数 print('求各位给个馒头吃') if __name__ == '__main__': zm=Begger('古墓派掌门','18',-10) zm.beg()
老叫花见小掌门挺聪明,笑着说:“看来你已经理解面向对象了”。
“前辈,什么叫面向对象啊”,小掌门瞪着水汪汪的大眼睛问道。
“说起面向对象,还得从面向过程说起”
面向对象vs面向过程
面向过程的程序设计的核心是过程(流水线式思维),过程即解决问题的步骤,面向过程的设计就好比精心设计好一条流水线,考虑周全什么时候处理什么东西。
优点是:极大的降低了写程序的复杂度,只需要顺着要执行的步骤,堆叠代码即可。
缺点是:一套流水线或者流程就是用来解决一个问题,代码牵一发而动全身。
应用场景:一旦完成基本很少改变的场景,著名的例子有Linux內核,git,以及Apache HTTP Server等。
面向对象的程序设计的核心是对象(上帝式思维),要理解对象为何物,必须把自己当成上帝,上帝眼里世间存在的万物皆为对象,不存在的也可以创造出来。面向对象的程序设计好比如来设计西游记,如来要解决的问题是把经书传给东土大唐,如来想了想解决这个问题需要四个人:唐僧,沙和尚,猪八戒,孙悟空,每个人都有各自的特征和技能(这就是对象的概念,特征和技能分别对应对象的属性和方法),然而这并不好玩,于是如来又安排了一群妖魔鬼怪,为了防止师徒四人在取经路上被搞死,又安排了一群神仙保驾护航,这些都是对象。然后取经开始,师徒四人与妖魔鬼怪神仙互相缠斗着直到最后取得真经。如来根本不会管师徒四人按照什么流程去取。
面向对象的程序设计的
优点是:解决了程序的扩展性。对某一个对象单独修改,会立刻反映到整个体系中,如对游戏中一个人物参数的特征和技能修改都很容易。
缺点:可控性差,无法向面向过程的程序设计流水线式的可以很精准的预测问题的处理流程与结果,面向对象的程序一旦开始就由对象之间的交互解决问题,即便是上帝也无法预测最终结果。于是我们经常看到一个游戏人某一参数的修改极有可能导致阴霸的技能出现,一刀砍死3个人,这个游戏就失去平衡。
应用场景:需求经常变化的软件,一般需求的变化都集中在用户层,互联网应用,企业内部软件,游戏等都是面向对象的程序设计大显身手的好地方。
在python 中面向对象的程序设计并不是全部。
面向对象编程可以使程序的维护和扩展变得更简单,并且可以大大提高程序开发效率 ,另外,基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
了解一些名词:类、对象、实例、实例化
类:具有相同特征的一类事物(人、狗、老虎)
对象/实例:具体的某一个事物(隔壁阿花、楼下旺财)
实例化:类——>对象的过程(这在生活中表现的不明显,我们在后面再慢慢解释)
类的声明
1 #class 类名: 2 # '类的文档字符串' 3 # 类体 4 #如: 5 class Begger: 6 def __init__(self,name,age,money): 7 self.name=name 8 self.age=age 9 self.money=money 10 def beg(self): # 定义一个要饭函数 11 print('求各位给个馒头吃')
在对象实例化时,会自动调用__init__函数,完成对象初识化,相当于c++,java中的构造函数
上述代码定义了一个Begger类,在实例化对象时,会自动初始化name,age,money实例属性
上面提到两个名词‘实例化对象’,‘实例属性’,下面我一一道来
实例化对象
类名加括号就是实例化,会自动触发__init__函数的运行,可以用它来为每个实例定制自己的特征
如
zm=Begger('古墓派掌门','18',-10)
实例属性与类属性及私有属性
1:实例属性: 最好在__init__(self,...)中初始化 内部调用时都需要加上self. 外部调用时用instancename.propertyname 2:类属性: 在__init__()外初始化 在内部用classname.类属性名调用 外部既可以用classname.类属性名又可以用instancename.类属性名来调用 3:私有属性: 1):单下划线_开头:只是告诉别人这是私有属性,外部依然可以访问更改 2):双下划线__开头:外部不可通过instancename.propertyname来访问或者更改 实际将其转化为了_classname__propertyname
如我们在乞丐类(Begger)中加一个count类属性,用来计算小乞丐的个数,每个小乞丐加一个婚姻状况(marriage)私有属性:
(内心戏:没钱还想结婚,做梦吧)
1 class Begger: 2 count=0 #这是一个类属性 3 def __init__(self,name,age,money,single=True): 4 self.name=name #实例属性 5 self.age=age 6 self.money=money 7 self._single=True #私有属性 8 Begger.count+=1 9 def beg(self): # 定义一个要饭函数 10 print('求各位给个馒头吃') 11 12 13 14 if __name__ == '__main__': 15 zm=Begger('古墓派掌门','18',-10) 16 zm.beg() 17 print("小乞丐个数:%s"%Begger.count)
类方法与静态方法
1:普通类方法: def fun_name(self,...): pass 外部用实例调用 2:静态方法:@staticmethod 不能访问实例属性!!! 参数不能传入self!!! 与类相关但是不依赖类与实例的方法!! 3:类方法:@classmethod 不能访问实例属性!!! 参数必须传入cls!!! 必须传入cls参数(即代表了此类对象-----区别------self代表实例对象),并且用此来调用类属性:cls.类属性名 *静态方法与类方法都可以通过类或者实例来调用。其两个的特点都是不能够调用实例属性
示例如下:
1 class Begger: 2 count=0 #这是一个类属性 3 def __init__(self,name,age,money,single=True): 4 self.name=name #实例属性 5 self.age=age 6 self.money=money 7 self._single=True #私有属性 8 Begger.count+=1 9 def beg(self): # 定义一个要饭函数,普通类方法 10 print('求各位给个馒头吃') 11 12 @staticmethod 13 def beg_skill():#定义一个乞讨技巧函数,静态方法 14 print("乞讨技巧") 15 16 17 @classmethod 18 def beg_counter(cls): 19 print("小乞丐个数:%s" % Begger.count) 20 21 22 if __name__ == '__main__': 23 zm=Begger('古墓派掌门','18',-10) 24 zm.beg() 25 zm.beg_skill() 26 Begger.beg_counter()
tip:类属性与类方法是类固有的方法与属性,不会因为实例不同而改变,写他们的目的是减少多实例时所创造出来的内存空间,加快运行速度
封装、继承、多态
"封装、继承、多态并称为类的三大特性,也是面向对象的一个主要特点"老叫花说道“下面我就一一跟你讲”
封装
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏
“封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。你用的那个乞讨类中的乞讨技巧就是很好的体现,别人只看到你表现出的乞讨的形式,并不知道你在背后所做的何种工作” 老叫花意味深长地说。
property特性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则 ps:面向对象的封装有三种方式: 【public】 这种其实就是不封装,是对外公开的 【protected】 这种封装方式对外不公开,但对朋友(friend)或者子类公开 【private】 这种封装对谁都不公开
python并没有在语法上把它们三个内建到自己的class机制中,在C++或java里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现
用法如下
class Begger: count=0 #这是一个类属性 def __init__(self,name,age,money,single=True): self._name=name self._age=age self._money=money self._single=True #私有属性 Begger.count+=1 def beg(self): # 定义一个要饭函数,普通类方法 print('求各位给个馒头吃') @property def age(self): return self._age @age.setter def age(self,value): self._age=value @age.deleter def age(self): del self._age @staticmethod def beg_skill():#定义一个乞讨技巧函数,静态方法 print("乞讨技巧") @classmethod def beg_counter(cls): print("小乞丐个数:%s" % Begger.count) if __name__ == '__main__': zm=Begger('古墓派掌门','18',-10) print(zm.age)#获取年龄属性 zm.age=20#设置年龄属性 print(zm.age) del zm.age#删除年龄属性
继承
继承,以通用的类为基础建立专门的类对象
继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类
“说起继承,老叫花子我可就有话说了,什么叫继承,就是儿子继承老子的东西。‘龙生龙,凤生凤,老鼠的儿子会打洞’。虽然说古人有云,‘王侯将相宁有种乎’ ,但有钱真的可以为所欲为,地主的儿子生下来就会继承老子的东西成为富二代,跟咱们这些臭要饭的就不一样了。我来给你举几个例子 ”
1 class Person: #定义一个人类 2 def __init__(self,name,age,money,single=True): 3 self.name=name #实例属性 4 self.age=age 5 self.money=money 6 self._single=True #私有属性 7 class Begger(Person):#定义一个乞丐类,继承Person 8 # count=0 #这是一个类属性 9 10 def beg(self): # 定义一个要饭函数,普通类方法 11 print('求各位给个馒头吃') 12 13 @staticmethod 14 def beg_skill():#定义一个乞讨技巧函数,静态方法 15 print("乞讨技巧") 16 # @classmethod 17 # def beg_counter(cls): 18 # print("小乞丐个数:%s" % Begger.count) 19 20 21 if __name__ == '__main__': 22 zm=Begger('古墓派掌门','18',-10) 23 zm.beg() 24 zm.beg_skill() 25 # Begger.beg_counter()
tip:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
在python3中,子类新建与父类重名的方法叫重写,子类执行父类的方法也可以直接用super方法(这些与c++,java类似),如我们重写一下子类init方法
1 class Person: 2 def __init__(self,name,age,money,single=True): 3 self.name=name #实例属性 4 self.age=age 5 self.money=money 6 self._single=True #私有属性 7 class Begger(Person): 8 count=0 #这是一个类属性 9 10 #重写父类__init__ 11 def __init__(self,name,age,money,single=True): 12 super(Begger,self).__init__(name,age,money,single) 13 #或者 14 # Person.__init__(self,name,age,money,single) 15 Begger.count+=1 16 17 18 19 def beg(self): # 定义一个要饭函数,普通类方法 20 print('求各位给个馒头吃') 21 @property 22 def age(self): 23 return self._age 24 25 @age.setter 26 def age(self,value): 27 self._age=value 28 29 @age.deleter 30 def age(self): 31 del self._age 32 @staticmethod 33 def beg_skill():#定义一个乞讨技巧函数,静态方法 34 print("乞讨技巧") 35 @classmethod 36 def beg_counter(cls): 37 print("小乞丐个数:%s" % Begger.count) 38 39 40 if __name__ == '__main__': 41 zm=Begger('古墓派掌门','18',-10) 42 zm.beg_counter() 43 zm.beg_skill()
抽象类与接口类
继承有两种用途:
一:继承基类的方法,并且做出自己的改变或者扩展(代码重用)
二:声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) p = Alipay() pay(p,200)
开发中容易出现的问题
class Alipay: ''' 支付宝支付 ''' def pay(self,money): print('支付宝支付了%s元'%money) class Applepay: ''' apple pay支付 ''' def pay(self,money): print('apple pay支付了%s元'%money) class Wechatpay: def fuqian(self,money): ''' 实现了pay的功能,但是名字不一样 ''' print('微信支付了%s元'%money) def pay(payment,money): ''' 支付函数,总体负责支付 对应支付的对象和要支付的金额 ''' payment.pay(money) p = Wechatpay() pay(p,200) #执行会报错
接口初成:手动报异常:NotImplementedError来解决开发中遇到的问题
class Payment: def pay(self): raise NotImplementedError class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #这里不报错 pay(p,200) #这里报错了
借用abc模块来实现接口
from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): @abstractmethod def pay(self,money): pass class Wechatpay(Payment): def fuqian(self,money): print('微信支付了%s元'%money) p = Wechatpay() #不调就报错了
实践中,继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。
在python中根本就没有一个叫做interface的关键字,上面的代码只是看起来像接口,其实并没有起到接口的作用,子类完全可以不用去实现接口 ,如果非要去模仿接口的概念,可以借助第三方模块
抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
#一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type='file' @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): '子类必须定义读功能' pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): '子类必须定义写功能' pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('文本数据的读取方法') def write(self): print('文本数据的读取方法') class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('硬盘数据的读取方法') def write(self): print('硬盘数据的读取方法') class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print('进程数据的读取方法') def write(self): print('进程数据的读取方法') wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家都是被归一化了,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type)
多态
多态指的是一类事物有多种形态
如:
class Person: def __init__(self,name,age,money,single=True): self.name=name #实例属性 self.age=age self.money=money self._single=True #私有属性 class landlord(Person): def __init__(self): print("这是一个地主") class prince(Person): def __init__(self): print("这是一个王子") class Begger(Person):#乞丐类 count=0 #这是一个类属性 #重写父类__init__ def __init__(self,name,age,money,single=True): super(Begger,self).__init__(name,age,money,single) #或者 # Person.__init__(self,name,age,money,single) Begger.count+=1 def beg(self): # 定义一个要饭函数,普通类方法 print('求各位给个馒头吃') @property def age(self): return self._age @age.setter def age(self,value): self._age=value @age.deleter def age(self): del self._age @staticmethod def beg_skill():#定义一个乞讨技巧函数,静态方法 print("乞讨技巧") @classmethod def beg_counter(cls): print("小乞丐个数:%s" % Begger.count) if __name__ == '__main__': zm=Begger('古墓派掌门','18',-10) zm.beg_counter() zm.beg_skill() wz=prince() dz=landlord()
鸭子类型
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
python程序员通常根据这种行为来编写程序。例如,如果想编写现有对象的自定义版本,可以继承该对象
也可以创建一个外观和行为像,但与它无任何关系的全新对象,后者通常用于保存程序组件的松耦合度。
结语
“以上就是面向对象的解释了,小家伙可还满意”老叫花拍了拍酒葫芦道
“前辈你懂这么多为什么还是一个要饭的啊”
“其实我以前跟他一样”说着,老叫花指了指对面的那个年轻人
“前辈,你再给我讲点其他的呗”
“好好,回头老叫花我慢慢给你讲点其他的,不过你得给我弄点酒。。。。。。。。。。。。”