Date: 2019-05-18
Author: Sun
1. 面向对象的思想
面向过程:面向处理,更多的是从计算机角度思考,注重计算每一个步骤,程序更像是一本cpu操作手册。
面向对象:以日常生活的角度思考问题的解决,更接近人的思维方式,让人可以从更高的层面考虑系统的构建
以你请朋友吃饭
面向过程 | 面向对象 |
---|---|
自己买菜 | 和朋友一块到饭店 |
自己摘菜 | 叫服务员点菜 |
自己洗菜 | 和朋友一块吃 |
自己做菜 | |
和朋友一块吃 |
面向对象的优点:
- 面向对象更加适合做应用的开发 --- 面向实体性编程为主,造飞机,造轮船
- 面向对象可以使你的代码更加优雅和紧凑
- 面向对象开发效率更高 ---- 将功能和数据进行隔离
- 面向对象代码复用度更高、可维护性更好
面向对象是一种思维方式,它认为万事万物皆对象,程序是由多个对象协作共同完成功能的,所以以后我们要从面向过程转向面向对象。以面向对象的方式考虑程序的构建。面向对象的核心是:类和对象
2. 类和对象
2.1 类和对象的概念
类是抽象的,在使用的时候通常会找到这个类的一个具体的存在,使用这个具体的存在。一个类可以找到多个对象。
对象是类的行为属性和数据属性进行具体化后的实例。
生活角度
- 类:具有相同特征和行为的对象的集合,是一个概念
- 对象:客观存在的一切事物,是类的实例
类: 汽车 超级英雄 电脑 杯子
对象: 红色的宝马 美国队长 桌上的mac pro 老王的黑色杯子
类就相当于制造飞机时的图纸,用它来进行创建的飞机就相当于对象
程序角度
- 类:用户自定义的数据类型,是模板,不占用内存
- 对象:由类定义的变量,占用内存
类:
成员属性(成员变量) 描述对象的静态特征,诸如,名字、身高体重
成员方法 描述对象的动态特征,例如:吃饭、睡觉、打豆豆
类和对象之间的关系
总之,类就是创建对象的模板
2.2 类的构成
类(Class) 由3个部分构成
- 类的名称:类名
- 类的属性:一组数据
- 类的方法:允许对进行操作的方法 (行为)
举例:
1)人类设计,只关心3样东西:
- 事物名称(类名):人(Person)
- 属性:身高(height)、年龄(age)
- 方法(行为/功能):跑(run)、打架(fight)
2)狗类的设计
- 类名:狗(Dog)
- 属性:品种 、毛色、性别、名字、 腿儿的数量
- 方法(行为/功能):叫 、跑、咬人、吃、摇尾巴
2.3 类的定义
#语法:
class 类名:
类体
注意:
- 类定义必须以关键字class
- 类名要符合标识符的规范
- 类名一般用大驼峰风格: 每个单词首字母大写,其它小写 ,例如MyBook YouMoney
- 类体必须缩进
- 在python3中类默认继承object,所以可以这样写 class Dog:,它等价于class Dog(object):
- 一个文件里只放一个类
(1) 定义一个Car类
# 定义类
class Car: #经典定义类,如果是Car(object)则为新式类
# 方法
def getCarInfo(self):
print(f'车轮子个数:{self.wheelNum}, 颜色:{self.color}')
def move(self):
print("车正在移动...")
说明:
- 定义类时有2种:新式类和经典类,上面的Car为经典类,如果是Car(object)则为新式类
- 类名 的命名规则按照"大驼峰"
(2) 成员方法
成员方法其实就是函数,作用域在类内,成员方法的第一个参数必须是self,self代表当前对象,也就是调用这个方法的对象,这个参数有系统传递。
class Dog(object):
def bark(self): #成员方法,第一个参数必须是self,代表当前调用对象
print('我是小可爱--丁丁')
dingding = Dog() #实例化一个对象
#调用方法,不需要传参数,self是系统传递的
#调用形式: 对象.方法([实参])
dingding.bark() #等价调用形式:bark(dingding)
注意:
- self参数在调用的时候不必传值,由系统传值
- self只能在实例方法中使用
- 方法和函数的区别:
- 方法作用域属于类,所以即便和普通函数重名,也不会被覆盖
- 方法的第一个参数必须是self,但函数不要求
- 方法必须通过对象调用,而函数不需要
- 方法的第一个参数self其实可以使任何合法标识符,不过一般约定俗成都是self
2.4 创建对象
上节2.3中定义了一个Car类;就好比有车一个张图纸,那么接下来就应该把图纸交给生成工人们去生成了。
python中,可以根据已经定义的类去创建出一个个对象。
创建对象的格式为:
对象名 = 类名()
创建对象:
# 定义类
class Car:
# 移动
def move(self):
print('车在奔跑...')
# 鸣笛
def toot(self):
print("车在鸣笛...嘟嘟..")
# 创建一个对象,并用变量BMW来保存它的引用
BMW = Car()
BMW.color = '黑色'
BMW.wheelNum = 4 #轮子数量
BMW.move()
BMW.toot()
print(BMW.color)
print(BMW.wheelNum)
说明:
- BMW = Car(),这样就产生了一个Car的实例对象,此时也可以通过实例对象BMW来访问属性或者方法
- 第一次使用BMW.color = '黑色'表示给BMW这个对象添加属性,如果后面再次出现BMW.color = xxx表示对属性进行修改
- BMW是一个对象,它拥有属性(数据)和方法(函数)
另外说明:
#语法: 对象 = 类名([实参])
dingding = Dog() #实例化一个对象
print(dingding) #<__main__.Dog object at 0x00000000023F40B8>
print(type(dingding)) #<class '__main__.Dog'>
#查看对象的类名
print(dingding.__class__)
2.5 成员属性
成员属性描述的是对象的静态特征,比如说狗名字、品种等,其实就是一个变量,作用域属于类,不会和类外的全局变量冲突。python中成员属性可以动态添加,也可以在构造函数中添加。成员属性属于对象,每个对象的成员属性都不同
- 给对象动态添加的属性只属于这个对象,其它对象没有该属性
- 使用__ slots__限制属性的动态绑定:
- 在构造函数中添加的属性属于所有对象(重点)
理解self
- 所谓的self,可以理解为自己
- 可以把self当做C++中类里面的this指针一样理解,就是对象自身的意思
- 某个对象调用其方法时,python解释器会把这个对象作为第一个参数传递给self,所以开发者只需要传递后面的参数即可
python中__slots__
作用?
当一个类需要创建大量实例时,可以通过__slots__
声明实例所需要的属性,如果超过了此属性范围进行对象属性赋值,就会限制,起到保护数据作用。
例如,
class Foo(object):
__slots__ = ['foo']
这样做带来以下优点:
- 更快的属性访问速度
- 通过取消
__dict__
的使用减少内存消耗 - 保护数据安全性
Python内置的字典本质是一个哈希表,它是一种用空间换时间的数据结构。为了解决冲突的问题,当字典使用量超过2/3时,Python会根据情况进行2-4倍的扩容。由此可预见,取消__dict__
的使用可以大幅减少实例的空间消耗。
#添加属性语法:
对象.成员属性 = 值
#引用方式:对象.成员属性
class Dog(object):
__slots__ = ('gender','name','age','kind') #这个类的对象这能有这些属性,同时也限制了采用__dict__属性可以用来查看实例属性
def __init__(self,name,kind,age):
self.name = name
self.kind = kind
self.age = age
def bark(tmp):
print('我是小可爱--丁丁')
dingding = Dog('丁丁','泰迪',3)
print('我是可爱的%s犬,%s,我今年%d岁了' % (dingding.kind, dingding.name, dingding.age))
dingding.gender = '公' #动态添加的属性
#dingding.weight = 8 #不能添加这个属性,语法错误
#查看实例属性
print(dingding.__dict__) #__dict__属性可以用来查看实例属性
print(dir(dingding)) #查看Dog的属性,包括实例属性
说明:
python中类如何来保护成员属性和成员方法呢?
采用self.__xxx方式来定义类的成员。(两个下划线, 见 保护对象的属性)
2.6 构造和析构
(1) 构造方法
-
目的:构造方法用于初始化对象,可以在构造方法中添加成员属性
-
时机:实例化对象的时候自动调用
-
参数:第一个参数必须是self,其它参数根据需要自己定义
-
返回值:不返回值,或者说返回None,不应该返回任何其他值
语法: def __init__(self,arg1,arg2....): 函数体 #参数:arg1,agr2...根据需要自己定义 #如果自己不定义构造方法,系统自动生成一个构造函数 def __init__(self): pass
注意:
-
如果没有定义构造方法,系统会生成一个无参构造方法,如果自己定义了构造方法,则系统不会自动生成
class 类名: def __init__(self): pass
-
一个类只能有一个构造方法,如果定义多个,后面的会覆盖前面的
-
构造函数由系统在实例化对象时自动调用,不要自己调用
class Dog(object): def __init__(self,name,kind,age): self.name = name #定义对象属性,这个类所有的对象都具有该属性 self.kind = kind #成员属性必须通过self.引用,否则是普通变量 self.age = age def bark(tmp): print('我是小可爱--丁丁') dingding = Dog('丁丁','泰迪',3) print('我是可爱的%s犬,%s,我今年%d岁了' % (dingding.kind, dingding.name, dingding.age))
(2) 析构方法
-
目的:对象销毁时,释放资源
-
时机:对象销毁时由系统自动调用
-
参数:除了self外,没有其他参数
-
返回值:不返回值,或者说返回None。
-
语法:
def __del__(self): #to do
案例
class Dog(object): #构造 def __init__(self,name,kind,age): self.name = name self.kind = kind self.age = age #析构 def __del__(self): print('拜拜了,二十年后又是一条好汉') def bark(tmp): print('我是小可爱--丁丁') dingding = Dog('丁丁','泰迪',3) print('我是可爱的%s犬,%s,我今年%d岁了' % (dingding.kind, dingding.name, dingding.age)) del dingding #销毁对象,自动调用析构方法
当删除一个对象时,python解释器也会默认调用一个方法,这个方法为__del__()
方法
3. 保护对象的属性
如果有一个对象,当需要对其进行修改属性时,有2种方法
- 对象名.属性名 = 数据 ---->直接修改
- 对象名.方法名() ---->间接修改
为了更好的保存属性安全,即不能随意修改,一般的处理方式为
- 将属性定义为私有属性
- 添加一个可以调用的方法,供调用
class People(object):
def __init__(self, name):
self.__name = name
def getName(self):
return self.__name
def setName(self, newName):
if len(newName) >= 5:
self.__name = newName
else:
print("error:名字长度需要大于或者等于5")
xiaoming = People("dongGe")
print(xiaoming.__name)
此时就会出现错误。
class People(object):
name = 'Tom' #公有的类属性
__age = 12 #私有的类属性
p = People()
print(p.name) #正确
print(People.name) #正确
print(p.__age) #错误,不能在类外通过实例对象访问私有的类属性
print(People.__age) #错误,不能在类外通过类对象访问私有的类属性
4. @property装饰器
Python中有一个被称为属性函数(property)的小概念,它可以做一些有用的事情。
- 将类方法转换为只读属性
- 重新实现一个类的属性的setter和getter方法
Python内置的@property装饰器就是负责把一个方法变成属性调用的,特别在对属性数据进行校验的时候。
# -*- coding: utf-8 -*-
__author__ = 'sun'
__date__ = '2019/5/28 16:23'
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@score.deleter
def score(self):
if not hasattr(self, '_score'):
raise AttributeError('Student object has no attribute "_score"')
del self._score
s = Student()
s.score =99
print(s.score)
del s.score
@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样程序运行时就减少了出错的可能性。
5. 魔法方法
(1)id() 方法
获取对象在内存中的地址
创建对象,并打印对象地址
dingding2 = Dog('丁丁', '泰迪', 3)
print(id(dingding2))
(2) __str__()
方法
自定义打印对象内容,以字符串形式打印出来
案例:
class Car:
def __init__(self, newWheelNum, newColor):
self.wheelNum = newWheelNum
self.color = newColor
def __str__(self):
msg = "嘿。。。我的颜色是" + self.color + "我有" + int(self.wheelNum) + "个轮胎..."
return msg
def move(self):
print('车在跑,目标:夏威夷')
BMW = Car(4, "白色")
print(BMW)
6. 静态方法和类方法
6.1 类方法
是类对象所拥有的方法,需要用修饰器@classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls
作为第一个参数(当然可以用其他名称的变量作为其第一个参数,但是大部分人都习惯以'cls'作为第一个参数的名字,就最好用'cls'了),能够通过实例对象和类对象去访问。
class People(object):
country = 'china'
#类方法,用classmethod来进行修饰
@classmethod
def getCountry(cls):
return cls.country
p = People()
print p.getCountry() #可以用过实例对象引用
print People.getCountry() #可以通过类对象引用
6.1 静态方法
需要通过修饰器@staticmethod
来进行修饰,静态方法不需要多定义参数
class People(object):
country = 'china'
@staticmethod
#静态方法
def getCountry():
return People.country
print People.getCountry()
综上:
从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法;而实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用
7. 类的继承
在现实生活中,继承一般指的是子女继承父辈的财产
在程序中,继承描述的是事物之间的所属关系,例如猫和狗都属于动物,程序中便可以描述为猫和狗继承自动物
# 定义一个父类,如下:
class Cat(object):
def __init__(self, name, color="白色"):
self.name = name
self.color = color
def run(self):
print("%s--在跑"%self.name)
# 定义一个子类,继承Cat类如下:
class Bosi(Cat):
def setNewName(self, newName):
self.name = newName
def eat(self):
print("%s--在吃"%self.name)
bs = Bosi("印度猫")
print('bs的名字为:%s'%bs.name)
print('bs的颜色为:%s'%bs.color)
bs.eat()
bs.setNewName('波斯')
bs.run()
说明:
- 私有的属性,不能通过对象直接访问,但是可以通过方法访问
- 私有的方法,不能通过对象直接访问
- 私有的属性、方法,不会被子类继承,也不能被访问
- 一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用
多重继承
class base(object):
def test(self):
print('----base test----')
class A(base):
def test(self):
print('----A test----')
# 定义一个父类
class B(base):
def test(self):
print('----B test----')
# 定义一个子类,继承自A、B
class C(A,B):
pass
obj_C = C()
obj_C.test()
print(C.__mro__) #可以查看C类的对象搜索方法时的先后顺序
重写父类方法
所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法
class Cat(object):
def sayHello(self):
print("halou-----1")
class Bosi(Cat):
def sayHello(self):
print("halou-----2")
bosi = Bosi()
bosi.sayHello()
调用父类的方法
class Cat(object):
def __init__(self,name):
self.__id = id #私有的,不被继承到子类
self.name = name
self.color = 'yellow'
class Bosi(Cat):
def __init__(self,name):
# 调用父类的__init__方法1(python2)
#Cat.__init__(self,name)
# 调用父类的__init__方法2
#super(Bosi,self).__init__(name)
# 调用父类的__init__方法3
super().__init__(name)
def getName(self):
return self.name
bosi = Bosi('xiaohua')
print(bosi.name)
print(bosi.color)
8 多态
多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
所谓多态:定义时的类型和运行时的类型不一样,此时就成为多态
Python “鸭子类型”
class F1(object):
def show(self):
print 'F1.show'
class S1(F1):
def show(self):
print 'S1.show'
class S2(F1):
def show(self):
print 'S2.show'
def Func(obj):
print obj.show()
s1_obj = S1()
Func(s1_obj)
s2_obj = S2()
Func(s2_obj)
说明
python3的面向对象编程其实还有一些高级应用,大部分工作中不会用到
敬请期待后续课程