面向对象的理解
考虑到部分读者可能没有接触过面向对象编程,所以先介绍下面向对象的一些特征,形成一个面向对象概念的基本认知,有助于后面具体的学习Python的面向对象编程。
// 对象引入 //
按照普通人的认知,对象就是我们日常生活中谈论到的男女对象。见过这样的问题:“我没有对象是不是就没办法学习面向对象编程了?”。
答案肯定 不是,编程界有这样一句名言, “万物皆对象” ,意思:把任何事物都看做一个对象。所有的事物都具有两个特点: 有什么 和 能做什么 ,举个例子:老鹰有翅膀,能够飞翔。在面向对象看来,老鹰就是一个对象,翅膀是属性,用来描述对象,而能飞翔是方法,这就是对象的功能。
// 类的引入 //
自然界除了老鹰外,还有很多有翅膀的动物,比如燕子,天鹅等。它们都具有上面两个相似的特点,我们可以把这种 具有相同或相似性质的对象 进行 抽象(抽取共性) 从而形成一个类,比如这里的鸟类,可以写出这样的伪代码:鸟类{
属性:翅膀 = (不同的颜色,不同的形状)
方法:飞翔(能否)
}
然后有个名词叫
类的实例
,类的一个个具体实现,其实就是
对象
,比如这里实例化不同的鸟的伪代码:
老鹰 = 鸟类(翅膀 = 长而宽阔,飞翔(能))
燕子 = 鸟类(翅膀 = 俊俏轻快,飞翔(能))
鸭子 = 鸟类(翅膀 = 较短,飞翔(否))
// 类设计的要求和思想 //
要求:高内聚,低耦合,从而调高对象的可复用性;
思想:以对象为中心,先发开类,得到对象,再通过对象间的相互通信实现功能。
类与对象
对类与对象的概念有个大概的认知后,我们来开始学习下Python中类与对象的具体语法。// 定义类与实例 //
Python中使用 class关键字 来定义类,我们来定义一个鸟类:class Bird:"""
鸟类
"""
kind = '动物'
name = "鸟"def __init__(self, name, wings):
self.name = name
self.wings = wingsdef can_fly(self, can):
print("%s有 %s 的翅膀,%s飞翔" % (self.name, self.wings, '能' if can else '不能'))if __name__ == '__main__':
eagle = Bird("老鹰", "长而宽阔")
duck = Bird("鸭子", "较短")
eagle.can_fly(True)
duck.can_fly(False)# 运行结果如下:
老鹰有 长而宽阔 的翅膀,能飞翔
鸭子有 较短 的翅膀,不能飞翔
// __init__() 初始化函数 //
在实例化对象的时候会自动调用,给该对象属性的值初始化,要注意:不推荐使用类名.__init__()这种方式去调用初始化函数,以避免引起不必要的问题。
// __new__()构造函数 //
这个函数涉及到Python经典类与新式类的概念了,在Python 2.x中,默认是经典类,除非显式的继承object类才是新式类。而在Python 3.x中默认所有类都是新式类,不用显式继承object类。
新式类相比经典类增加了很多的内置属性,比如可以通过__class__获得自身类型,还有这里的__new__() 函数。而这个函数的调用时机在__init__函数之前,作用是:可以调用其他类的构造方法或者直接返回别的对象来作为本类的实例。
__new__(cls, *args,**kw),第一个参数表示要实例化的类,该参数在实例化的时候由Python解释器自动提供。另外要注意,__new__() 函数,必须要有返回值,如果该函数没有成功返回cls类型的对象,是不会调用 __init__() 来对对象进行初始化的!!!
初学者可能会弄混,特意列出来帮助区分:__init__():用于初始化实例,控制初始化过程,可添加一些属性,做额外操作,发生在类实例被创建完后,它是对象级别的函数。
__new__():用于控制生成新实例的过程,它是类级别的函数。
// 类属性与实例属性 //
类属性是类的属性 ,当定义了一个类属性后,这个变量虽然归类所有,但是类和实例都可以访问到, 类属性是类和实例的共有数据 。当 类属性和实例属性 同名 时,访问顺序是: 实例属性 -> 类属性 ,如果两个都不存在则会报错。比如下面这样的代码:print("类访问类变量:%s" % Bird.kind)
print("类访问变类量:%s" % Bird.name)
print("实例访问类变量:%s" % eagle.kind)
print("实例访问同名变量:%s" % duck.name)# 运行结果如下:
类访问类变量:动物
类访问变类量:鸟
实例访问类变量:动物
实例访问同名变量:鸭子
实例属性
则是
与实例绑定的属性
,可通过
实例名.属性名
的方式调用,代表了
实例的数据部分
。
// 类函数、成员函数与静态函数 //
类函数 :用于 访问类属性 ,使用 @classmethod装饰器 来修饰,第一个参数是cls,类本身,用于调用类属性,但是不能访问实例属性。 类函数可以通过类直接调用,或通过实例直接调用。但无论哪种调用方式,最左侧传入的参数一定是类本身!!!代码示例如下:class A: @classmethoddef fun_a(cls):
print(type(cls), cls)if __name__ == '__main__':
A.fun_a()
a = A()
a.fun_a()# 运行结果如下:
<class 'type'> <class '__main__.A'>
<class 'type'> <class '__main__.A'>
成员函数和类实例绑定
:类实例化后才能调用,它的第一个参数表示实例本身,一般用self表示,成员函数可以直接操作对象内部的数据。如果使用类直接调用成员函数,需要显式地将实例作为参数传入。代码示例如下:
class B:def fun_b(self):
print("Call fun_b()")if __name__ == '__main__':
b = B()
b.fun_b()
B.fun_b(b) # 类调用成员函数需将实例传入# 运行结果如下:
Call fun_b()
Call fun_b()
静态函数
,在定义上面的fun_b函数时,智能提示里就有一个Make method static的选项,对于这种
不需要self参数的函数(无需实例参与)
,都可定义成静态函数,调用过程中无需将类实例化。使用
@staticmethod装饰器
来声明,通过
类名.函数名
或
实例.函数名
进行调用,代码示例如下:
class C: @staticmethoddef fun_c():
print("Call fun_c()")if __name__ == '__main__':
C.fun_c()
c = C()
c.fun_c()# 运行结果如下:
Call fun_c()
Call fun_c()
// 访问控制 //
所谓的访问控制,就是类的属性和函数是公有还是私有,如果属性和函数只能在类内部访问,而不能被实例访问的话,我们就称这个属性或函数为私有的。
Python 和其他编程语言不同,没有类似于public和private这样的访问权限修饰符,而是采用一种名字改编技术。默认公有,而私有的属性名和函数名会加上两下划线,比如下面的 __skill,当然这只是 伪私有,改成了_类名私有属性/函数名 ,比如下面调用people._Person__skill,是可以访问到私有成员的:
class People:
sex = 1 # 类属性
__skill = "敲代码" # 私有类属性,只能类内部访问,外部无法访问def speak(self):
print("我是一个人,技能是:%s" % self.__skill, end='\t')
people = People()
people.speak()
people.sex = -1
print("性别:" + ("男" if people.sex == 1 else "女"))
print("访问私有属性:%s" % people._People__skill)# 运行结果如下:
我是一个人,技能是:敲代码 性别:女
访问私有属性:敲代码
虽然可以这样访问到私有成员,但是不建议这样做!另外说下几点要注意的:
单下划线开头的变量名或函数名 ,同样是私有成员,不过类和实例都能访问,也会被子类继承。如果你不想属性或函数被子类继承就还是用双下划线吧!
开头结尾都是双下划线的属性或函数是类的特殊成员,有特殊用途,比如上面的 __init__ 初始化函数。
定义的变量和某个保留关键字冲突,可以使用单下划线作为后缀,比如:in_ = 1。
// 动态绑定 //
Python中可以 动态地为类或对象绑定属性或函数 , 类动态绑定属性与函数 ,对该类的所有实例有效。代码示例如下:class A:def __init__(self, id_):
self.id_ = id_# 定义一个用于动态绑定的函数def set_name(self, name):
print("调用了动态绑定的函数")
self.name = nameif __name__ == '__main__':# 动态绑定一个属性
A.kind = "人类"# 动态绑定一个函数
A.set_name = set_name
a = A(1)# 类访问动态绑定的属性
print(A.kind)# 实例访问动态绑定的属性
print(a.kind)# 类访问动态绑定的函数
A.set_name(a,'123')# 实例访问动态绑定的函数
a.set_name('321')# 运行结果如下:
人类
人类
调用了动态绑定的函数
调用了动态绑定的函数
实例动态绑定属性与函数,只对当前对象有效
,对其他实例无效,需要用到一个
MethodType类
,代码示例如下:
from types import MethodTypeclass B:def __init__(self, id_):
self.id_ = id_# 定义一个用于动态绑定的函数def set_name(self, name):
print("调用了动态绑定的函数")
self.name = nameif __name__ == '__main__':
b_1 = B('1')# 动态为实例1绑定一个属性
b_1.kind = "人类"# 动态为实例1绑定一个函数
b_1.set_name = MethodType(set_name, b_1)# 实例1设置动态绑定的属性与函数
print(b_1.kind)
b_1.set_name('123')# 另一个类实例调用动态绑定的属性
b_2 = B('2')
print(b_2.kind)# 运行结果如下:
人类
Traceback (most recent call last):
调用了动态绑定的函数
File "/Users/jay/Project/Python/Book/Chapter 9/9_7.py", line 30, in
print(b_2.kind)
AttributeError: 'B' object has no attribute 'kind'
继承
面向对象的最大优点是代码重用,而实现代码重用的重要的方法就是通过Python的继承机制。这个继承理解为我们日常说的遗产继承,儿子继承父亲的遗产。
类比成编程里对应子类和父类,子类继承父类所有的属性与函数,可以进行重写或者扩展以实现更多的功能。
Python中关于继承的规则如下:继承写法:class 子类(父类);
子类可以继承父类的所有属性与函数;
子类定义与父类同名的属性与方法会自动覆盖;
重写时如果想调用父类的同名函数可以使用super().函数名调用;
父类的私有属性、函数不能继承,即__(双下划线)开头的属性名和函数名;
子类可以调用super().init() 的方式初始化父类。
// 单继承 //
所谓的单继承就是 只继承一个父类 ,代码示例如下:class Bird:def __init__(self, name):
self.name = namedef can_fly(self, can):
self.can = candef __str__(self):return self.name + ("能够" if self.can == True else "不能够") + "飞翔。"class Duck(Bird):# 子类扩展父类中的方法def set_color(self, color):
self.color = color# 重写父类中里的方法def __str__(self):return self.color + "的" + self.name + ("能够" if self.can == True else "不能够") + "飞翔," + "会游泳。"if __name__ == '__main__':
duck = Duck("小鸭子")
duck.can_fly(False)
duck.set_color("黄色")
print(duck)# 运行结果如下:
黄色的小鸭子不能够飞翔,会游泳。
另外,要注意:
父类是无法调用子类函数的
,比如下述代码:
bird = Bird('鸟')
bird.can_swin()
# 运行后直接报错:
Traceback (most recent call last):
File "/Users/jay/Project/Python/Book/Chapter 9/9_8.py", line 33, in
bird.can_swin()
AttributeError: 'Bird' object has no attribute 'can_swin'
// 多继承 //
多继承就是同时继承多个父类的属性与函数,多个父类间用逗号隔开,要注意如果父类们中有相同的函数,调用的顺序是:谁在前面先调用,比如有这样一个方法:
class Person(Name, Sex,Age),三个父类里都有一个show的函数,那么子类调用的是Name里的show()!
你可以通过内置属性__mro__查看对象搜索方法时的先后顺序。另外,如果不是非得用多继承不可的话,应该尽量避免使用它,有时会出现一些不可遇见的BUG。代码示例如下:
class A:def show_A(self):
print('父类A')class B:def show_B(self):
print('父类B')# 定义一个子类,继承A和B类class C(A, B):def show_C(self):
print('子类C')if __name__ == '__main__':
c = C()
c.show_A()
c.show_B()
c.show_C()# 运行结果如下:
父类A
父类B
子类C
组合
多继承的一个替代方案就是通过组合的方式, 把 需要用到的类丢到组合类中实例化 ,代码示例如下:class Book:def __init__(self, num):
self.num = numclass Phone:def __init__(self, num):
self.num = numclass Wallet:def __init__(self, num):
self.num = numclass Bag:def __init__(self, x, y, z):
self.book = Book(x)
self.phone = Phone(y)
self.wallet = Wallet(z)def show_bag(self):
print("您的背包里有:【书本】* %d 【手机】* %d 【钱包】* %d" %
(self.book.num, self.phone.num, self.wallet.num))if __name__ == '__main__':
bag = Bag(3, 2, 1)
bag.show_bag()# 运行结果如下:
您的背包里有:【书本】* 3 【手机】* 2 【钱包】* 1
对象相关的内置函数
Python中还为我们提供了一些与对象相关的内置函数,如下表所示:函数 | 作用 |
issubclass(class, classinfo) | 如果第一个参数是第二个参数的子类,返回True,否则返回False |
isinstance(object, classinfo) | 如果第一个参数是第二个参数的实例对象,返回True,否则返回Fals |
hasattr(object, name) | 测试一个对象中是否有指定的属性,属性名要用引号括着 |
getattr(object, name, [,default]) | 返回对象的指定属性值,不存在返回default值,没设会报ArttributeError异常 |
setattr(object, name, value) | 设置对象中指定属性的值,属性不存在会新建并赋值 |
delattr(object, name) | 删除对象中的指定属性的值,不存在会报ArttributeError异常 |
property(fget,fset,fdel,doc) | 返回一个可以设置属性的属性 |