一. 类和对象相关知识
1. 概念
类:把一类事物的相同特征和动作整合到一起就是类
python的class术语与c++有一定区别,与 Modula-3更像。
python中一切皆为对象,且python3统一了类与类型的概念,类型就是类。
对象:对象就是基于类创建的一个具体的事物,就是将动作和特征整合到一起
2. 初识类
class Data: # 定义类名
"""
first class # 类的说明
"""
pass # 类体
新式类与经典类
在Python3中只有新式类,在Python2中区分经典类与新式类
概念
- 新式类
新式类必须继承至少一个父类,如果没有父类则使用object
作为父类继承 - 经典类
没有继承任何父类
#经典类
class name:
pass
#新式类
class name(superclass):
pass
在Python3中以上两种方式都是新式类
3. 类的作用
定义一个类
class People:
country = 'china'
def __init__(self, name):
self.name = name
def walk(self):
print('%s could walk' % self.name)
类有两个作用:属性引用和实例化
属性引用
print(People.country) # 引用类的数据属性
>>china
print(People.walk) # 引用类的函数属性
>><function People.walk at 0x000002049F6ADB70>
People.eat = 'rice' # 增加属性
del People.eat # 删除属性
实例化
实例化使用类名()
的格式
p1 = People('Jack')
p1.walk()
>>Jack could walk
self的作用是在实例化时自动将对象/实例本身传给init的第一个参数。
4. 类属性增删改查
我们定义的类的属性到底存到哪里了?有两种方式查看
dir(类名):查出的是一个名字列表
类名.__dict__:查出的是一个字典,key为属性名,value为属性值
除了我们自己定义的类的属性,类本身还有一些内建的特殊的属性
调用方式 | 用法 |
---|---|
类名.__name__ | 类的名字(字符串) |
类名.__doc__ | 类的文档字符串 |
类名.__base__ | 类的第一个父类 |
类名.__bases__ | 类所有父类构成的元组 |
类名.__dict__ | 类的字典属性 |
类名.__module__ | 类定义所在的模块 |
类名.__class__ | 实例对应的类(仅新式类中) |
类属性的增删改查
class People:
country = 'china'
def __init__(self, name):
self.name = name
def walk(self):
print('%s could walk' % self.name)
print(People.__dict__)
# 增
People.gender = 'male'
print(People.__dict__)
# 删
del People.gender
print(People.__dict__)
# 改
People.country = 'USA'
print(People.__dict__)
# 查
print(People.country)
5. 实例属性增删改查
class People:
country = 'china'
def __init__(self, name):
self.name = name
def walk(self):
print('%s could walk' % self.name)
p1 = People('Jack')
print(p1.__dict__)
# 增
p1.gender = 'male'
print(p1.__dict__)
# 删
del p1.gender
print(p1.__dict__)
# 改
p1.country = 'USA'
print(p1.__dict__)
# 查
print(p1.country)
6. 对象与实例属性
定义在类内的属性进入类的__dict__
,定义在__init__
函数内的属性进入实例的__dict__
中
修改类的属性会影响实例的属性,修改实例的属性不会影响类的属性
通过下面例子说明一下
class People:
country = 'china'
def __init__(self, name):
self.name = name
def walk(self):
print('%s could walk' % self.name)
# 修改类的属性,类和实例的属性都改变了
p1 = People('Jack')
People.country = 'USA'
print(p1.country)
print(People.country)
>>USA
>>USA
# 修改实例的属性,之改变了实例的属性,没有改变类的属性
p1.country = 'china'
print(p1.country)
print(People.country)
>>china
>>USA
print(People.__dict__)
print(p1.__dict__)
>>{'__module__': '__main__', 'country': 'USA', '__init__': <function People.__init__ at 0x0000025C214ADB70>, 'walk': <function People.walk at 0x0000025C214ADBF8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
>>{'name': 'Jack', 'country': 'china'}
上面的例子有一个共同的特点,是使用=
修改或者新增属性,通过=
实际上是为实例新增了属性,通过打印属性字典也可以看出,类和实例中都有country
属性。如果在实例中不通过=
去修改类的属性呢?
class People:
country = 'china'
l = ['a', 'b']
def __init__(self, name):
self.name = name
def walk(self):
print('%s could walk' % self.name)
p1 = People('Jack')
p1.l.append('c')
p1.country = 'USA'
print(p1.l)
print(p1.country)
print(People.l)
print(People.country)
print(People.__dict__)
print(p1.__dict__)
>>['a', 'b', 'c']
>>USA
>>['a', 'b', 'c']
>>china
>>{'__module__': '__main__', 'country': 'china', 'l': ['a', 'b', 'c'], '__init__': <function People.__init__ at 0x000002682FA0DAE8>, 'walk': <function People.walk at 0x000002682FA0DB70>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}
>>{'name': 'Jack', 'country': 'USA'}
由以上例子可以看出,类和实例中都有country
属性,在实例修改属性后没有影响类的country
属性
而实例中没有列表l
属性,在实例中修改l
就是在修改类中的属性,如果采用p1.l = [1,2,3]
则是给实例新增属性,那么就会与类无关。下面的例子可以说明这一点。
class People:
country = 'china'
l = ['a', 'b']
def __init__(self, name):
self.name = name
def walk(self):
print('%s could walk' % self.name)
p1 = People('Jack')
p1.l.append('c')
print(p1.l)
print(People.l)
>>['a', 'b', 'c']
>>['a', 'b', 'c']
p1.l = [1, 2, 3]
print(p1.l)
print(People.l)
>>[1, 2, 3]
>>['a', 'b', 'c']
7. 静态属性
使用property
关键字将类的函数的调用伪装成数据属性的调用,用例如下所示
class People:
country = 'china'
l = ['a', 'b']
def __init__(self, name):
self.name = name
@property
def walk(self):
print('%s could walk' % self.name)
p1 = People('Jack')
p1.walk
>>Jack could walk
8. 类方法
使用classmethod
关键字可以为类定义类方法,与类绑定, 类方法需要进行类的实例化就可以直接调用类的方法
class People:
country = 'china'
l = ['a', 'b']
def __init__(self, name):
self.name = name
@classmethod
def get_country(cls):
print(cls.country)
People.get_country()
>>china
9. 静态方法
使用staticmethod
关键字可以定义静态方法,不与类绑定,也不与实例绑定,这些方法成为类的工具包, 不能使用类变量和实例变量,只是名义上归类管理
class People:
country = 'china'
l = ['a', 'b']
def __init__(self, name):
self.name = name
@staticmethod
def foo():
print('do something')
People.foo()
>>do something
组合
类A和类B组合,那么两者就是AhasB的关系,A类可以使用B类的所有方法
class province:
def __init__(self, name):
self.name = name
def prt_pro_name(self):
print('the province\'s is: %s' % self.name)
class country:
def __init__(self, name, province):
self.name = name
self.province = province
def prt_cou_name(self):
print('the country\'s name is: %s'% self.name)
p1 = province('beijing')
c1 = country('china', p1)
c1.province.prt_pro_name()
c1.prt_cou_name()
>>the province's is: beijing
the country's name is: china
继承
Python支持多继承,可以有多个父类。子类继承了父类的所有属性。子类如果定义的属性与父类重名,不会覆盖父类,只是在自己的字典中增加了属性。
如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。
接口设计与归一化设计
继承有两种用途:
- 继承基类的方法,并且做出自己的改变或者扩展(代码重用)
- 声明某个子类兼容于某基类,定义一个接口类Interface,接口类中定义了一些接口名(就是函数名)且并未实现接口的功能,子类继承接口类,并且实现接口中的功能
实际应用中继承的第一种含义意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。
继承的第二种含义非常重要。它又叫“接口继承”。
接口继承实质上是要求“做出一个良好的抽象,这个抽象规定了一个兼容接口,使得外部调用者无需关心具体细节,可一视同仁的处理实现了特定接口的所有对象”——这在程序设计上,叫做归一化。
归一化使得高层的外部使用者可以不加区分的处理所有接口兼容的对象集合——就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕。
归一化,让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。
介绍 归一化之前必须要介绍抽象类的概念
抽象类
抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化
从设计角度去看,如果类是从现实对象抽象而来的,那么抽象类就是基于类抽象而来的。
从实现角度来看,抽象类与普通类的不同之处在于:抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法。这一点与接口有点类似,但其实是不同的
import abc
class linux_file(metaclass=abc.ABCMeta):
all_type='file'
@abc.abstractmethod #定义抽象方法,无需实现功能
def read(self):
'子类必须定义读功能'
pass
@abc.abstractmethod #定义抽象方法,无需实现功能
def write(self):
'子类必须定义写功能'
pass
class Text(linux_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('文本数据的读取方法')
def write(self):
print('文本数据的读取方法')
class Sata(linux_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('硬盘数据的读取方法')
def write(self):
print('硬盘数据的读取方法')
class Process(linux_file): #子类继承抽象类,但是必须定义read和write方法
def read(self):
print('进程数据的读取方法')
def write(self):
print('进程数据的读取方法')
抽象类与接口的关系
抽象类的本质还是类,指的是一组类的相似性,包括数据属性(如all_type)和函数属性(如read、write),而接口只强调函数属性的相似性。
抽象类是一个介于类和接口直接的一个概念,同时具备类和接口的部分特性,可以用来实现归一化设计
继承顺序
Python2中有新式类和经典类的概念:有父类的类是新式类,没有父类的类是经典类
Python3中的类都是新式类,因为即使没有任何继承也默认继承呢个object类
Python3中的继承顺序
Python3中的继承顺序都是广度优先,即先从一个分支中继承直到顶点,不继承顶点而先去继承旁支,直到最后一支继承到最后把顶点类继承下来,具体继承顺序如下所示
首先设定一种继承方式如下所示
图中的继承顺序是A–>B–>C–>E–>F–>D,在Python3中可以使用mro方法获取继承顺序
class D:
pass
class F(D):
pass
class E(F):
pass
class C(D):
pass
class B(C):
pass
class A(B,E):
pass
a = A
print(a.mro())
>>[<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]
Python2中的继承顺序
Python2中分为经典类和新式类,新式类的继承顺序与Python3中一致,经典类的继承顺序为深度优先,即一支开始继承一直继承到顶点类再去继承旁支,上图的继承顺序为:A–>B–>C–>D–>E–>F
在Python2中没有mro内置方法
子类调用父类的方法
class vehicle:
def __init__(self, name, speed, power):
self.name = name
self.speed = speed
self.power = power
def run(self):
print('it is running...')
class car(vehicle):
def __init__(self, name, speed, power, brand):
vehicle.__init__(self,name, speed, power)
self.brand = brand
def run(self):
print('I have a %s' % self.brand)
vehicle.run(self)
c1=car('sports car', '320km/h', 'gas', 'Maserati')
c1.run()
super调用父类的方法
class vehicle:
def __init__(self, name, speed, power):
self.name = name
self.speed = speed
self.power = power
def run(self):
print('it is running...')
class car(vehicle):
def __init__(self, name, speed, power, brand):
# vehicle.__init__(self,name, speed, power)
super(car,self).__init__(name, speed, power) # python3中super后括号中的参数可以不添加
self.brand = brand
def run(self):
print('I have a %s' % self.brand)
# vehicle.run(self)
super(car, self).run() # python3中super后括号中的参数可以不添加
c1=car('sports car', '320km/h', 'gas', 'Maserati')
c1.run()
多态
多态指的是一类事物有多种形态(定义类的角度)
多态性是指具有不同功能的函数可以使用相同的函数名,这样就可以用一个函数名调用不同内容的函数。(调用的角度)
多态性
在面向对象方法中一般是这样表述多态性:向不同的对象发送同一条消息,不同的对象在接收时会产生不同的行为(即方法)。也就是说,每个对象可以用自己的方式去响应共同的消息。所谓消息,就是调用函数,不同的行为就是指不同的实现,即执行不同的函数。
多态性的好处
增加了程序的灵活性,以不变应万变,不论对象千变万化,使用者都是同一种形式去调用
增加了程序额可扩展性,通过继承类创建了一个新的类,使用者无需更改自己的代码,还是用已有的方法去调用
具体实现代码如下所示
import abc
class File(metaclass=abc.ABCMeta):
@abc.abstractmethod
def file_write(self):
pass
@abc.abstractmethod
def file_read(self):
pass
class Text(File):
def file_write(self):
print('text file execute write operation')
def file_read(self):
print('text file execute read operation')
class Process(File):
def file_write(self):
print('process file execute write operation')
def file_read(self):
print('process file execute read operation')
class Disk(File):
def file_write(self):
print('disk file execute write operation')
def file_read(self):
print('disk file execute read operation')
def func_read(obj):
obj.file_read()
def func_write(obj):
obj.file_write()
t1 = Text()
p1 = Process()
d1 = Disk()
func_read(t1)
func_read(p1)
func_read(d1)
func_write(t1)
func_write(p1)
func_write(d1)
灵活性:都是使用file_write方法去调用写方法
可扩展性:Disk类继承File类后,依然可以使用func_write方法
封装
封装数据的主要原因是:保护隐私
封装方法的主要原因是:隔离复杂度
封装的两个层面
第一个层面
第一个层面的封装(什么都不用做):创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装
第二个层面
第二个层面的封装:类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问
在python中用双下划线的方式实现隐藏属性(设置成私有的)
类中所有双下划线开头的名称如__x
都会自动变形成:_类名__x
的形式:
这种自动变形的特点:
- 类中定义的
__x
只能在内部使用,如self.__x
,引用的就是变形的结果。 - 这种变形其实正是针对外部的变形,在外部是无法通过
__x
这个名字访问到的。 - 在子类定义的
__x
不会覆盖在父类定义的__x
,因为子类中变形成了:_子类名__x
,而父类中变形成了:_父类名__x
,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。:
注意:对于这一层面的封装(隐藏),我们需要在类中定义一个函数(接口函数)在它内部访问被隐藏的属性,然后外部就可以使用了
需要注意的问题
- 这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:
_类名__属性
,然后就可以访问了,如a._A__N
- 变形的过程只在类的定义是发生一次,在定义后的赋值操作,不会变形
- 在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的
通过property实现封装
上面已经介绍了一些与property的使用方法,这里主要介绍propert与getter,setter,deleter方法配合使用实现封装的概念
class Foo:
def __init__(self,val):
self.__NAME=val #将所有的数据属性都隐藏起来
@property
def name(self):
return self.__NAME #obj.name访问的是self.__NAME(这也是真实值的存放位置)
@name.setter
def name(self,value):
if not isinstance(value,str): #在设定值之前进行类型检查
raise TypeError('%s must be str' %value)
self.__NAME=value #通过类型检查后,将值value存放到真实的位置self.__NAME
@name.deleter
def name(self):
raise TypeError('Can not delete')
f=Foo('Jone')
print(f.name)
# f.name=10 #抛出异常'TypeError: 10 must be str'
del f.name #抛出异常'TypeError: Can not delete'
反射
通过字符串的形式操作对象相关的属性。python中的一切事物都是对象,都可以使用反射
实现反射的四个方法:
- hasattr(object,name)
- getattr(object, name, default=None)
- setattr(x, y, v)
- delattr(x, y)
使用反射的用途
实现可插拔设计
设定一种场景,一段程序没有完成,另一个人要基于此段程序设计,可以使用反射来先做后面的设计,等前面的设计完成后直接使用前面的设计,无需修改代码
class FtpClient:
'ftp客户端,但是还么有实现具体的功能'
def __init__(self,addr):
print('正在连接服务器[%s]' %addr)
self.addr=addr
#无论FtpClient是否完成都可以继续开发
f1=FtpClient('192.168.1.1')
if hasattr(f1,'get'):
func_get=getattr(f1,'get')
func_get()
else:
print('---->不存在此方法')
print('处理其他的逻辑')
动态导入模块
import importlib
importlib.import_module(module_name) # module_name 为字符串形式
类的内置属性attr
继承方式完成包装
组合方式完成授权
__getattribute__
只有__getter__
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__
只有__getattribute__
class Foo:
def __init__(self,x):
self.x=x
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
f1=Foo(10)
f1.x
f1.xxxxxx
__getter__
& __getattribute__
共存
class Foo:
def __init__(self,x):
self.x=x
def __getattr__(self, item):
print('执行的是我')
# return self.__dict__[item]
def __getattribute__(self, item):
print('不管是否存在,我都会执行')
raise AttributeError('error')
f1=Foo(10)
f1.x
f1.xxxxxx
#当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError