文章目录
7.1对象魔法
使用对象的好处:
多态:可对不同类型的对象指向相同的操作,而这些操作一样能够正常运行
封装:对外部隐藏有关对象工作原理的细节
继承:可基于通用类创建出专用类
7.1.1多态
这大致意味着即便你不知道变量指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异。
7.1.2多态和方法
方法:与对象属性相关联的函数称为方法
以下是字符串、列表的方法count()
>>> 'abc'.count('a')
1
>>> [1, 2, 'a'].count('a')
1
如果有一个变量x,你无需知道它是字符串还是列表就能调用方法count:只要你向这个方法提供一个字符作为参数,它就能正常运行。
每当无需知道对象是什么样的就能对其执行操作时,都是多态在起作用。
通过内置运算符和函数大量使用了多态。
>>> 1 + 2
3
>>> 'Fish' + 'license'
'Fishlicense'
事实上,要破坏多态,唯一的办法是使用诸如type、issubclass等函数显式地执行类型检查,
7.1.3封装
封装(encapsulation)指的是向外部隐藏不必要的细节。
多态让你无需知道对象所属的类(对象的类型)就能调用其方法,而封装让你无需知道对象的构造就能使用它。
>>> o = OpenObject() # 对象就是这样创建的
>>> o.set_name('Sir Lancelot')
>>> o.get_name()
'Sir Lancelot'
要将名称‘封装’在对象中,将其作为一个属性即可
7.1.4继承
如果你已经有了一个类,并要创建一个与之很像的类(可能只是新增了几个方法),就将这个新类继承之前的类,对新类对象调用老类的方法即可
7.2类
7.2.1类到底是什么
每个对象都属于特定的类,并成为该类的实例
一个类(云雀)的对象为另一个类(鸟)的子集时,前者就是后者的子类,后者就是前者的超类
子类的所有实例都有超类的方法,需要定义子类时,只需要新增父类没有的方法,或者重写一些既有的方法
7.2.2创建自定义类
class Person:#class语句创建独立的命名空间,用于在其中定义函数
class Student:
job=’teacher’
def info(self,name,Id):
self.name=name
self.Id=Id
self.sex='male'
def print(self):
print(self.name,self.Id,self.sex)
>>>s=Student()
>>>s.info('jack',1)
>>>s.print()
jack 1 male
对s调用info()和print()时,s都会作为第一个参数自动传递给它们。因此在传参时,实际都自动传了第一个参数,这个看不见的参数就是self,self作为默认的第一参数不能省略。
这里self就是指类本身,self.name就是Student类的属性变量,是Student类所有。而name是外部传来的参数,不是Student类所自带的。故,self.name = name的意思就是把外部传来的参数name的值赋值给Student类自己的属性变量self.name。
self.sex是对象的属性,只属于创建后的实例
job是整个类的属性,每个实例也具有该属性
name已经成为该类的一个属性,封装在每个对象上,因此可以从外部直接调用和访问
>>>s.name
‘jack’
7.2.3 属性、函数、方法
#这是一个普通的函数
def funct():
print('I dont not...')
#这是一个方法
class Class:
def meth(ff):
print('I have a self')
>>>type(funct)
function
>>>type(a.meth)
method
函数可以直接运行
>>>funct()
I dont not...
方法需要建立对象来调用
>>>a=Class()
>>>a.meth()
I have a self
将属性关联到一个普通函数,调用这个方法实际执行的是函数
>>>a.meth=funct
>>>a.meth()
I dont not...
方法也可以赋值给变量, 将另一个变量指向同一个方法,这个变量也被关联到类的实例
class Bird:
color='blue'
def show(self):
print(self.color)
>>>a=Bird()
>>>a.show()
blue
>>>birdcolor=a.show #方法赋值给变量
>>>birdcolor()
blue
7.2.4再谈隐藏
一个普通的类普通的方法可以外部访问(实例.方法名/属性名)
class Student:
def info(self,name):
self.name=name
>>>s=Student()
>>>s.info('jack')
>>>s.name#外部方式访问
'jack'
当然也可以通过外部方式修改对象,因为现在name属性就是公共的
>>>s.name=‘hellen’
>>>s.name
‘hellen’
为避免这种情况,可将属性定义为私有,让其名称以两个下划线打头即可
class Student:
def info(self,name):
self.__name=name
>>>s=Student()
>>>s.info('jack')
>>>s.__name#如果通过内部函数赋值,无法再从外部访问,只能在内部调用
报错AttributeError: 'Student' object has no attribute '__name
>>>hasattr(s,'__name')
False
如果仍然用外部方法修改和访问
>>>s.__name='jj'#相当于给s另外添加了一个外部属性__name,实际上__name改成其他名称都可以,只是新增了另外一个属性
>>>s.__name
'jj'
>>>hasattr(s,'__name')
True
这时候原来的name属性已经是私有的了,无法从外部访问
需要在类内部构造一个访问的方法
class Student:
def info(self,name):
self.__name=name
def print(self):#访问的方法
print(self.__name)
>>>s.print()#通过print方法访问
jack
这样外部也无法修改对象内部状态
同理,如果将方法也加两个下划线,正常调用方法也会出错
class Student:
def __info(self,name):
self.__name=name
>>>s=Student()
>>>s.info('jack')
报错
仍然可以在类内部构造一个访问私有方法的函数
class Student:
def __info(self,name):
self.__name=name
return self.__name
def print(self):
print(self.__info('alice'))
>>>s=Student()
>>>s.print()
alice
如果一定要从外部访问私有方法,可以对象名._类名__私有方法名
>>>s._Student__info('allen')
‘allen’
7.2.5类的命名空间
def foo(x):return x*x等价于foo=lambda x:x*x。它们都创建一个返回参数平方的函数,并将这个函数关联到变量foo。
在class语句中定义的代码都是一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命名空间,类定义其实就是要指向的代码段。
class C:
print('nothing')
nothing
class C:
n=0#在类里面定义的属性是整个类的属性
def init(self):
C.n+=1
>>>a=C()
>>>a.init() #初始化所有实例
>>>C.n
1
>>>b=C()
>>>b.init()
>>>C.n
2
上述代码在类作用域内定义了一个变量,所有的成员(实例)都可以访问它
>>>a.n#类的属性值也是类下面实例的属性值
2
>>>b.n
2
>>>a.n=5#只改变实例的属性值
>>>a.n
5
>>>b.n
2
>>>C.n
2
新值被写入a的一个属性中,这个属性遮住了类级变量
a=1#全局空间变量
#scope1=vars()
#print(scope1)
class Test:
a=2#类属性
#scope2=vars()
#print(scope2)
def sing(self,song):
a=3#方法空间变量,如果方法中a=3不存在
#scope3=vars()
#print(scope3)
self.a=4#方法空间属性,如果方法中没有定义self.a=4
self.name='cindy'
self.song=song
sc=vars()
print(self.a,self.name,'is singing',self.song,)
print(a)
>>>t=Test()
>>>t.sing('<happy new year>')
4 cindy is singing <happy new year> # 2 cindy is singing <happy new year>
3 #1
这里
scope1打印出来,是个全局作用域,’a’:1在这个全局作用域中
scope2打印出来的是类的作用域
{’__module__’: ‘__main__’, ‘__qualname__’: ‘Test’, ‘a’: 2, ‘scope2’: {…}}
scope3打印出来是方法的作用域,其中self.a和self.name都不在该作用域
{‘a’: 3, ‘song’: ‘’, ‘self’: <__main__.Test object at 0x0000000004FD5DA0>}
如果方法中self.a=4不存在,则打印出来的self.a=2,用的是类空间的属性值
2 cindy is singing
如果方法中a=3不存在,则打印出来的a=1
因此引用顺序都是由内向外的:
self.a 属性的引用优先顺序:方法空间self.a ——>类空间a
a变量的引用优先顺序:方法空间a ——>全局空间a
7.2.6指定超类
要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起。
class Filter: #定义超类
def init(self):
self.bloked=[]#bloked可以改成其他名称,只是用来指定一个属性值
def filter(self,sequence):
return [x for x in sequence if x not in self.bloked]
class SpamFilter(Filter):#子类SpamFilter继承超类Filter
def init(self):#重写超类的方法
self.bloked=['spam']
>>>f=Filter()
>>>f.init()
>>>f.filter([1,2,3])
[1, 2, 3]
>>>s=SpamFilter()
>>>s.init()
>>>s.filter(['spam','span',1,2,3])
['span', 1, 2, 3]
定义子类的两大要点:
继承
重写
7.2.7深入探讨继承
要确定一个类是否是另一个类的子类,可用内置函数issubclass(子类,超类)
>>>issubclass(SpamFilter,Filter)
True
获取一个类的基类,可以访问其特殊属性:类.__bases__
>>>SpamFilter.__base__
__main__.Filter
>>>SpamFilter.__bases__
(__main__.Filter,)
要确定一个对象是否是该类的实例,可用内置函数isinstance(实例,类)
>>>isinstance(s,SpamFilter)#s是SpamFilter的直接实例
True
>>>isinstance(s,Filter) #s是Filter的间接实例
True
>>>a='dfdfsd'#isinstance也可用于类型
>>>isinstance(a,str)
True
获取一个对象的类,使用属性:实例.__class__,也可以用type(实例)查看
>>>s.__class__
__main__.SpamFilter
>>>type(s)
__main__.SpamFilter
7.2.8多个超类
多重继承:一个子类继承自多个超类
class Caculator:
def caculate(self,expression):
self.value=eval(expression)
class Talker:
def talk(self):
print('I am {}'.format(self.value))
class TalkerCaculator(Caculator,Talker):
pass
>>>tc=TalkerCaculator()
>>>tc.caculate('5+9*2')#直接调用超类方法
>>>tc.talk()
I am 23
如果多个超类有同名方法,在定义子类的继承顺序上,靠前的超类方法会覆盖靠后的超类方法,查找特定方法或属性时访问超类的顺序称为方法解析顺序(MRO),
class Caculator:
def caculate(self,expression):
self.value=eval(expression)
def talk(self):
print('I come from caculator')
class Talker:
def talk(self):
print('I am {}'.format(self.value))
class TalkerCaculator(Caculator,Talker):
pass
>>>tc=TalkerCaculator()
>>>tc.caculate('5+9*2')
>>>tc.talk()
I come from calculator
7.2.9接口和内省
接口(协议):对外暴露的方法和属性
hasattr(实例.方法或属性) 函数用于判断对象是否包含对应的属性和方法。
class Test:
a=1#类的属性
def sing(self,song):
self.name='cindy'
self.song=song
print(self.a,self.name,'is singing',self.song)
>>>t=Test()
>>>t.sing('《好运来》')
1 cindy is singing 《好运来》
>>>hasattr(t,'a')
>>>hasattr(t,'song')
>>>hasattr(t,'name')
>>>hasattr(t,'sing')
True
指定的、未指定的、外部传入的属性和方法都可以用该函数判断
callable()检查方法是否可被调用,getattr()返回属性值,属性不存在时可指定默认值,setattr()设置对象的属性
>>>getattr(t,'sing')
<bound method Test.sing of <__main__.Test object at 0x0000000004BCD400>>
>>>callable(getattr(t,'sing',None))
True
>>>getattr(t,'name')
cindy
>>>setattr(t,'name','alice')
>>>t.name
‘alice’
要查看对象中存储的所有值,可检查属性:实例.__dict__
>>>t.__dict__
{'name': 'cindy', 'song': '《好运来》'}#类a属性不在其中
7.2.10抽象基类
有些第三方模块提供了显式指定接口的理念的各种实现,Python通过引入模块abc提供了官方解决方案。
一般而言,抽象类是不能(至少是不应该)实例化的类,其职责是定义子类应实现的一组抽象方法。
from abc import ABC,abstractmethod
class Talker(ABC):
@abstractmethod#装饰器用来将方法标记为抽象的,在子类中必须实现方法
def talk(self):
pass
这里定义的Talker类就是抽象类(即包含抽象方法的),最重要的特征是不能被实例化
>>>tt=Talker()
报错
即使是从它派生子类,只要没有把抽象的方法重写,它仍然无法被实例化
class Ta(Talker):
pass
>>>tr=Ta()
报错
但是如果重写这个抽象方法
class Ta(Talker):
def talk(self):
print('hi')
>>>tr=Ta()
则不会报错
>>>tr.talk()
hi
>>>isinstance(tr,Talker)#tr也是Talker抽象类的实例
True
Notice:如果抽象类Talker()里面有多个方法,需要在子类中重写被@abstractmethod
标记的全部方法,如果只是普通的方法,没有被标记的,就可以不重写。
另外再写一个不是从Talker派生出来的类
class Duck:
def talk(self):
print('duck')
>>>d=Duck()
>>>isinstance(d,Talker)
False
显然它不是Talker类
但是,可以将它注册到Talker类:基类.register(被注册的类)
>>>Talker.register(Duck)
__main__.Duck
>>>isinstance(d,Talker)
True
这样Duck类的实例也是Talker类的实例,同时Duck类也成为了Talker的子类
>>>issubclass(Duck,Talker)
True
如果Duck没有重写talk方法,Duck类同样不能实例化
参考文献:https://blog.csdn.net/CLHugh/article/details/75000104
PS:内置属性__init__,建立对象时可以直接传参
class Student(object):
def __init__(self, name, score):#这里的__init__是内置方法,把属性绑定到对象
self.name = name
self.score = score
>>>student = Student("Hugh", 99)#直接传参
>>>student.name
"Hugh"
>>>student.score
99