[Python]第七章再谈抽象

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

猜你喜欢

转载自blog.csdn.net/weixin_40844116/article/details/84312127