第七章 再谈抽象
自定义`类和对象
7.1 对象魔法
多态:可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够正常运行。
封装:对外部隐藏有关对象工作原理的细节。
继承:可基于通用类创建出专用类。
7.1.1 多态
大致意味着即便你不知道变量指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异。
7.1.2 多态和方法
与对象属性相关联的函数称为方法。
标准库模块random包含一个名为choice的函数,它从序列中随机选择一个元素。下面使用这个函数给变量提供一个值。
'''
from random import choice
x = choice(['Hello, world!', [1, 2, 'e', 'e', 4],"张三"])
print(x)
'''
执行这些代码后,x可能包含字符串'Hello, world!',也可能包含列表[1, 2, 'e', 'e', 4]。具体是哪一个,你不知道也不关心。你只关心x包含多少个'e',而不管x是字符串还是列表你都能找到答案。者就是方法的多态。
张三
------------------
(program exited with code: 0)
请按任意键继续. . .
7.1.3 封装
指的是向外部隐藏不必要的细节。这听起来有点像多态(无需知道对象的内部细节就可使用它)。这两个概念很像,因为它们都是抽象的原则。它们都像函数一样,可帮助你处理程序的组成部分,让你无需关心不必要的细节。
7.1.4 继承
继承是类的继承,以基类为基础而创建新类或者是专用类。
7.2 类
7.2.1 类是什么
类是对象模型,对象是建立在类的基础上的。张三是人类,黑子是犬类,人和犬是动物类。其中张三和黑子是对象,它是人类和犬类的具体化,而人类和犬类是继承了动物类特性的子类。
7.2.2 创建自定义类
和大部分编程语言一样创建类用关键字class。比如建一个person(人)类,人有姓名,人会说“你好”。
'''
class Person:
name=""
def set_name(self,name):
self.name=name
def get_name(self):
return self.name
def greet(self):
print("{}你好!".format(self.name))
'''
这个类中有一个属性三个方法,分别是设置姓名、获取姓名和输出你好!谁谁;但它只是个模板不是个具体的person(人),还不能直接运行,还必须让它具体到“具体的人”,才可以在程序中起作用,那就是用这个模板创造个对象。
'''
foo=Person()
bar=Person() #创建了foo和bar两个对象。
foo.set_name("张三")
bar.set_name("李四") #为这两个对象的name属性赋值(起名字)
foo.greet()
bar.greet() #两个对象个输出一段话
'''
张三你好!
李四你好!
------------------
(program exited with code: 0)
请按任意键继续. . .
self是什么。对foo调用set_name和greet时,foo都会作为第一个参数自动传递给它们。我将这个参数命名为self,这非常贴切。实际上,可以随便给这个参数命名,但鉴于它总是指向对象本身,因此习惯上将其命名为self。显然,self很有用,甚至必不可少。如果没有它,所有的方法都无法访问对象本身——要操作的属性所属的对象。
'''
'''
class Person:
名=""
def set_名(这个,名):
这个.名=名
def get_名(这个):
return 这个.名
def greet(这个):
print("{}你好!".format(这个.名))
foo=Person()
bar=Person() #创建了foo和bar两个对象。
foo.set_名("张三")
bar.set_名("李四") #为这两个对象的name属性赋值(起名字)
foo.greet()
bar.greet()
'''
'''
张三你好!
李四你好!
------------------
(program exited with code: 0)
请按任意键继续. . .
7.2.3 属性、函数和方法
实际上,方法和函数的区别表现在参数self上。方法(更准确地说是关联的方法)将其第一个参数关联到它所属的实例,因此无需提供这个参数。无疑可以将属性关联到一个普通函数,但这样就没有特殊的self参数了。
类中的成员分为实例属性、实例方法、类属性、类方法、静态方法等
i、类属性:直接在类中定义的属性,可以通过类或实例对象来访问。
ii、实例方法:将self作为第一个参数的方法。
iii、类方法:使用@classmethod修饰的方法,将cls作为第一个参数。
iv、静态方法:使用@staticmethod修饰的方法,没有任何必选参数,不需要将cls作为第一个参数。
v、实例属性:在实例中赋予的属性。
例:
'''
class X: #定义类X
x="x" #类属性
def show_x(self): #实例方法
pass
@classmethod
def run(cls): #类方法
pass
@staticmethod
def hold(cls): #静态方法
bass
case_x=X() #实例化对象
case_x.name_for_x="Class_X" #实例属性
'''
7.2.4 再谈隐藏
隐藏又叫封装。Python没有为私有属性提供直接的支持,要让方法或属性成为私有的,只能在类的内部访问。(不能从外部访问),只需让其名称以两个下划线打头即可。
'''
'''
class Y:
__y_name #私有属性
pass
y=Y()
y.__y_name
'''
'''
__y_name #私有属性
NameError: name '_Y__y_name' is not defined
------------------
(program exited with code: 1)
请按任意键继续. . .
7.2.5 类的命名空间
在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命名空间。类定义其实就是要执行的代码段。
7.2.6 指定超类
子类扩展了超类的定义。要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起。
'''
class Student(Person):
school=""
score=""
def motion(self):
print("{}打篮球".format(self.name))
def get_school(self):
return self.school
def set_scholl(self,school):
self.school=school
def get_score(self):
return score
def set_score(self,score):
self.score=score
stu=Student()
stu.school="伦敦大学"
stu.set_name("王五")
stu.motion()
print(stu.school)
'''
王五打篮球
------------------
(program exited with code: 0)
请按任意键继续. . .
这里的Person就是student的超类,它继承了Person类的包括name在内的成员。但如果子类重定义父类(这里是pPerson)成员,它将执行子类的成员而非父类。
7.2.7 深入探讨继承
要确定一个类是否是另一个类的子类,可使用内置方法issubclass。
'''
print(issubclass(Student,Person))
'''
True
------------------
(program exited with code: 0)
请按任意键继续. . .
如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__。
'''
print(Student.__bases__)
'''
(,)
------------------
(program exited with code: 0)
请按任意键继续. . .
要确定对象是否是特定类的实例,可使用isinstance。
'''
print(isinstance(stu,Student))
print(isinstance(stu,Person))
'''
True
True
------------------
(program exited with code: 0)
请按任意键继续. . .
stu是Student类的(直接)实例,但它也是Person类的间接实例,因为Student是Person的子类。换而言之,所有Student对象都是Person对象。从前一个示例可知,isinstance也可用于类型,如字符串类型(str)。
如果你要获悉对象属于哪个类,可使用属性__class__。
'''
print(stu.__class__)
print(str.__class__)
print("a".__class__)
print(int.__class__)
print((15).__class__)
print((3.14).__class__)
'''
------------------
(program exited with code: 0)
请按任意键继续. . .
对于实例还可使用type(s)来获悉其所属的类。
7.2.8 多个超类
python是允许多重继承的。也就是一个类可以有多个父类。
'''
class A:
def show_a(self):
print("这是类A")
class B:
def show_b(self):
print("这是类B")
class C:
def show_c(self):
print("这是类C")
class D(A,B,C):
def show_d(self):
print("这是类D")
print(issubclass(D,A))
print(issubclass(D,B))
print(issubclass(D,C))
isd = D()
isd.show_a()
isd.show_b()
isd.show_c()
isd.show_d()
'''
True
True
True
这是类A
这是类B
这是类C
这是类D
------------------
(program exited with code: 0)
请按任意键继续. . .
多重继承是一个功能强大的工具。然而,除非万不得已,否则应避免使用多重继承,因为在有些情况下,它可能带来意外的“并发症”。使用多重继承时,有一点务必注意:如果多个超类以不同的方式实现了同一个方法(即有多个同名方法),必须在class语句中小心排列这些超类,因为位于前面的类的方法将覆盖位于后面的类的方法。
7.2.9 接口和内省
接口这一概念与多态相关。处理多态对象时,你只关心其接口(协议)——对外暴露的方法和属性。
(《python基础教程(第三版)》这一节有点文不对题——其实这一章写的也有点乱)
下面按照此处内容介绍几个有关的知识点吧
1、hasattr
hasattr() 函数用于判断对象是否包含对应的属性或方法。
格式是hasattr(object, name),其中object为对象。name为属性名或方法名字符串。
'''
class Coordinate:
x=10
y=-5
z=0
def sub(self,s):
pass
point1 = Coordinate()
print(hasattr(point1, 'x'))
print(hasattr(point1, 'y'))
print(hasattr(point1, 'z'))
print(hasattr(point1, 'no'))
print(hasattr(point1, 'sub'))
'''
True
True
True
False
True
------------------
(program exited with code: 0)
请按任意键继续. . .
2、callable
用于检查一个对象是否是可调用的。如果返回True,object仍然可能调用失败;但如果返回False,调用对象ojbect绝对不会成功。对于函数, 方法, lambda 函数式, 类, 以及实现了 __call__ 方法的类实例, 它都返回 True。
格式:callable(object)其中object为要检验的对象。
'''
class Z:
is_z=20
def __call__(self):
pass
z=Z()
def f():
pass
print(callable(isd))
print(callable(A))
print(callable(isd.show_a))
print(callable(5))
print(callable(z))
print(callable(f))
'''
False
True
True
False
True
True
------------------
(program exited with code: 0)
请按任意键继续. . .
3、getattr
用于返回一个对象属性值。
格式getattr(object, name[, default])其中:object是对象;name是对象属性名字符串;default是默认返回值,如果不提供该参数,在没有对应属性时,将触发 AttributeError。
'''
print(getattr(z,"is_z"))
print(getattr(z,"s"))
'''
20
Traceback (most recent call last):
File "xx.py", line 14, in
print(getattr(z,"s"))
AttributeError: 'Z' object has no attribute 's'
------------------
(program exited with code: 1)
请按任意键继续. . .
4、setattr
对应函数 getattr(),用于设置属性值,该属性必须存在。
格式为setattr(object, name, value)其中:object为对象。name是对象属性名符串。value为要设定的属性值。
'''
setattr(z,"is_z",40)
print(z.is_z)
'''
40
------------------
(program exited with code: 0)
请按任意键继续. . .
5、__dict__
此属性是一个字典类型,用于存储的对象等的所有值。其实有验证如下结论。
“1) 内置的数据类型没有__dict__属性。
2) 每个类有自己的__dict__属性,就算存着继承关系,父类的__dict__ 并不会影响子类的__dict__。
3) 对象也有自己的__dict__属性, 存储self.xxx 信息,父子类对象公用__dict__。 ”
(以上三点引至“//偏执”的博文《Python __dict__属性详解》地址:http://www.cnblogs.com/alvin2010/p/9102344.html)
6、type
函数type()如果你只有第一个参数则返回对象的类型,三个参数返回新的类型对象。
isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同最好使用 isinstance()。
格式为type(object)或type(name, bases, dict)其中:object为对象;name为类的名称;bases 为基类的元组;dict为一个字典,其中包含类内定义的命名空间变量。一个参数返回对象类型, 三个参数,返回新的类型对象。
7.2.10 抽象基类
抽象类需要借助模块实现,抽象类是一个特殊的类,它只能被继承,不能被实例化。它的内部一般规定了方法(包括抽象方法),必须建立一个实现了父类的所有抽象方法的非抽象的子类实现这些方法,才可使用。
抽象类与接口有点类似,但其实是不同的。抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计。严格的说Python中没有接口概念。这是因为Python可以实现多重继承,有抽象类足也。但如果非要弄个“接口”就要调用abc模块以实现“接口类”。
①接口:
'''
from abc import abstractmethod,ABCMeta #这里是为了实现接口类调用的模块
class P(metaclass=ABCMeta): #在这里声明metaclass=ABCMeta
@abstractmethod #然后这里一个装饰器abstractmethod,就声明这个类是接口类
def pay(self,m):
pass
'''
②抽象类:
'''
import abc #利用abc模块实现抽象类
class F(metaclass=abc.ABCMeta):
a_t='f'
@abc.abstractmethod #定义抽象方法,无需实现功能
def r(self):
pass