python类的相关知识第二部分

2023-05-30,,

类的继承、多态、封装

一、类的继承

1、应用场景:

类大部分功能相同,大类包含小类的情况

例如:

动物类

共性:都要吃喝拉撒、都有头有脚

特性:

猫类、走了很轻,叫声特别,喜欢白天睡觉

狗类、的叫声很大,晚上睡觉

2、继承顺序

python2版本中多重继承有分两种继承循序

(1)、一直往上找 、找到最高级的父类再重另外一个分支找,直到找到为止。

(2)、一直往上找,找到最高级父类的下一层后就不找了。从另外一个分支找,另外一个分支没找到最后才找最高级的父类。

再python3中只有第一种继承循序。

3、子类调用父类的方法:

4、接口继承和归一化设计

接口继承和归一化设计例子:

 import abc

 class Animal(metaclass=abc.ABCMeta):
@abc.abstractclassmethod
def shout(self): #定义抽象方法,无需实现功能
pass @abc.abstractclassmethod
def run(self):
pass @abc.abstractclassmethod
def jump(self):
pass class Felid(Animal):
# 子类继承抽象类,但是必须定义shout、run、jump方法,否则实例化后就报错了。这个就是归一化设计。
def __init__(self,name):
self.name = name def shout(self):
print("%s 在叫" % self.name) def run(self):
print("%s 在跑" % self.name) def jump(self):
print("%s 在跳" % self.name) tiger = Felid("老虎")
tiger.shout()

接口继承和归一化设计

二、类的多态

1、应用场景:

不同的类有相同的方法,不同的对象调用相同的方法。多态是依附于继承存在的,有了继承才能有多态。

例如:

H2O分子:有三种形态体现

(1)、水

(2)、冰

(3)、水蒸气

2、代码实现例子

 class H2O:
def __init__(self,name,temp):
self.name = name
self.temp=temp def who_am_i(self):
if int(self.temp) <0:
print("I am %s." % self.name)
elif int(self.temp) >0 and int(self.temp) <100:
print("I am %s" % self.name)
else:
print("I am %s" % self.name) class Ice(H2O):
pass class Water(H2O):
pass class Steam(H2O):
pass s1 = Ice("冰",-1)
s2 = Water("水",10)
s3 = Steam("空气",1000) def func(obj):
obj.who_am_i() func(s1)
func(s2)
func(s3)

多态的例子

三、类的封装

1、单下划线和双下划线的私有属性和方法

2、反射

实现反射的四个方法:

hasattr(object,name),getattr(object,name,default=None),setattr(object,name,value),delattr(object,name)

四种方法例子展示:

 class Foo:
foo_name = "my foo test" def __init__(self,name,addr):
self.name = name
self.addr = addr def test1(self):
print("test1") def test2(self):
print("test2") # hasattr
t1 = Foo("FooTest","beijing")
print(hasattr(t1,"foo_name"))
print(hasattr(t1,"test1"))
# getattr
print(getattr(t1,"foo_name"))
func1 = getattr(t1,"test1")
func1()
print(getattr(t1,"test10","not found"))
# setattr
setattr(t1,"start_time","")
print(t1.__dict__)
#delattr
delattr(t1,"start_time")
print(t1.__dict__) #打印显示##
True
True
my foo test
test1
not found
{'name': 'FooTest', 'addr': 'beijing', 'start_time': ''}
{'name': 'FooTest', 'addr': 'beijing'}

反射四个方法使用例子

好处:

(1)、实现可插拔机制

 # a同学写的类
class FtpClient:
"A同学写的功能,但是没有完成"
def __init__(self,addr):
print("正在连接服务器%s " % addr)
self.addr = addr # b同学要调用,并进行其他的工作
f1 = FtpClient("192.168.1.1")
if hasattr(f1,"get"):
func_get = getattr(f1,"get")
func_get
else:
print("功能还未完成,等待中。。")

反射插拔式设计例子

可以看出,一方没有完成的,完全不影响另一方的其他工作的进行。

(2)、实现动态导入模块

 import importlib

 __import__("import_lib.mataclass")
#importlib.import_module("import_lib.mataclass")
#两种效果一样,但是官方建议用下面一种。

3、__getattr__,__setattr__,delattr__

例子:

 class Foo:
x =1
def __init__(self,y):
self.y = y def __getattr__(self, item):
print("----> from getattr: 你找的属性不存在") def __setattr__(self, key, value):
print("----> from setattr: ")
# self.key = value # 这里这么写是一个无限递归
self.__dict__[key]=value # __setattr__ 其实就是重写了__dict__,所以不是特殊需求,一般都不会去动这块。 def __delattr__(self, item):
print("----> from delattr: ")
self.__dict__.pop(item) # 触发__getattr__
f1 = Foo(10)
f1.z # 只有在使用点调用属性且属性找不到的时候才触发__getattr__ # 触发__setattr__
f1.z = 3
print(f1.__dict__) # 这里显示字典为空,是因为重写了__setattr__,凡是赋值操作都会触发他的运行,这里什么都没有写,所以是空
# 触发__delattr__
f1.a = 3#
del f1.a
print(f1.__dict__)

演示例子

4、二次加工标准类型

(1)、包装

包装:python为我们提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了继承、派生知识。

例子:

 class List(list):
def append(self, p_object):
"派生自己的append方法:加上类型检测"
if not isinstance(p_object,int):
print("must be int")
return
super().append(p_object) def mid(self):
"新增一个取列表中间值的方法"
index = len(self)//2
return self[index]
# 只允许数值进行append操作
L1 = List([1,2,3,4,5])
print(L1)
L1.append(10)
print(L1)
L1.append("")
print(L1)
# 取中间值
print(L1.mid()) ##打印信息
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5, 10]
must be int
[1, 2, 3, 4, 5, 10]
4

包装例子

(2)、授权

授权:授权是包装的一个特性,包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其他的则保持原样。授权的过程,即是所有更新的功能都是有心累的某部分来处理,但是已存在的功能就授权给对象的默认属性。

实现授权的关键点就是覆盖__getattr__方法。

例子:

 import time

 class FileHandle:
def __init__(self,filename,mode,encoding):
self.file=open(filename,mode=mode,encoding=encoding) def write(self,line): #重写了write方法
t = time.strftime("%Y-%m-%d %X")
self.file.write("%s %s" % (t,line)) def __getattr__(self, item):
# print(self.file,item,type(item))
return getattr(self.file,item) f1 = FileHandle("a.txt","w+","utf-8")
f1.write("aaaaaaaa\n")
f1.write("bbbb\n")
f1.seek(0) ##使用原来open类的seek方法
print(f1.read()) ##打印
2017-05-04 14:58:48 aaaaaaaa
2017-05-04 14:58:48 bbbb

授权例子

5、__setitem__,__getitem__,delitem__

这三个方法和上面的__setattr__,__getattr__,delattr__差不多,区别就在于,attr是用点的方式去访问的,item的是用["key"]的方式访问的。

例子:

 class Foo:
def __init__(self,name):
self.name = name def __getitem__(self, item):
print("----> getitem: ")
print(self.__dict__[item]) def __setitem__(self, key, value):
print("----> setitem: ")
self.__dict__[key] = value def __delitem__(self, key):
print("----> delitem: ")
self.__dict__.pop(key) def __delattr__(self, item):
print("----> delattr: ")
self.__dict__.pop(item) f1 = Foo("aaa")
f1['age'] = 18 # 触发setitem
f1['name'] #触发 getitem
print(f1.__dict__)
del f1.name
del f1['age']
f1["name"] = "bbb"
print(f1.__dict__) #打印
----> setitem:
----> getitem:
aaa
{'name': 'aaa', 'age': 18}
----> delattr:
----> delitem:
----> setitem:
{'name': 'bbb'}

例子

6、__str__,__repr__,__format__

改变对象的字符串显示__str__,__repr__

自定制格式化字符串__format__

例子:

 format_dict = {
'nat':'{obj.name}-{obj.addr}-{obj.type}',
'tna':'{obj.type}:{obj.name}:{obj.addr}',
'tan':'{obj.type}/{obj.addr}/{obj.name}',
} class School:
def __init__(self,name,addr,type):
self.name = name
self.addr = addr
self.type = type def __repr__(self):
return 'School(%s,%s)' % (self.name,self.addr) def __str__(self):
return "(%s,%s)" %(self.name,self.addr) def __format__(self, format_spec):
if not format_spec or format_spec not in format_dict:
format_spec='nat'
fmt = format_dict[format_spec]
return fmt.format(obj=self) s1 = School("清华","beijing","public")
print('from repr: ',repr(s1))
print('from str: ',str(s1))
print(s1)
"""
str函数或者print函数--->obj.__str__()
repr函数或者交互式解释器--->obj.__repr__()
如果__str__没有定义,那么就会使用__repr__来代替输出
注意:这两个方法的返回值必须是字符串,否则抛出异常
"""
print(format(s1,'nat'))
print(format(s1,'tna'))
print(format(s1,'tan'))
print(format(s1,'aaa')) ##打印
from repr: School(清华,beijing)
from str: (清华,beijing)
(清华,beijing)
清华-beijing-public
public:清华:beijing
public/beijing/清华
清华-beijing-public

例子

7、__slots__

最大的用途就是用来节省内存。

例子:

 '''
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。 '''
class Foo:
__slots__='x' f1=Foo()
f1.x=1
f1.y=2#报错
print(f1.__slots__) #f1不再有__dict__ class Bar:
__slots__=['x','y'] n=Bar()
n.x,n.y=1,2
n.z=3#报错 __slots__使用

例子

8、__iter__,__next__

例子:

class Fib:
def __init__(self):
self._a = 0
self._b = 1 def __iter__(self):
return self def __next__(self):
self._a,self._b=self._b,self._a + self._b
return self._a f1 = Fib() print(f1.__next__())
print(next(f1))
print(next(f1)) for i in f1:
if i > 100:
break
print('%s ' %i,end='') #打印
1
1
2
3 5 8 13 21 34 55 89

实现迭代器协议例子

9、文件描述符__set__,__get__,__delete__

(1)、描述符是什么:描述符本质就是一个新式类,在这个新式类中,至少实现了__get__(),__set__(),__delete__()中的一个,这也被称为描述符协议

__get__():调用一个属性时,触发

__set__():为一个属性赋值时,触发

__delete__():采用del删除属性时,触发

(2)、描述符是干什么的:描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的属性,不能定义到构造函数中)

(3)、描述符分两种:数据描述符(至少实现了__get__()和__set__())、非数据描述符(没有实现__set__())

(4)、注意事项:

(4.1)、描述符本身应该定义成新式类,被代理的类也应该是新式类

(4.2)、必须把描述符定义成这个类的类属性,不能定义在构造函数中

(4.3)、要严格遵循该优先级,优先级由高到低分别是:

(4.3.1)、类属性

(4.3.2)、数据描述符

(4.3.3)、实例属性

(4.3.4)、非数据描述符

(4.3.5)、找不到的属性触发__getattr__()

(5)、描述符的使用

 class Str:
"数据描述符"
def __get__(self, instance, owner):
print("-->instance: %s" % instance)
print("-->owner: %s" % owner) def __set__(self, instance, value):
print("-->instance: %s" % instance)
print("-->value: %s" % value) def __delete__(self, instance):
print("-->instance: %s" % instance) class People:
name = Str() #描述符
def __init__(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary p1 = People("test1",22,1121.1) # 触发 __set__
p1.name # 触发__get__
print(p1.__dict__) #发现name属性没有。这里说明一个问题,是数据描述符优先级高于实例属性。 ##打印
-->instance: <__main__.People object at 0x000002CA13189978>
-->value: test1
-->instance: <__main__.People object at 0x000002CA13189978>
-->owner: <class '__main__.People'>
{'age': 22, 'salary': 1121.1}

数据描述符优先级例子

 class Str:
"数据描述符"
def __init__(self,key):
self.key = key def __get__(self, instance, owner):
return instance.__dict__[self.key] def __set__(self, instance, value):
if not isinstance(value,str):
print("传入是数据不是字符串!")
# raise TypeError("传入是数据不是字符串!")
return
instance.__dict__[self.key] = value def __delete__(self, instance):
instance.__dict__.pop[self.key] class People:
name = Str("name") #数据描述符
def __init__(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary p1 = People("test1",22,1121.1) # 触发 __set__
p1.name # 触发__get__
print(p1.__dict__) #发现name属性没有。这里说明一个问题,是数据描述符优先级高于实例属性。
p2 = People(1111,22,232323)
print(p2.__dict__) #打印
{'name': 'test1', 'age': 22, 'salary': 1121.1}
传入是数据不是字符串!
{'age': 22, 'salary': 232323}

赋值类型判断的描述符例子

 class Str:
"数据描述符"
def __init__(self,key,exspect_type):
self.key = key
self.exspect_type = exspect_type def __get__(self, instance, owner):
return instance.__dict__[self.key] def __set__(self, instance, value):
if not isinstance(value,self.exspect_type):
print("传入是数据不是%s" % self.exspect_type)
# raise TypeError("传入是数据不是字符串!")
return
instance.__dict__[self.key] = value def __delete__(self, instance):
instance.__dict__.pop[self.key] class People:
name = Str("name",str) #数据描述符
age = Str("name",int) #数据描述符
salary = Str("name",float) #数据描述符
def __init__(self,name,age,salary):
self.name = name
self.age = age
self.salary = salary p1 = People("test1",22,1121.1) # 触发 __set__
p1.name # 触发__get__
print(p1.__dict__) #发现name属性没有。这里说明一个问题,是数据描述符优先级高于实例属性。
p2 = People("test2",22,232323)
print(p2.__dict__) #打印
{'name': 1121.1}
传入是数据不是<class 'float'>
{'name': 22}

支持多类型判断的描述符例子

(6)、类的装饰器

 def Typed(**kwargs):
def decorate(obj):
for k,v in kwargs.items():
setattr(obj,k,v)
return obj
return decorate @Typed(x=1,y=2,z=3)
class Foo:
pass
print(Foo.__dict__)
print(Foo.x) @Typed(name = "test1")
class Bar:
pass
print(Bar.name) #打印
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, 'x': 1, 'y': 2, 'z': 3}
1
test1

类的装饰器(带参数)

 class Typed:
def __init__(self,name,expect_type):
self.name = name
self.expect_type = expect_type def __get__(self, instance, owner):
return instance.__dict__[self.name] def __set__(self, instance, value):
if not isinstance(value,self.expect_type):
raise TypeError("类型错误,不是%s" % self.expect_type) # 接收到实例化的值并判断传进来的值的类型是否是装饰器传进来的数据类型。条件满足就赋值。
instance.__dict__[self.name] = value def __delete__(self, instance):
instance.__dict__.pop(self.name) def func(**kwargs):
def decorate(obj):
for k,v in kwargs.items():
print(k,v)
setattr(obj,k,Typed(k,v)) #重点在这里,这里将k,v传到Typed类中,也就是Typed类的两个属性,name和expect_type。
return obj
return decorate @func(name=str,age=int) #(2)在实例化之前有一个func装饰器,装饰器会将Foo类传入func。@dacorate ==》Foo=dacorate(Foo)
class Foo:
def __init__(self,name,age):
self.name = name
self.age = age f1 = Foo("test1",19) #(1)实例化的时候将值传给Foo类,
print(f1.__dict__) @func(salary=float)
class Bar:
def __init__(self,salary):
self.salary = salary b1 = Bar(1000.0)
print(b1.__dict__)

装饰器+数据描述符来限定数据传入的类型判断

(7)、自定制property

 class Newproperty:
def __init__(self,func):
self.func = func def __get__(self, instance, owner):
return self.func(instance) class Foo:
def __init__(self,name,width,length):
self.name = name
self.width = width
self.length = length @Newproperty # 等同于 area = Newproperty(area)
def area(self):
return self.width * self.length f1 = Foo("房子",10,14)
print(f1.area)
print(f1.__dict__) #打印
140
{'name': '房子', 'width': 10, 'length': 14}

定制property例子

 class Lazyproperty:
def __init__(self,func):
self.func = func def __get__(self, instance, owner):
value = self.func(instance)
setattr(instance,self.func.__name__,value) # 这里将值保存到了对象的字典里面。所以当值存在的时候就不会在重复赋值了。
return value
# def __set__(self, instance, value): # 如果这里加了set,那么优先级就不一样了。属于数据描述符的优先级了。所以这里的延迟计算就失效了。
# print("aaaa") class Foo:
def __init__(self,name,width,length):
self.name = name
self.width = width
self.length = length @Lazyproperty # 等同于 area = Newproperty(area)
def area(self):
return self.width * self.length f1 = Foo("房子",10,14)
print(f1.area)
print(f1.__dict__)

实现延迟计算功能

(8)、描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述符是很多高级哭和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件。

10、__init__,__del__

构造函数,析构函数

析构方法__del__:当对象在内存中被释放时,自动触发执行。

注:此方法一般无需定义,应为python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都交给python解释器来执行,所以析构函数的调用是有解释器在进行垃圾回收时触发执行的。

构造函数__init__:用于初始化类的内容部状态,也就是说当该类实例化的时候就会执行该函数。

11、__enter__,__exit__

上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

 class Open:
def __init__(self,name):
self.name = name def __enter__(self):
print("出现with语句,对象的__enter__被触发,有返回值这复制给as声明的变量") def __exit__(self, exc_type, exc_val, exc_tb):
print("with 中代码执行完毕执行这里。")
print(exc_type)
print(exc_val)
print(exc_tb)
return True #如果少了这句的话,抛出异常后就不执行下面的代码了 with Open('a.txt') as f:
print("======>执行代码快")
raise AttributeError("抛出异常")
print(""*100)

例子

__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

如果__exit__()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后面的语句正常执行

好处:

(1)、使用with语句的目的就是把代码快放入with中执行,with结束后,自动完成清理工作,无须手动干预

(2)、在需要管理一些资源,比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无需再去关心这个问题。后面的网络编程将大有用处。

12、property

13、__doc__

这个方法是类的描述信息

特点:这个属性无法被继承

14、__module__和__class__

__module__表示当前操作的对象在哪个模块

__class__表示当前操作的对象的类是什么

15、__call__

对象后面加括号,触发执行。

注:构造方法的执行是由创建对象触发的,即:对象 = 类名;而对于__call__方法的执行是由对象后加括号触发的,即:对象()或者类()

16、metaclass,元类

python中一切皆对象,类本身也是一个对象,当使用关键字class的时候,python解释器在加载class的时候就会创建一个对象

创建类的两种方式:

(1):

class Foo:
def func(self):
print('from func')

(2):

def func(self):
print('from func')
x=1
Foo=type('Foo',(object,),{'func':func,'x':1})

什么是元类:

元类是类的类,是类的模版

元类是用来控制如何创建类的,正如类是创建对象的模版一样。

元类的实例为类,正如类的实例对象(f1对象是Foo类的一个实例,Foo类是type类的一个实例。)

type是python的一个内建元类,用来直接控制生成类,python中任何class定义的类其实都是type类实例化的对象。

 class Mytype(type):
def __init__(self,what,bases=None,dict=None):
print('mytype init') def __call__(self, *args, **kwargs):
obj=self.__new__(self)
self.__init__(obj,*args,**kwargs)
return obj class Foo(object,metaclass=Mytype):
x=1111111111 def __init__(self,name):
self.name=name def __new__(cls, *args, **kwargs):
return super().__new__(cls) f1=Foo('egon') print(f1.__dict__) 自定制元类纯净版

自制元类例子

#元类总结
class Mymeta(type):
def __init__(self,name,bases,dic):
print('===>Mymeta.__init__') def __new__(cls, *args, **kwargs):
print('===>Mymeta.__new__')
return type.__new__(cls,*args,**kwargs) def __call__(self, *args, **kwargs):
print('aaa')
obj=self.__new__(self)
self.__init__(self,*args,**kwargs)
return obj class Foo(object,metaclass=Mymeta):
def __init__(self,name):
self.name=name
def __new__(cls, *args, **kwargs):
return object.__new__(cls) '''
需要记住一点:名字加括号的本质(即,任何name()的形式),都是先找到name的爹,然后执行:爹.__call__ 而爹.__call__一般做两件事:
1.调用name.__new__方法并返回一个对象
2.进而调用name.__init__方法对儿子name进行初始化
''' '''
class 定义Foo,并指定元类为Mymeta,这就相当于要用Mymeta创建一个新的对象Foo,于是相当于执行
Foo=Mymeta('foo',(...),{...})
因此我们可以看到,只定义class就会有如下执行效果
===>Mymeta.__new__
===>Mymeta.__init__
实际上class Foo(metaclass=Mymeta)是触发了Foo=Mymeta('Foo',(...),{...})操作,
遇到了名字加括号的形式,即Mymeta(...),于是就去找Mymeta的爹type,然后执行type.__call__(...)方法
于是触发Mymeta.__new__方法得到一个具体的对象,然后触发Mymeta.__init__方法对对象进行初始化
''' '''
obj=Foo('egon')
的原理同上
''' '''
总结:元类的难点在于执行顺序很绕,其实我们只需要记住两点就可以了
1.谁后面跟括号,就从谁的爹中找__call__方法执行
type->Mymeta->Foo->obj
Mymeta()触发type.__call__
Foo()触发Mymeta.__call__
obj()触发Foo.__call__
2.__call__内按先后顺序依次调用儿子的__new__和__init__方法
'''

python类的相关知识第二部分的相关教程结束。

《python类的相关知识第二部分.doc》

下载本文的Word格式文档,以方便收藏与打印。