面向对象2.0
什么是面向对象?
面向对象是一种编程范式。OOP编程是利用“类”和“对象”来创建各种模型来实现对真实世界的描述
面向过程 = 个人视角
我要去做大保健,我只需考虑,我有没有钱,去哪家店,怎么去,做什么价位的就可以,你的每一步都要通过程序定义出来,写死了,在这个程序里,你只被设定了去做大保健的功能,你说中途我想去个ktv,那可能会导致整个程序的逻辑都得更改。 用面向过程的方式写代码,那你care的就是整个事情的执行过程
面向对象 = 上帝视角
如果你是上帝,你现在要创世纪,把这么多人、动物、山河造出来,上帝光靠自己干,一个一个的造人,多累呀,让你干这个活,你肯定是先造模子,一个男人模子,一个女人模子,剩下的就一个个复制就行啦。这个模子的作用是什么?模子定义了人这个物种所具备的所有特征\(或者说,我们把具备这些特征的个体归为人类\)。
这个世界上所有的东西都是你定义的,你需要用最高效的方式去造世界,最高效的方式就是,先把世界按物种、样貌、有无生命等各种维度分类,然后给每类东西建模型,再让其在不脱离你基本横型定义的框架下,自我繁衍(世界要多姿多彩,所以即使是同一物种,也要有些不一样)
为什么要使用面向对象?
1、使程序更加容易扩展和易更改,使开发效率变的更高
2、基于面向对象的程序可以使它人更加容易理解你的代码逻辑,从而使团队开发变得更从容。
面向过程和面向对象优缺点对比
面向过程的优点:复杂问题流程化,简单化(把一个复杂的问题,分成一个个小问题去一个个解决。)
缺点:扩展性差, 牵一发动全身。程序维护的难度大
面向对象的优点:可扩展性强。
缺点:编程复杂度高于面向过程
属性查找
1、类的数据属性是所有对象共享的
#类的数据属性是所有对象共享的,id都一样
print(id(OldboyStudent.school))
print(id(s1.school)) #4377347328
print(id(s2.school)) #4377347328
print(id(s3.school)) #4377347328
2、类的函数数据是绑定给对象用的,称为绑定到对象的方法
#类的函数属性是绑定给对象使用的,obj.method称为绑定方法,内存地址都不一样
print(OldboyStudent.learn) #<function OldboyStudent.learn at 0x1021329d8>
print(s1.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x1021466d8>>
print(s2.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146710>>
print(s3.learn) #<bound method OldboyStudent.learn of <__main__.OldboyStudent object at 0x102146748>>
#ps:id是python的实现机制,并不能真实反映内存地址,如果有内存地址,还是以内存地址为准
绑定方法
lass OldboyStudent:
school='oldboy'
def __init__(self,name,age,sex):
self.name=name
self.age=age
self.sex=sex
def learn(self):
print('%s is learning' %self.name) #新增self.name
def eat(self):
print('%s is eating' %self.name)
def sleep(self):
print('%s is sleeping' %self.name)
s1=OldboyStudent('李坦克','男',18)
s2=OldboyStudent('王大炮','女',38)
s3=OldboyStudent('牛榴弹','男',78)
类中定义的函数(没有被任何装饰器装饰的)是类的函数属性,类可以使用,但必须遵循函数的参数规则,有几个参数需要传几个参数
s1.learn() #等同于OldboyStudent.learn(s1)
s2.learn() #等同于OldboyStudent.learn(s2)
s3.learn() #等同于OldboyStudent.learn(s3)
注意:绑定到对象的方法的这种自动传值的特征,决定了在类中定义的函数都要默认写一个参数self,self可以是任意名字,但是约定俗成地写出self。
封装
什么是封装?
在类中对数据的赋值、内部调用对外部用户是透明的,这使类变成了一个胶囊或容器,里面包含着类的数据和方法
封装的作用
1、封装数据,防止数据被随意修改。
2、隔离复杂度,使外部程序不需要关注对象内部的构造,只要通过此对外提供的接口进行访问即可
如何隐藏?
#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:
class A:
__N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
def __init__(self):
self.__X=10 #变形为self._A__X
def __foo(self): #变形为_A__foo
print('from A')
def bar(self):
self.__foo() #只有在类内部才可以通过__foo的形式访问到.
#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形
变形需要注意的问题
1、这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N
2、变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形。
实例代码
class B:
__x = 1
def __init__(self,name):
self.__name=name # self._B__name=name
b = B('egon')
# 验证问题1
print(b._B__name) # egon
b.__age = 18
# 验证问题2
print(b.__dict__) # {'_B__name': 'egon', '__age': 18}
print(b.__age)
3、在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
引子:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __author__:JasonLIN
class A:
def foo(self):
print('A.foo')
def bar(self):
print('A.bar') # 按照继承查找的顺序,访问的是B的foo
self.foo()
class B(A):
def foo(self):
print('B.foo')
b=B()
b.bar()
需求:就是要在调用b.bar()时访问类A的foo方法
class A:
def __foo(self): # _A__foo
print('A.foo')
def bar(self):
print('A.bar')
self.__foo() # self._A__foo() 访问A的foo
class B(A):
def __foo(self): # _B__foo
print('B.foo')
b=B()
b.bar()
多态
多态指的是一类事物有多种形态,简单点说:“一个接口,多种实现”
多态性
一、什么是多态动态绑定(在继承的背景下使用时,有时也称为多态性)
多态性是指在不考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性
静态多态性:如任何类型都可以用运算符+进行运算
动态多态性:如下
peo=People()
dog=Dog()
pig=Pig()
#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk()
二、为什么要用多态性(多态性的好处)
1.增加了程序的灵活性
2.增加了程序额可扩展性
鸭子类型
Python崇尚鸭子类型,即‘如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子’
序列类型有多种形态:字符串,列表,元组,但他们直接没有直接的继承关系
#str,list,tuple都是序列类型
s=str('hello')
l=list([1,2,3])
t=tuple((4,5,6))
#我们可以在不考虑三者类型的前提下使用s,l,t
s.__len__()
l.__len__()
t.__len__()
len(s)
len(l)
len(t)
继承
什么是继承?
继承指的是类与类之间的关系,是一种什么“是”什么的关系,继承的功能之一就是用来解决代码重用问题
python中类的继承分为:单继承和多继承
class ParentClass1: #定义父类
pass
class ParentClass2: #定义父类
pass
class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
pass
class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
# 尽量不要用多继承
pass
查看继承
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
派生
当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
继承实现原理
定义的每一个类,python会计算出一个方法解析顺序(MRO)列表
MRO列表原则
1、子类会先于父类被检查
2、多个父类会根据它们在列表中的顺序被检查
3、如果对下一个类存在两个合法的选择,选择第一个父类
关键看代码
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __author__:JasonLIN
class A(object):
def __init__(self):
print('A')
super(A, self).__init__()
class B(object):
def __init__(self):
print('B')
super(B, self).__init__()
class C(A):
def __init__(self):
print('C')
super(C, self).__init__()
class D(A):
def __init__(self):
print('D')
super(D, self).__init__()
class E(B, C):
def __init__(self):
print('E')
super(E, self).__init__()
class F(C, B, D):
def __init__(self):
print('F')
super(F, self).__init__()
class G(D, B):
def __init__(self):
print('G')
super(G, self).__init__()
if __name__ == '__main__':
g = G()
f = F()
print(G.mro())
# [<class '__main__.G'>, <class '__main__.D'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>]
组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合
1、继承的方式
通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。
当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
2、组合的方式
用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# __author__:JasonLIN
class People:
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
class Course:
def __init__(self, name, period, price):
self.name = name
self.period = period
self.price = price
def tell_info(self):
print('<%s %s %s>' % (self.name, self.period, self.price))
class Teacher(People):
def __init__(self, name, age, sex, job_title):
super().__init__(name, age, sex)
self.job_title = job_title
self.course = []
self.students = []
class Student(People):
def __init__(self, name, age, sex):
super().__init__(name, age, sex)
self.course = []
egon = Teacher('egon', 18, 'male', '沙河霸道金牌讲师')
s1 = Student('牛榴弹', 18, 'female')
python = Course('python', '3mons', 3000.0)
linux = Course('python', '3mons', 3000.0)
# 为老师egon和学生s1添加课程
egon.course.append(python)
egon.course.append(linux)
s1.course.append(python)
# 为老师egon添加学生s1
egon.students.append(s1)
# 使用
for obj in egon.course:
obj.tell_info()
总结:
当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
特性
property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。
计算园的周长和面积
mport math
class Circle:
def __init__(self,radius): #圆的半径radius
self.radius=radius
@property
def area(self):
return math.pi * self.radius**2 #计算面积
@property
def perimeter(self):
return 2*math.pi*self.radius #计算周长
c=Circle(10)
print(c.radius)
print(c.area) #可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) #同上
'''
输出结果:
314.1592653589793
62.83185307179586
'''
注意:此时的特性area和perimeter不能被赋值
c.area=3 #为特性area赋值
'''
抛出异常:
AttributeError: can't set attribute
'''
为什么要用property?
将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式
遵循了统一访问的原则
property用法
class People:
def __init__(self, name):
self.__name = name
@property
def name(self):
# print('getter')
return self.__name
@name.setter
def name(self, val):
# print('setter',val)
if not isinstance(val, str):
print('名字必须是字符串类型')
return
self.__name = val
@name.deleter
def name(self):
print('deleter')
print('不允许删除')
p = People('egon')
print(p.name)
p.name = 'EGON'
print(p.name)
# del p.name
绑定方法与非绑定方法
在类内部定义的函数,分为两大类:
一:绑定方法:绑定给谁,就应该由谁来调用,谁来调用就回把调用者当作第一个参数自动传入绑定到对象的方法:在类内定义的没有被任何装饰器修饰的绑定到类的方法:在类内定义的被装饰器classmethod修饰的方法
二:非绑定方法:没有自动传值这么一说了,就类中定义的一个普通工具,对象和类都可以使用
非绑定方法:不与类或者对象绑定。
class Foo:
def __init__(self, name):
self.name = name
def tell(self): # 绑定到对象
print('名字是%s' % self.name)
@classmethod # 绑定到类
def func(cls): # cls=Foo
print(cls)
@staticmethod # 非绑定方法,不会自动传值
def func1(x, y):
print(x+y)
f = Foo('egon')
Foo.func1(1, 2)
f.func1(1, 3)
绑定方法和非绑定方法的应用
import settings
import hashlib
import time
class People:
def __init__(self,name,age,sex):
self.id=self.create_id()
self.name=name
self.age=age
self.sex=sex
def tell_info(self): # 绑定到对象的方法
print('Name:%s Age:%s Sex:%s' %(self.name,self.age,self.sex))
@classmethod
def from_conf(cls):
obj=cls(
settings.name,
settings.age,
settings.sex
)
return obj
@staticmethod
def create_id():
m=hashlib.md5(str(time.time()).encode('utf-8'))
return m.hexdigest()
# p=People('egon',18,'male')
#绑定给对象,就应该由对象来调用,自动将对象本身当作第一个参数传入
# p.tell_info() #tell_info(p)
#绑定给类,就应该由类来调用,自动将类本身当作第一个参数传入
# p=People.from_conf() #from_conf(People)
# p.tell_info()
#非绑定方法,不与类或者对象绑定,谁都可以调用,没有自动传值一说
p1=People('egon1',18,'male')
p2=People('egon2',28,'male')
p3=People('egon3',38,'male')
print(p1.id)
print(p2.id)
print(p3.id)
反射
** python面向对象中的反射:通过字符串的形式操作对象相关的属性。python中的一切事物都是对象(都可以使用反射)**
四个可以实现自省的函数 下列方法适用于类和对象(一切皆对象,类本身也是一个对象)
hasattr(object, name)
判断object中有没有一个name字符串对应的方法或属性
getattr(object, name, default=None)
def getattr(object, name, default=None): # known special case of getattr
"""
getattr(object, name[, default]) -> value
Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
When a default argument is given, it is returned when the attribute doesn't
exist; without it, an exception is raised in that case.
"""
pass
setattr(x, y, v)
def setattr(x, y, v): # real signature unknown; restored from __doc__
"""
Sets the named attribute on the given object to the specified value.
setattr(x, 'y', v) is equivalent to ``x.y = v''
"""
pass
delattr(x, y)
def delattr(x, y): # real signature unknown; restored from __doc__
"""
Deletes the named attribute from the given object.
delattr(x, 'y') is equivalent to ``del x.y''
"""
pass
代码实例:
class BlackMedium:
feature='Ugly'
def __init__(self,name,addr):
self.name=name
self.addr=addr
def sell_house(self):
print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name)
def rent_house(self):
print('%s 黑中介租房子啦,傻逼才租呢' %self.name)
b1=BlackMedium('万成置地','回龙观天露园')
#检测是否含有某属性
print(hasattr(b1,'name'))
print(hasattr(b1,'sell_house'))
#获取属性
n=getattr(b1,'name')
print(n)
func=getattr(b1,'rent_house')
func()
# getattr(b1,'aaaaaaaa') #报错
print(getattr(b1,'aaaaaaaa','不存在啊'))
#设置属性
setattr(b1,'sb',True)
setattr(b1,'show_name',lambda self:self.name+'sb')
print(b1.__dict__)
print(b1.show_name(b1))
#删除属性
delattr(b1,'addr')
delattr(b1,'show_name')
delattr(b1,'show_name111')#不存在,则报错
print(b1.__dict__)
类也是对象
class Foo(object):
staticField = "old boy"
def __init__(self):
self.name = 'wupeiqi'
def func(self):
return 'func'
@staticmethod
def bar():
return 'bar'
print getattr(Foo, 'staticField')
print getattr(Foo, 'func')
print getattr(Foo, 'bar')
内置方法
isinstance(obj,cls)检查是否obj是否是类 cls 的对象
class Foo(object):
pass
obj = Foo()
print(isinstance(obj, Foo))
issubclass(sub, super)检查sub类是否是 super 类的派生类
class Foo(object):
pass
class Bar(Foo):
pass
print(issubclass(Bar, Foo)) # True
item系列:
把对象定制成类似于字典一样的对象,可以像操作字典一样去操作类
class Foo: # Dict
def __init__(self, name):
self.name = name
def __getitem__(self, item): # item = 'namexxx'
# print('getitem...')
return self.__dict__.get(item)
def __setitem__(self, key, value):
# print('setitem...')
# print(key,value)
self.__dict__[key] = value
def __delitem__(self, key):
# print('delitem...')
# print(key)
del self.__dict__[key]
obj = Foo('egon')
print(obj.__dict__)
#
# 查看属性:
# obj.属性名
print(obj['namexxx']) # obj.name
# 设置属性:
obj.sex = 'male'
obj['sex'] = 'male'
print(obj.__dict__)
print(obj.sex)
# 删除属性
# del obj.name
# del obj['name']
print(obj.__dict__)
__del__模块(析构方法)
class Open:
def __init__(self,filename):
print('open file.......')
self.filename=filename
def __del__(self):
print('回收操作系统资源:self.close()')
f=Open('settings.py')
# del f # 手动执行f.__del__()
print('----main------') # 自动执行f.__del__()
元类
产生类的类称之为元类,默认所以用class定义的类,他们的元类是type
# 类也是对象,Foo=type(....)
class Foo:
pass
obj=Foo()
print(type(obj)) # <class '__main__.Foo'>
print(type(Foo)) # <class 'type'>
class Bar:
pass
print(type(Bar)) # <class 'type'>
前戏
储存知识exec
参数1:字符串形式的命令
参数2:全局作用域(字典形式),如果不指定默认就适应glabals()
参数3:局部作用域(字典形式),如果不指定就默认使用locals()
代码示例
g = {
'x': 1,
'y': 2
}
l = {}
exec("""
global x,m
x=10
m=100
z=3
""", g, l)
print(g)
print(l) # {'z': 3}
创建类的两种方式
方式一:使用class关键字
class Chinese(object):
country='China'
def __init__(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)
方式二:就是手动模拟class创建类的过程
Foo = type("Chinese", (object,), {})
print(Foo) # <class '__main__.Chinese'>
详细分析
#创建类主要分为三部分
1 类名
2 类的父类
3 类体
#类名
class_name='Chinese'
#类的父类
class_bases=(object,)
#类体
class_body="""
country='China'
def __init__(self,name,age):
self.name=name
self.age=age
def talk(self):
print('%s is talking' %self.name)
"""
步骤一(先处理类体->名称空间):类体定义的名字都会存放于类的名称空间中(一个局部的名称空间),我们可以事先定义一个空字典,然后用exec去执行类体的代码(exec产生名称空间的过程与真正的class过程类似,只是后者会将__开头的属性变形),生成类的局部名称空间,即填充字典
class_dic={}
exec(class_body,globals(),class_dic)
print(class_dic)
#{'country': 'China', 'talk': <function talk at 0x101a560c8>, '__init__': <function __init__ at 0x101a56668>}
步骤二:调用元类type(也可以自定义)来产生类Chinense
Foo=type(class_name,class_bases,class_dic) #实例化type得到对象Foo,即我们用class定义的类Foo
print(Foo)
print(type(Foo))
print(isinstance(Foo,type))
'''
<class '__main__.Chinese'>
<class 'type'>
True
'''
一切皆对象
一切皆对象,对象可以怎么用?
1、都可以被引用,x=obj
2、都可以当作函数的参数传入
3、都可以当作函数的返回值
4、都可以当作容器类的元素,
l=[func,time,obj,1]
自定义元类
元类内部也应有有一个call方法,会在调用Foo时触发执行
class Foo:
def __call__(self, *args, **kwargs):
print(self)
print(args) # (1, 2, 3)
print(kwargs) # {'a': 1, 'b': 1, 'c': 3}
obj = Foo()
obj(1, 2, 3, a=1, b=1, c=3) # obj.__call__(obj,1,2,3, a=1, b=1, c=1)
自定义元类三步走:
1、创建一个空对象obj
2、初始化
3、return obj
class Mymeta(type):
def __init__(self, class_name, class_bases, class_dic):
if not class_name.istitle():
raise TypeError('类名的首字母必须大写')
if '__doc__' not in class_dic or not class_dic['__doc__'].strip():
raise TypeError('必须有注释,且注释不能为空')
super(Mymeta,self).__init__(class_name, class_bases, class_dic)
def __call__(self, *args, **kwargs): #obj=Chinese('egon',age=18)
# print(self) #self=Chinese
# print(args) #args=('egon',)
# print(kwargs) #kwargs={'age': 18}
# 第一件事:先造一个空对象obj
obj=object.__new__(self)
# 第二件事:初始化obj
self.__init__(obj,*args,**kwargs)
# 第三件事:返回obj
return obj
class Chinese(object,metaclass=Mymeta):
"""中国人"""
country = 'China'
def __init__(self, name, age):
self.name = name
self.age = age
def talk(self):
print('%s is talking' % self.name)
obj = Chinese('egon', age=18) # Chinese.__call__(Chinese,'egon',18)
print(obj.__dict__)