初识
面向对象初识
面向过程编程&函数式编程
回顾刚接触编程时,面对一个待解决的问题,想到的解决方案即为一步步按我们的习惯来加以实现,以测量对象中的元素个数为例:
s='I miss WL' count=0 for i in s: count+=1 print(count) #9
后来,我们接触了函数,了解到可以通过编写函数来为同一个目的的任务统一规划,大大节省了代码量:
def fun(s): count=0 for i in s: count+=1 return count print(fun('I miss WL')) #9 print(fun([4,2,9,2,2,6])) #6
通过比较不难看出,函数式编程较之与面向过程编程有着最为明显的两个特点:
1、代码重用性高
2、代码可读性强
函数式编程&面向对象编程
以日常购物为例,函数式编程方式如下:
def shopping(username,money): pass def check_paidgoods(username,money): pass def check_unpaidgoods(username,money): pass def save(username,money): pass
学习了面向对象的思想后,其编程风格变成了下面这样:
class ShoppingCar: def shopping(username,money): pass def check_paidgoods(username,money): pass def check_unpaidgoods(username,money): pass def save(username,money): pass
通过比较不难看出面向对象编程较之于函数式编程有如下优势:
1、面向对象编程是一类相似功能函数的集合,使你的代码更清晰化,更合理化
2、面向对象即要求用上帝的视角看问题,类其实就是一个公共模板(厂房),对象就从具体的模板实例化出来
另外,强行补充一下:
类:就是具有相同属性和功能的一类事物
对象:就是类的具体表现
类的结构
类的定义主要包含两方面:静态属性和动态属性。
其中静态属性指的是属性、静态变量或静态字段;动态属性指的是函数或方法
class Human: """ 此类主要是构建人类 """ mind = '有思想' # 第一部分:静态属性 属性 静态变量 静态字段 dic = {} l1 = [] def work(self): # 第二部分:方法 函数 动态属性 print('人类会工作')
在上述代码中,class是关键字,用于定义一个类,其用法与def相同
Human为类名,类名的命名方式为驼峰命名法,即首字母大写,私有类可以用一个下划线开头
从类名的角度研究类
类名操作静态属性
case one:类名.__dict__方式
class Human: mind='有思想' dic={} l1=[] def work(self): print('人类会工作') #通过这种方式只能查询不能修改 print(Human.__dict__) #{'__module__': '__main__', 'mind': '有思想', 'dic': {}, 'l1': [], 'work': <function Human.work at 0x0000014589CE20D0>, '__dict__': <attribute '__dict__' of 'Human' objects>, '__weakref__': <attribute '__weakref__' of 'Human' objects>, '__doc__': None} print(Human.__dict__['mind']) #有思想 # Human.__dict__['mind']='无脑' #错误 # print(Human.__dict__)
case two:万能的点.
class Human: mind='有思想' dic={} l1=[] def work(self): print('人类会工作') #通过万能的点.可以增删改查类中的单个属性 print(Human.mind) #有思想 Human.mind='无脑' print(Human.mind) #无脑 del Human.mind Human.walk='走路' print(Human.walk) #走路
简单可总结一下:如果想要查询类中的所有内容,通过第一种__dict__方法;如果只是操作单个属性则用万能的点的方式
类名操作动态方法
前提:除了两个特殊方法(静态方法、类方法)之外,一般不会通过类名操作一个类中的方法
class Human: mind='有思想' dic={} l1=[] def work(self): print('人类会工作') def tools(self): print('人类会使用工具') Human.work(111) #人类会工作 Human.tools(111) #人类会使用工具 #下面这种方式可以用,但一般都不用 Human.__dict__['work'](111) #人类会工作
从对象的角度研究类
什么是对象
对象是从类中出来的,只要是类名加上(),这就是一个实例化过程,这样就会实例化一个对象
下面我们俩创建一个类并随之创建一个对象:
class Human: mind='有思想' def __init__(self): print(666) print(self) def work(self): print('人类会工作') def tools(self): print('人类会使用工具') obj=Human() #666 <__main__.Human object at 0x000001FDD85C1DD8> print(obj) #<__main__.Human object at 0x0000022F9FFD1CF8>
观察执行的结果,我们不难推测实例化一个对象时究竟发生了什么?
1、在内存中开辟了一个对象空间
2、自动执行类中的__init__方法,并将这个对象空间(内存地址)传给了__init__方法的第一个位置参数self
3、在__init__方法中通过self给对象空间添加属性
完整实例如下:
class Human: mind = '有思想' language = '使用语言' def __init__(self,name,sex,age,hobby): # self 和 obj 指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性。 self.n = name self.s = sex self.a = age self.h = hobby obj = Human('barry','男',18,'运动')
对象操作对象空间属性
case one:对象查看对象中的所有属性。对象.__dict__
class Human: mind='有思想' language='实用语言' def __init__(self,name,sex,age,hobby): #self和obj指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性 self.name=name self.sex=sex self.age=age self.hobby=hobby obj=Human('凝宝','男',23,'摄影') print(obj.__dict__) #{'name': '凝宝', 'sex': '男', 'age': 23, 'hobby': '摄影'}
case two:对象操作对象中的单个属性。万能的点.
class Human: mind='有思想' language='实用语言' def __init__(self,name,sex,age,hobby): #self和obj指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性 self.name=name self.sex=sex self.age=age self.hobby=hobby obj=Human('凝宝','男',23,'摄影') obj.job='IT' #增 del obj.name #删 obj.sex='女' #改 print(obj.sex) #女 #查 print(obj.__dict__) #{'sex': '女', 'age': 23, 'hobby': '摄影', 'job': 'IT'}
对象查看类中的属性
class Human: mind='有思想' language='实用语言' def __init__(self,name,sex,age,hobby): #self和obj指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性 self.name=name self.sex=sex self.age=age self.hobby=hobby obj=Human('凝宝','男',23,'摄影') print(obj.mind) #有思想 print(obj.language) #实用语言 obj.age=18 print(obj.age) #18
对象操作类中的方法
class Human: mind='有思想' language='实用语言' def __init__(self,name,sex,age,hobby): #self和obj指向的是同一个内存地址同一个空间,下面就是通过self给这个对象空间封装四个属性 self.name=name self.sex=sex self.age=age self.hobby=hobby def work(self): print(self) print('人类会工作') def tools(self): print('人类会使用工具') obj=Human('凝宝','男',23,'摄影') obj.work() #<__main__.Human object at 0x0000019F575286D8> 人类会工作 obj.tools() #人类会使用工具
类中的方法一般都是通过对象执行的(出去类方法和静态方法外),并且对象执行这些方法都会自动将对象空间传给方法中的第一个参数self
那么我们不禁疑问,self是什么?
self其实就是类中方法(函数)的第一个位置参数,只不过解释器会自动将调用这个函数的对象传给self。所以我们要把类中的方法的第一个参数约定俗成设置成self,代表这个就是对象
类空间问题以及类之间的关系
类的空间问题
何处可以添加对象属性
对象的属性不仅可以在__init__里面添加,还可以在类的其他方法或者类的外面添加
class A: def __init__(self,name): self.name=name def func(self,sex): self.sex=sex #类的外面添加 obj=A('Barry') obj.age=18 print(obj.__dict__) #{'name': 'Barry', 'age': 18} #类内部也可以 obj=A('barry') #__init__方法可以 obj.func('男') #func方法也可以
何处可以添加类的静态属性
类的属性不仅可以在类内部添加,还可以在类的外部添加
class A: def __init__(self,name): self.name=name def func(self,sex): self.sex=sex def func1(self): A.bbb='ccc' #类的外面添加 A.aaa='NB' print(A.__dict__) #{'__module__': '__main__', '__init__': <function A.__init__ at 0x000001CA793C20D0>, 'func': <function A.func at 0x000001CA7A34A9D8>, 'func1': <function A.func1 at 0x000001CA7A34AA60>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'aaa': 'NB'} #类内部也可以 A.func1(111) print(A.__dict__) #{'__module__': '__main__', '__init__': <function A.__init__ at 0x000001CA793C20D0>, 'func': <function A.func at 0x000001CA7A34A9D8>, 'func1': <function A.func1 at 0x000001CA7A34AA60>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None, 'aaa': 'NB', 'bbb': 'ccc'}
对象如何找到类的属性
对象查找属性的顺序:先从对象空间找 ------> 类空间找 ------> 父类空间找 ------->.....
类名查找属性的顺序:先从本类空间找 -------> 父类空间找--------> ........
上面的顺序都是单向不可逆,类名不可能找到对象的属性。
类与类之间的关系
⼤千世界, 万物之间皆有规则和规律. 我们的类和对象是对⼤千世界中的所有事物进⾏归类. 那事物之间存在着相对应的关系. 类与类之间也同样如此. 在⾯向对象的世界中. 类与类中存在以下关系:
1. 依赖关系
2. 关联关系
3. 组合关系
4. 聚合关系
5. 实现关系
6. 继承关系(类的三大特性之一:继承。
依赖关系
将一个类的对象或者类名传到另一个类的方法使用。我用的着你,但你不属于我,这种关系是最弱的。
以冰箱装大象为例:
class Elphant: def __init__(self, name): self.name = name def open(self,obj1): ''' 开⻔ ''' print('大象要开门了,默念三声,开') obj1.open_door() def close(self): ''' 关⻔ ''' print('大象要关门了,默念三声,关') class Refrigerator: def open_door(self): print("冰箱⻔被打开了") def close_door(self): print("冰箱⻔被关上了") elphant1 = Elphant('大象') haier = Refrigerator() elphant1.open(haier)
关联、聚合、组合关系
其实这三个在代码上写法是⼀样的. 但是, 从含义上是不⼀样的.
1. 关联关系. 两种事物必须是互相关联的. 但是在某些特殊情况下是可以更改和更换的.
2. 聚合关系. 属于关联关系中的⼀种特例. 侧重点是xxx和xxx聚合成xxx. 各⾃有各⾃的声明周期. 比如电脑. 电脑⾥有CPU, 硬盘, 内存等等. 电脑挂了. CPU还是好的. 还是完整的个体
3. 组合关系. 属于关联关系中的⼀种特例. 写法上差不多. 组合关系比聚合还要紧密. 比如⼈的⼤脑, ⼼脏, 各个器官. 这些器官组合成⼀个⼈. 这时. ⼈如果挂了. 其他的东⻄也跟着挂了
继承
什么是面向对象的继承?
比较官方的说法就是:
继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。
字面意思就是:子承父业,合法继承家产,就是如果你是独生子,而且你也很孝顺,不出意外,你会继承你父母所有家产,他们的所有财产都会由你使用(败家子儿除外)。
class Person: def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex class Cat: def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex class Dog: def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex # 继承的用法: class Aniaml(object): def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex class Person(Aniaml): pass class Cat(Aniaml): pass class Dog(Aniaml): pass
继承的优点也是显而易见的:
1,增加了类的耦合性(耦合性不宜多,宜精)。
2,减少了重复代码。
3,使得代码更加规范化,合理化。
继承的分类
就像上面的例子:
Aminal 叫做父类,基类,超类。
Person Cat Dog: 子类,派生类。
继承:可以分单继承,多继承。
这里需要补充一下python中类的种类(继承需要):
在python2x版本中存在两种类.:
⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
python3x版本中只有一种类:
python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object
单继承
类名,对象执行父类方法
class Aniaml(object): type_name = '动物类' def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def eat(self): print(self) print('吃东西') class Person(Aniaml): pass class Cat(Aniaml): pass class Dog(Aniaml): pass # 类名:可以调用父类的属性,方法。 print(Person.type_name) #动物类 Person.eat(111)#111 吃东西 print(Person.type_name) #动物类 # 对象: # 实例化对象 p1 = Person('凝宝','男',18) print(p1.__dict__) #{'name': '凝宝', 'age': 18, 'sex': '男'} # 对象执行类的父类的属性,方法。 print(p1.type_name) #动物类 p1.type_name = '666' print(p1) #<__main__.Person object at 0x00000256497F0F60> p1.eat() #<__main__.Person object at 0x00000256497F0F60> 吃东西
执行顺序
class Aniaml(object): type_name = '动物类' def __init__(self, name, sex, age): self.name = name self.age = age self.sex = sex def eat(self): print(self) print('吃东西') class Person(Aniaml): def eat(self): print('%s 吃饭' % self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass p1 = Person('barry', '男', 18) # 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。 p1.eat() #barry 吃饭 # 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。
同时执行类以及父类方法
method one:如果想执行父类的func方法,这个方法并且子类中也用,那么就在子类的方法中写上:父类.func(对象,其他参数)
class Aniaml(object): type_name = '动物类' def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def eat(self): print('吃东西') class Person(Aniaml): def __init__(self,name,sex,age,mind): Aniaml.__init__(self,name,sex,age) # 方法一 self.mind = mind def eat(self): super().eat() print('%s 吃饭'%self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass # 方法一: Aniaml.__init__(self,name,sex,age) p1 = Person('凝宝','laddboy',18,'有思想') print(p1.__dict__) #{'name': '凝宝', 'age': 18, 'sex': 'laddboy', 'mind': '有思想'}
method two:利用super,super().func(参数)
class Aniaml(object): type_name = '动物类' def __init__(self,name,sex,age): self.name = name self.age = age self.sex = sex def eat(self): print('吃东西') class Person(Aniaml): def __init__(self,name,sex,age,mind): #super(Person,self).__init__(name,sex,age) # 方法二 super().__init__(name,sex,age) # 方法二 self.mind = mind def eat(self): super().eat() print('%s 吃饭'%self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass p1 = Person('凝宝','laddboy',18,'有思想') print(p1.__dict__) #{'name': '凝宝', 'age': 18, 'sex': 'laddboy', 'mind': '有思想'}
多继承
悟空镇楼
class ShenXian: # 神仙 def fei(self): print("神仙都会⻜") class Monkey: # 猴 def chitao(self): print("猴⼦喜欢吃桃⼦") class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是⼀只猴 pass sxz = SunWukong() # 孙悟空 sxz.chitao() # 会吃桃⼦ sxz.fei() # 会⻜
此时, 孙悟空是⼀只猴⼦, 同时也是⼀个神仙. 那孙悟空继承了这两个类. 孙悟空⾃然就可以执⾏这两个类中的⽅法. 多继承⽤起来简单. 也很好理解. 但是多继承中, 存在着这样⼀个问题. 当两个⽗类中出现了重名⽅法的时候. 这时该怎么办呢? 这时就涉及到如何查找⽗类⽅法的这么⼀个问题.即MRO(method resolution order) 问题. 在python中这是⼀个很复杂的问题. 因为在不同的python版本中使⽤的是不同的算法来完成MRO的.
这里需要补充一下python中类的种类(继承需要):
在python2x版本中存在两种类.:
⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
python3x版本中只有一种类:
python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object
经典类的多继承
虽然在python3中已经不存在经典类了. 但是经典类的MRO最好还是学⼀学. 这是⼀种树形结构遍历的⼀个最直接的案例. 在python的继承体系中. 我们可以把类与类继承关系化成⼀个树形结构的图. 上代码:
class A: pass class B(A): pass class C(A): pass class D(B, C): pass class E: pass class F(D, E): pass class G(F, D): pass class H: pass class Foo(H, G): pass
对付这种mro画图就可以:
继承关系图已经有了. 那如何进⾏查找呢? 记住⼀个原则. 在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个.
图中每个圈都是准备要送鸡蛋的住址. 箭头和⿊线表⽰线路. 那送鸡蛋的顺序告诉你入⼝在最下⾯R. 并且必须从左往右送. 那怎么送呢?
如图. 肯定是按照123456这样的顺序来送. 那这样的顺序就叫深度优先遍历. ⽽如果是142356呢? 这种被称为⼴度优先遍历. 好了. 深度优先就说这么多. 那么上⾯那个图怎么找的呢? MRO是什么呢? 很简单. 记住. 从头开始. 从左往右. ⼀条路跑到头, 然后回头. 继续⼀条路跑到头. 就是经典类的MRO算法.
类的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C.
新式类的多继承
mro序列
MRO是一个有序列表L,在类被创建时就计算出来。
通用计算公式为:
mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )
(其中Child继承自Base1, Base2)
如果继承至一个基类:class B(A)这时B的mro序列为
mro( B ) = mro( B(A) ) = [B] + merge( mro(A) + [A] ) = [B] + merge( [A] + [A] ) = [B,A]
如果继承至多个基类:class B(A1, A2, A3 …)这时B的mro序列
mro(B) = mro( B(A1, A2, A3 …) ) = [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] ) = ...
计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。
表头和表尾
表头:
列表的第一个元素
表尾:
列表中表头以外的元素集合(可以为空)
示例
列表:[A, B, C]
表头是A,表尾是B和C
列表之间的+操作
+操作:
[A] + [B] = [A, B]
(以下的计算中默认省略)
---------------------
merge操作示例:
如计算merge( [E,O], [C,E,F,O], [C] ) 有三个列表 : ① ② ③ merge不为空,取出第一个列表列表①的表头E,进行判断 各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表 取出列表②的表头C,进行判断 C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除 merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] ) 进行下一次新的merge操作 ...... ---------------------
计算mro(A)方式:
mro(A) = mro( A(B,C) ) 原式= [A] + merge( mro(B),mro(C),[B,C] ) mro(B) = mro( B(D,E) ) = [B] + merge( mro(D), mro(E), [D,E] ) # 多继承 = [B] + merge( [D,O] , [E,O] , [D,E] ) # 单继承mro(D(O))=[D,O] = [B,D] + merge( [O] , [E,O] , [E] ) # 拿出并删除D = [B,D,E] + merge([O] , [O]) = [B,D,E,O] mro(C) = mro( C(E,F) ) = [C] + merge( mro(E), mro(F), [E,F] ) = [C] + merge( [E,O] , [F,O] , [E,F] ) = [C,E] + merge( [O] , [F,O] , [F] ) # 跳过O,拿出并删除 = [C,E,F] + merge([O] , [O]) = [C,E,F,O] 原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C]) = [A,B] + merge( [D,E,O], [C,E,F,O], [C]) = [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳过E = [A,B,D,C] + merge([E,O], [E,F,O]) = [A,B,D,C,E] + merge([O], [F,O]) # 跳过O = [A,B,D,C,E,F] + merge([O], [O]) = [A,B,D,C,E,F,O] ---------------------
封装
封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容。因此,在使用面向对象的封装特性时,需要:
1、将内容封装到某处
2、从某处调用被封装的内容
第一步:将内容封装到某处
self 是一个形式参数,当执行 obj1 = Foo('wupeiqi', 18 ) 时,self 等于 obj1
当执行 obj2 = Foo('alex', 78 ) 时,self 等于 obj2
所以,内容其实被封装到了对象 obj1 和 obj2 中,每个对象中都有 name 和 age 属性,在内存里类似于下图来保存。
第二步:从某处调用被封装的内容
调用被封装的内容时,有两种情况:
- 通过对象直接调用
- 通过self间接调用
1、通过对象直接调用被封装的内容
上图展示了对象 obj1 和 obj2 在内存中保存的方式,根据保存格式可以如此调用被封装的内容:对象.属性名
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Foo: def __init__( self , name, age): self .name = name self .age = age obj1 = Foo( 'wupeiqi' , 18 ) print obj1.name # 直接调用obj1对象的name属性 print obj1.age # 直接调用obj1对象的age属性 obj2 = Foo( 'alex' , 73 ) print obj2.name # 直接调用obj2对象的name属性 print obj2.age # 直接调用obj2对象的age属性 |
2、通过self间接调用被封装的内容
执行类中的方法时,需要通过self间接调用被封装的内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
class Foo: def __init__( self , name, age): self .name = name self .age = age def detail( self ): print self .name print self .age obj1 = Foo( 'wupeiqi' , 18 ) obj1.detail() # Python默认会将obj1传给self参数,即:obj1.detail(obj1),所以,此时方法内部的 self = obj1,即:self.name 是 wupeiqi ;self.age 是 18 obj2 = Foo( 'alex' , 73 ) obj2.detail() # Python默认会将obj2传给self参数,即:obj1.detail(obj2),所以,此时方法内部的 self = obj2,即:self.name 是 alex ; self.age 是 78 |
综上所述,对于面向对象的封装来说,其实就是使用构造方法将内容封装到 对象 中,然后通过对象直接或者self间接获取被封装的内容。
多态
多态,简单的说就是同一个对象,多种形态。python默认支持多态
# 在java或者c#定义变量或者给函数传值必须定义数据类型,否则就报错。 def func(int a): print('a必须是数字') # 而类似于python这种弱定义类语言,a可以是任意形态(str,int,object等等)。 def func(a): print('a是什么都可以') # 再比如: class F1: pass class S1(F1): def show(self): print 'S1.show' class S2(F1): def show(self): print 'S2.show' # 由于在Java或C#中定义函数参数时,必须指定参数的类型 # 为了让Func函数既可以执行S1对象的show方法,又可以执行S2对象的show方法,所以,定义了一个S1和S2类的父类 # 而实际传入的参数是:S1对象和S2对象 def Func(F1 obj): """Func函数需要接收一个F1类型或者F1子类的类型""" print obj.show() s1_obj = S1() Func(s1_obj) # 在Func函数中传入S1类的对象 s1_obj,执行 S1 的show方法,结果:S1.show s2_obj = S2() Func(s2_obj) # 在Func函数中传入Ss类的对象 ss_obj,执行 Ss 的show方法,结果:S2.show Python伪代码实现Java或C # 的多态
python中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。 对于代码上的解释其实很简答: class A: def f1(self): print('in A f1') def f2(self): print('in A f2') class B: def f1(self): print('in A f1') def f2(self): print('in A f2') obj = A() obj.f1() obj.f2() obj2 = B() obj2.f1() obj2.f2() # A 和 B两个类完全没有耦合性,但是在某种意义上他们却统一了一个标准。 # 对相同的功能设定了相同的名字,这样方便开发,这两个方法就可以互成为鸭子类型。 # 这样的例子比比皆是:str tuple list 都有 index方法,这就是统一了规范。 # str bytes 等等 这就是互称为鸭子类型。
类的成员
细分类的组成成员
之前已经大致了解过类大致分两块区域:静态字段(静态变量)部分和方法部分
每个区域详细划分又可以分为:
class A: company_name = '朝朝暮暮' # 静态变量(静态字段) __iphone = '1353333xxxx' # 私有静态变量(私有静态字段) def __init__(self,name,age): #特殊方法 self.name = name #对象属性(普通字段) self.__age = age # 私有对象属性(私有普通字段) def func1(self): # 普通方法 pass def __func(self): #私有方法 print(666) @classmethod # 类方法 def class_func(cls): """ 定义类方法,至少有一个cls参数 """ print('类方法') @staticmethod #静态方法 def static_func(): """ 定义静态方法 ,无默认参数""" print('静态方法') @property # 属性 def prop(self): pass
类的私有成员
对于每一个类的成员而言都有两种形式:
- 公有成员,在任何地方都能访问
- 私有成员,只有在类的内部才能方法
私有成员和公有成员的访问限制不同:
静态字段(静态属性)
- 公有静态字段:类可以访问;类内部可以访问;派生类中可以访问
- 私有静态字段:仅类内部可以访问
普通字段(对象属性)
- 公有普通字段:对象可以访问;类内部可以访问;派生类中可以访问
- 私有普通字段:仅类内部可以访问;
方法:
- 公有方法:对象可以访问;类内部可以访问;派生类中可以访问
- 私有方法:仅类内部可以访问;
总结:
对于这些私有成员来说,他们只能在类的内部使用,不能再类的外部以及派生类中使用.
ps:非要访问私有成员的话,可以通过 对象._类__属性名,但是绝对不允许!!!
为什么可以通过._类__私有成员名访问呢?因为类在创建时,如果遇到了私有成员(包括私有静态字段,私有普通字段,私有方法)它会将其保存在内存时自动在前面加上_类名.
类的其他成员
这里的其他成员主要就是类方法:
方法包括:普通方法、静态方法和类方法,三种方法在内存中都归属于类,区别在于调用方式不同。
实例方法
定义:第一个参数必须是实例对象,该参数名一般约定为“self”,通过它来传递实例的属性和方法(也可以传类的属性和方法);
调用:只能由实例对象调用。
类方法
定义:使用装饰器@classmethod。第一个参数必须是当前类对象,该参数名一般约定为“cls”,通过它来传递类的属性和方法(不能传实例的属性和方法);
调用:实例对象和类对象都可以调用。
静态方法
定义:使用装饰器@staticmethod。参数随意,没有“self”和“cls”参数,但是方法体中不能使用类或实例的任何属性和方法;
调用:实例对象和类对象都可以调用。
双下方法(后面会讲到)
定义:双下方法是特殊方法,他是解释器提供的 由爽下划线加方法名加爽下划线 __方法名__的具有特殊意义的方法,双下方法主要是python源码程序员使用的,
我们在开发中尽量不要使用双下方法,但是深入研究双下方法,更有益于我们阅读源码。
调用:不同的双下方法有不同的触发方式,就好比盗墓时触发的机关一样,不知不觉就触发了双下方法,例如:__init__
实例方法
简而言之,实例方法就是类的实例能够使用的方法。
类方法
使用装饰器@classmethod。
原则上,类方法是将类本身作为对象进行操作的方法。假设有个方法,且这个方法在逻辑上采用类本身作为对象来调用更合理,那么这个方法就可以定义为类方法。另外,如果需要继承,也可以定义为类方法。
如下场景:
假设我有一个学生类和一个班级类,想要实现的功能为:
执行班级人数增加的操作、获得班级的总人数;
学生类继承自班级类,每实例化一个学生,班级人数都能增加;
最后,我想定义一些学生,获得班级中的总人数。
思考:这个问题用类方法做比较合适,为什么?因为我实例化的是学生,但是如果我从学生这一个实例中获得班级总人数,在逻辑上显然是不合理的。同时,如果想要获得班级总人数,如果生成一个班级的实例也是没有必要的。
class Student: __num = 0 def __init__(self,name,age): self.name = name self.age= age Student.addNum() # 写在__new__方法中比较合适,但是现在还没有学,暂且放到这里 @classmethod def addNum(cls): cls.__num += 1 @classmethod def getNum(cls): return cls.__num a = Student('太白金星', 18) b = Student('武sir', 36) c = Student('alex', 73) print(Student.getNum())
静态方法
使用装饰器@staticmethod。
静态方法是类中的函数,不需要实例。静态方法主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个类的名称空间中,便于使用和维护。
譬如,我想定义一个关于时间操作的类,其中有一个获取当前时间的函数。
import time class TimeTest(object): def __init__(self, hour, minute, second): self.hour = hour self.minute = minute self.second = second @staticmethod def showTime(): return time.strftime("%H:%M:%S", time.localtime()) print(TimeTest.showTime()) t = TimeTest(2, 10, 10) nowTime = t.showTime() print(nowTime)
如上,使用了静态方法(函数),然而方法体中并没使用(也不能使用)类或实例的属性(或方法)。若要获得当前时间的字符串时,并不一定需要实例化对象,此时对于静态方法而言,所在类更像是一种名称空间。
属性
什么是特性property
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
为什么要用property
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则
由于新式类中具有三种访问方式,我们可以根据他们几个属性的访问特点,分别将三个方法定义为对同一个属性:获取、修改、删除
isinstace 与 issubclass
isinstance(a,b):判断a是否是b类(或者b类的派生类)实例化的对象
issubclass(a,b): 判断a类是否是b类(或者b的派生类)的派生类