python 面向对象高级编程
使用__slots__
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017501655757856
slots 是跟踪的意思,在python中是在类中使用一个变量,主要的功能是 限制 为该类的实例绑定的属性 只能是__slots__中声明的
class Student(object): __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
但是这个只对当前类(Student)的实例起作用,不对子类起作用,如果子类中也有__slots__这个变量,那么可以为子类实例绑定的属性就是 父类和子类__slots__的并集
使用@property
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017502538658208
这是个装饰器,功能是能够让 封装数据的方法 像实例的属性一样使用,为什么增加能封装数据的方法呢,因为可以对设置实例的属性时可以检查,但是实例调用方法不如通过属性来访问那样简便,虽然都是一行代码
这个是python原生实现的装饰器,是为了让方法变成属性调用
怎么使用呢,和装饰器的方法一模一样
@property装饰器是将读取属性的方法变成属性调用的 。然后@property装饰器又创建了另外一个装饰器@score.setter,这个装饰器是为了 把设置属性的方法 变成 属性 调用的
注意一个@score.setter可以写道多个属性的设置方法上,
class Student(object): count = 0 @property#下面是getter def score(self): return self.__score @score.setter#下面是#setter,注意这个下面函数名就是设置时调用的名称,可以和getter的函数名相同,之所以不同仅仅是为了说明,不同时也可以工作 def set_score(self,value): if not isinstance(value,int): raise ValueError('value must int object!') elif not 0<=value<=100: raise ValueError('value must between 0 and 100') else: self.__score=value @property#如果一个属性只用@property包裹,那么这个属性是只读的,设置时会出错 def difference(self): return 100-self.score
@property 广泛运用在类的定义中,可以让使用者写出简短的代码,同时又能保证了对参数的检查,避免程序出现意外错误
class Screen(object): @property def width(self): return self.__width @width.setter def width(self,value): self.__width=value @property def height(self): return self.__height @width.setter里面的s.height出来的一直是1024,我一直不知道为什么,最后才发现是这里写错了,因为是复制过来的然后没改完,总是犯这样的错误,而且最后错误出现的时候也不会溯源 def height(self,value): self.__height=value @property def resolution(self): return self.width*self.height#还要注意上面的错误可以在这里将width改为__width,__height来解决 if __name__ == "__main__": s = Screen() s.height = 768 s.width = 1024 print('resolution =', s.resolution) if s.resolution == 786432: print('测试通过!') else: print('测试失败!') pass
多重继承MaxIn
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017502939956896
python 允许多重继承,而有的语言只允许单一集成(Java)
在面向对象的设计思想中,通常按照不同的归属关系来设置类间的层次,但是归类关系的不同会造成多种多样的层次,会大大增加层次的复杂度,这样的设计是不行的。
采用多重继承,可以让一个类同时继承多个类,从而拥有多个父类的功能
为了使条例清晰,就指定主要的层次和
在设计类的关系时,通常都是单一继承下来的,如果需要增加额外的类,就通过多重继承来实现,这种设计通常称为MaxIn
为了更好的看出类的继承关系,我们就把额外的类增加一个MaxIn后缀名,来表示他不是主要的类
MaxIn的目的就是为一个类增加多重功能,从而只设计类的时候优先考虑通过多重继承的方式来组合多个MaxIn的功能,而不是设计复杂的层次关系,只需要根据需要选择不同的功能的类来组合,就可以快速构建出所需要的类
定制类
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017590712115904
我们在定义类的时候,除了寻常的定义类的功能之外,还能通过在类内部定义一些特殊的函数如__init__等来让类有一些另外的功能,这些函数是用来定义特殊功能的
__str__()和__repr__()
修改打印实例时显示的字符
>>> print(Student('Michael')) <__main__.Student object at 0x109afb190>
打印出一堆<__main__.Student object at 0x109afb190>
,不好看。
定义好__str__()方法
>>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... >>> print(Student('Michael')) Student object (name: Michael)
但是直接敲出来变量打印出来的还时原来的样子
>>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310>
这是因为显示变量用的是__repr__()而不是__str__(),两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。
解决办法是再定义一个__repr__()
。但是通常__str__()
和__repr__()
代码都是一样的,所以,有个偷懒的写法:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__
__iter__()
前面已经说过、我们定义的类其实也是一种数据类型,可以使用isinstance()来判断,如果我们想要让定义的类能够被迭代,即传入for循环中,我们就可以通过定义__iter__()这个特殊函数来实现
该方法返回一个迭代对象,然后for循环就会不断的调用迭代对象的__next__()来获取下一个值,直到遇到StopIterable错误结束循环
作者举了一个例子,以斐波那契数列为例,定义了一个Fib类,可以作用于for循环
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值
结果
>>> for n in Fib(): ... print(n) ... 1 1 2 3 5 ... 46368 75025
__getitem__
如果我们想让定义的类像list数据类型一样,可以通过下标来访问,就可以通过定义__getitem__()来实现
class Fib(object):#其实是通过重新执行 生成循环 然后在要取的那个值让循环结束,返回这个值,不是从生成好的结果中取,因为他不是用来储存的数据类型 def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
但是这里还不能实现List的切片功能,如果想要实现,仍然可以
class Fib(object): def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L
如果想要对切片的step参数和负数也做相应的实现,作者说也可以
如果把对象看成dict
,__getitem__()
的参数也可能是一个可以作key的object,例如str
。
与之对应的是__setitem__()
方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()
方法,用于删除某个元素。
总之,通过上面的方法,我们自己定义的类表现得和Python自带的list、tuple、dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。
__getattr__
通过定义这个函数,我们可以在使用实例访问未定义的属性时,返回这个属性,原理就是在这个函数中创建这个属性然后返回、即动态的返回一个属性
当调用未存在的属性时,Python会调用__getattr__来试图获得一个属性。注意,只有在调用的属性不存在时,才会去这个函数中寻找
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99
此外,也可以返回一个函数
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25
不过相应的调用方式也需要改变,因为调用的方法嘛
>>> s.age() 25
到此为止我们就实现了可以访问没有定义的属性,但是我们现在访问任何属性都可以,如果我们没有在__getattr__中对这个属性做处理,就会返回None,如果我们想要只响应几个特定的属性,我们就需要让__getattr__还能抛出异常
class Student(object): def __getattr__(self, attr): if attr=='age': return lambda: 25 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
作者说这个方法的用处很大,可以把一个类的所有属性和方法的调用全部动态化处理,不需要任何特殊手段了。
作者举了一个例子说明了动态化处理的应用,针对一些完全动态化的情况
比如实现一个根据URL实现完全动态调用的SDK,
class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path):# return Chain('%s/%s' % (self._path, path))#这里为什么又用China创建了实例? def __str__(self): return self._path __repr__ = __str__ #效果 >>> Chain().status.user.timeline.list#请求的时status.user.timeline.list属性#这样就是链式调用 '/status/user/timeline/list'#返回的
对应于在SDK中为每一个URL的API写一个方法,通过__getattr__()来实现不仅简单,而且还不用随着API的改变而改变
__call__
可以对实例本身调用
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name) #调用方法 >>> s = Student('Michael') >>> s() # self参数不要传入 My name is Michael.
__call__还可以定义参数,这样的话,调用对象就和调用函数一样了
所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,比如函数和我们上面定义的带有__call__()
的类实例:
>>> callable(Student()) True >>> callable(max) True >>> callable([1, 2, 3]) False >>> callable(None) False >>> callable('str') False
通过callable()可以判断一个对象是否时可调用对象
使用枚举类
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017595944503424
对象实例有限且固定的 类称为 枚举类,比如季节、月份等
python中提供了Enum类
from enum import Enum Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
这样我们就获得了Month
类型的枚举类,可以直接使用Month.Jan
来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items(): print(name, '=>', member, ',', member.value)
value
属性则是自动赋给成员的int
常量,默认从1
开始计数。
如果需要更精确地控制枚举类型,可以从Enum
派生出自定义类:
from enum import Enum, unique @unique class Weekday(Enum): Sun = 0 # Sun的value被设定为0,还可以设定为别的值 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6
@unique
装饰器可以帮助我们检查保证没有重复值。
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。
Enum
可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。
理解元类、ORM
参考链接:https://www.liaoxuefeng.com/wiki/1016959663602400/1017592449371072
笔记(其实都是抄的):https://www.cnblogs.com/Gaoqiking/p/10744253.html