写在篇前
OOP(Object Oriented Programming),即面向对象的程序设计,不同于传统的面向过程的程序设计,它大大地降低了软件开发的难度,使编程就像搭积木一样简单,是当今编程以及模式设计一股势不可挡的潮流。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。面向对象编程的基础就是类,所谓“类生一,一生二,二生三,三生万物”,类是对现实世界事物的抽象,一个类包括了现实世界中一组对象的方法和属性。
封装
封装就是把数据和操作数据的方法绑定起来,通过已定义的接口实现对数据的访问以及修改,屏蔽繁杂的技术细节。编写一个类就是对数据和数据操作的封装,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。如下示例代码,即是封装了一个Student类,包含类属性Student_number
,对象属性__id
,name
,score
,以及一个打印学生成绩的方法print_info()
,这样实例化对象之后没就可以直接调用print_info()
方法获取学生信息,而无需关注其实现细节。
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
class Student(object):
"""
Student class
"""
# student_number是一个类属性, 可通过类访问,也可通过对象访问,所有对象共享该表量
student_number = 0
# 方法可有默认参数、可变参数、关键字参数和命名关键字参数
def __init__(self, sid, name, score):
"""
Student class init method
"""
Student.student_number += 1
self.__id = sid
self.name = name
self.score = score
def print_info(self):
"""
get Student info
"""
print('%s: %s' % (self.name, self.score), end=' ')
bart = Student('001', 'Bart Simpson', 59)
bart.print_score()
print(bart.student_number) # 1
print(Student.student_number) # 1
lisa = Student('002', 'Lisa Simpson', 87)
lisa.print_score()
print(bart.student_number) # 2
print(Student.student_number) # 2
继承
继承是从已有类得到继承信息创建新类的过程,一方面可以继承父类所有的属性和方法,另一方面可以增加或则重写父类方法,以适应具体业务流程。
单继承
这里紧接上面的例子,创建一个MasterStudent
类,使其继承Student
类,这样子类并拥有了父类的所有方法和属性(实际上父类__init__()
方法不会被继承,这一点放到后面再讨论);另外再增加新的属性major
和新的方法change_major()
。
class MasterStudent(Student):
"""
MasterStudent class
"""
def __init__(self, sid, name, score, major):
"""
Student class init method
"""
super().__init__(sid, name, score)
self.major = major
def print_info(self):
"""
get Student score
"""
super().print_info() # 调用父类方法
print('and majoring in %s' % self.major)
def change_major(self, major):
"""
change major
"""
self.major = major
if __name__ == '__main__':
ms = MasterStudent('001', 'jeffery', '90', 'bio')
ms.print_info()
关于继承,需要注意的一个问题是构造方法(__new__
)和初始化方法(__init__
)的执行顺序:
class A(object):
def __new__(cls, *args, **kwargs):
print('A __new__')
return super().__new__(cls, *args, **kwargs)
def __init__(self):
print('A __init__')
class B(A):
def __new__(cls, *args, **kwargs):
print('B __new__')
return super().__new__(cls, *args, **kwargs)
def __init__(self):
print('B __init__')
super().__init__()
# 输出结果
B __new__
A __new__
B __init__
A __init__
Mixin
Mixin是python中多继承的一种方案,但是事实上它和一般意义上的多继承(其他编程语言如C++)是有区别的,它更像是Java中的接口,但是是提供默认实现的接口(Java接口默认不提供实现)。举个例子,民航飞机是一种交通工具,对于土豪们来说直升机也是一种交通工具。对于这两种交通工具,它们都有一个功能是飞行,但是轿车没有。所以,我们不可能将飞行功能写在交通工具这个父类中。但是如果民航飞机和直升机都各自写自己的飞行方法,又违背了代码尽可能重用的原则(如果以后飞行工具越来越多,那会出现许多重复代码)。怎么办,那就只好让这两种飞机同时继承交通工具以及飞行器两个父类,这样就出现了多重继承。这时又违背了继承必须是”is-a”关系。这个难题该怎么破?
下面,我们用python实现一下:
class Transportation(object):
"""
交通工具的基类
"""
pass
class FlyMixin(object):
"""
Mixin 类,告诉其他人,这是一个"接口",能够扩充类的某一个功能
"""
def fly_able(self):
pass
class Airliner(Transportation, FlyMixin):
pass
class Helicopter(Transportation, FlyMixin):
pass
Mixin其实应用非常广泛,在django中,如某一个页面需要登陆才允许访问,则可以让相应View继承LoginRequiredMixin
class LogoutView(LoginRequiredMixin, View):
"""
比如退出登陆,当然禁止没有登陆的用户执行该操作
"""
def get(self, request):
logout(request)
return HttpResponseRedirect(reverse("index"))
看完代码,你会很明显的发现FlyMixin
不就是一个普通类吗?所谓Mixin不就是多继承吗?对,但是我们一般约定以Mixin结尾的类作为接口类,用来灵活扩充类的功能。
抽象
抽象类是一个特殊的类,它从一堆类中抽取相同的内容,组建成一个新的类。它的特殊之处在于只能被继承,不能被实例化。比如三角形类、正方形类、菱形类都是属于图形类,都应该有边长属性,求面积、求周长的方法。下面举一个实例,说明python中抽象类的使用。
class A(object):
"""
抽象类不能被实例化
"""
__metaclass__ = abc.ABCMeta
@abc.abstractmethod
def load(self, _input):
pass
@abc.abstractmethod
def save(self, output, data):
pass
class B(A):
def load(self, _input):
return _input.read()
def save(self, output, data):
return output.write(data)
if __name__ == '__main__':
print(issubclass(B, A))
print(isinstance(B(), A))
print(A.__subclasses__())
还有一种通过__subclasshook__
魔法方法的用法我在这也提一下:
class C(object, metaclass=abc.ABCMeta):
@abc.abstractmethod
def say(self):
pass
@classmethod
def __subclasshook__(cls, _cls):
"""
该方法意味着只要一个类实现了他的抽象方法,就会被认为是该类的子类
"""
print('class invoke ********')
if cls is C:
if any("say" in B.__dict__ for B in _cls.__mro__):
return True
return NotImplemented
class D(object):
def say(self):
print('function say implemented in subclass')
print(issubclass(D, C))
print(isinstance(D(), C))
print(D.__dict__)
print(C.__subclasshook__(D))
多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态,多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
class Car:
def __init__(self, name):
self.name = name
def drive(self):
raise NotImplementedError("Subclass must implement abstract method")
def stop(self):
raise NotImplementedError("Subclass must implement abstract method")
class SportsCar(Car):
def drive(self):
return 'SportsCar driving!'
def stop(self):
return 'SportsCar breaking!'
class Truck(Car):
def drive(self):
return 'Truck driving slowly because heavily loaded.'
def stop(self):
return 'Truck breaking!'
cars = [Truck('Bananatruck'),
Truck('Orangetruck'),
SportsCar('Z3')]
for car in cars:
print(car.name + ': ' + car.drive())
但是,如果你仔细看完上面的代码,似乎你觉得并没有什么特殊之处。因为,Python 没有覆写(override
)的概念,也就是说严格来讲,Python 并不支持多态。相反,个人觉得,上述代码表现的行为,用带太语言编程里的鸭子类型也许更合适。
在动态语言中经常提到鸭子类型,所谓鸭子类型就是:If it walks like a duck and quacks like a duck, it must be a duck。鸭子类型是编程语言中动态类型语言中的一种设计风格,一个对象的特征不是由父类决定,而是通过对象的方法决定的。
特殊方法&属性
python类中有很多特殊方法、属性或则约定,熟悉这些特性,能让你编写更加健壮的代码,主要如下:
- __XXX
该类变量是一个私有变量(private),只有内部可以访问,外部不能访问。
- _XXX_
该类变量是特殊变量,特殊变量是可以直接访问的,不是private。
变量。
- _XXX
该类变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思
就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
XXX_
定义的一个变量和某个保留关键字冲突,这时候可以使用单下划线作为后缀, 以示区分。
特殊属性
# 用类名和对象名效果会不同,请自己尝试
MasterStudent(obj).__doc__ # 类注释
MasterStudent(obj).__name__ # 类名(可能带命名空间)
MasterStudent(obj).__bases__ # 直接父类
MasterStudent(obj).__dict__ # 类信息,字典类型
MasterStudent(obj).__class__ # 一般实例调用__class__属性时会指向该实例对应的类,然后可以再去调用其它类属性(注意是类属性,不是实例属性)
MasterStudent(obj).__module__ # 模块名
MasterStudent(obj).__mro__ # 多重继承init时的解析顺序,(<class '__main__.MasterStudent'>, <class '__main__.Student'>, <class 'object'>)
MasterStudent(obj).__qualname__ # qualified refernece name,见PEP3155
MasterStudent(obj).__slots__ # 见下面解释
MasterStudent(obj).method.__annotations__ # 注解,见下面解释
-
__slot__
用于限制类中的属性(只能是这些属性),使用
__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。如果父类与子类中都定义了__slot__
,则结果为父类与子类__slot__
的合集。
import enforce
class Student(object):
__slots__ = ('name', 'score')
@enforce.runtime_validation
def __init__(self, name: str): # annotations
self.name = name
-
__annotations__
__annotations__
是python3引入的类型检查机制,是一种推荐性写法,如果要强制runtime进行类型检查,可以使用enforce
包,并用装饰器@enforce.runtime_validation
装饰相应的方法。查看annotations使用MasterStudent.__init__.__annotations__
魔法方法
-
__init__(),__del__(),__new__()
这个方法是一个类的构造函数,与之对应的
__del__()
是析构函数,通过此方法我们可以定义一个对象的初始操作。但实际上,新式类的__new__()
才是真正的初始化函数。首先,我们验证一下这三个方法的执行顺序:
class A(object): def __init__(self): print('__init__') def __new__(cls, *args, **kwargs): print('__new__') return super().__new__(cls, *args, **kwargs) # cls表示一个类,一个当前要被实例化的类,参数由py解释器自动提供 def __del__(self): print('__del__') a = A() print('do something') # __new__ # __init__ # do something # __del__ # 实际上,__new__()负责创建一个对象,__init__负责定制化该对象,即是在对象创建好之后初始化变量
既然知道了
__new__()
方法,我们是不是可以考虑一下,如何应用它呢?最常见的就是单例模式了,下面给出实现实例。class Singleton(object): _instance = None def __new__(cls, *args, **kwargs): """ 注意这实际上是一个类方法, cls 表示当前类 :param args: :param kwargs: :return: """ if cls._instance is None: cls._instance = super().__new__(cls, *args, **kwargs) return cls._instance s1 = Singleton() s2 = Singleton() if s1 is s2: print('yeah')
-
__repr__(),__str__()
分别为
str()、repr()
函数提供接口,打印的更好看。class Pair: def __init__(self, x, y): self.x = x self.y = y def __repr__(self): return 'Pair({0.x!r}, {0.y!r})'.format(self) def __str__(self): return '({0.x!s}, {0.y!s})'.format(self) def __eq__(self, other): if self.x == other.x and self.y == other.y: return True else: return False p = Pair(3, 4) pp = eval(repr(p)) if pp == p: print('true')
-
__getitem__(),__setitem__(),__delitem__()
__getitem__()
将对象当作list使用,如obj = ACLASS(), obj_first=obj[0]class A(object): def __init__(self): self['B'] = "BB" self['D'] = "DD" self.jj = 'jj' del self['D'] def __setitem__(self, name, value): """ 每当属性被赋值的时候都会调用该方法 """ print("__setitem__:Set %s Value %s" % (name, value)) self.__dict__[name] = value def __getitem__(self, name): """ 当访问属性时会调用该方法 """ print("__getitem__:Try to get attribute named '%s'" % name) if hasattr(self, name): return getattr(self, name) return None def __delitem__(self, name): """ 当删除属性时调用该方法 """ print("__delitem__:Delect attribute '%s'" % name) del self.__dict__[name] print(self.__dict__) if __name__ == "__main__": X = A() b = X['jj'] print(b)
-
__getattr__(),__setattr__(),__delattr__()
为
getattr(),del obj.attr,setattr(),hasattr()
提供接口,这是魔法方法中的一个难点,并且发现不少其他博客居然讲错了,但也许是py3和py2的差别,请参考官方文档-py3属性设置。需要铭记实例对象属性寻找的顺序如下:- 首先访问
__getattribute__()
魔法方法(隐含默认调用,无论何种情况,均会调用此方法 - 去实例对象t中查找是否具备该属性:
t.__dict__
中查找,每个类和实例对象都有一个__dict__
的属性 - 若在
t.__dict__
中找不到对应的属性, 则去该实例的类中寻找,即t.__class__.__dict__
- 若在实例的类中也招不到该属性,则去父类中寻找,即
t.__class__.__bases__.__dict__
中寻找 - 若以上均无法找到,则会调用
__getattr__
方法,执行内部的命令(若未重载__getattr__
方法,则直接报错:AttributeError
)
#! /usr/bin/python # _*_ coding: utf-8 _*_ class Test(object): def __init__(self, count): self.count = count def __getattr__(self, name): print('__getattr__') raise AttributeError('no such field') def __getattribute__(self, item): """ 一旦重载了 __getattribute__() 方法, 如果找不到属性, 则必须要手动加入第④步, 否则无法进入到 第⑤步 (__getattr__)的 :param item: :return: """ print('__getattribute__') return super().__getattribute__(item) def __setattr__(self, name, value): print('__setattr__') super().__setattr__(name, value) def __delattr__(self, name): print('__delattr__') super().__delattr__(name) # 希望读者好好体会下面测试代码的输出,并思考为什么 t = Test(3) print(t.count) print('*'*40) t.x = 10 # __setattr__ a = t.x # __getattribute__ print(a) # 10 print('**', t.__dict__) # __getattribute__ # ** {'x': 10} if 'x' in t.__class__.__bases__[0].__dict__: print('** x in base class dict') else: print('** x not in base class dict') # __getattribute__ # ** x not in base class dict del t.x # __delattr__ a = t.x # __getattribute__ # __getattr__
summary:
__getattr__(self, name)
: 访问不存在的属性时调用__getattribute__(self, name)
:访问存在的属性时调用(先调用该方法,查看是否存在该属性,若不存在,接着去调用①)__setattr__(self, name, value)
:设置实例对象的一个新的属性时调用__delattr__(self, name)
:删除一个实例对象的属性时调用 - 首先访问
-
__gt__(),__lt__(),__eq__(),__ne__(),__ge__()
定义对象比较方法,为关系运算符>,>=,<,==等提供接口,这就类似于C++里面的运算符重载。
class SavingsAccount(object): def __init__(self, name, pin, balance=0.0): self._name = name self._pin = pin self._balance = balance def __lt__(self, other): return self._balance < other._balance s1 = SavingsAccount("Ken", "1000", 0) s2 = SavingsAccount("Bill", "1001", 30) print(s1 < s2)
其实,类似的数值运算的魔法方法非常非常之多,好吧,我真的用了两个,现在是三个非常来形容,以下列出部分,希望读者根据上面的例子举一反三:
class Point(object): def __init__(self): self.x = -1 self.y = 5 # 正负,取反、绝对值 def __pos__(self): pass def __neg__(self): pass def __invert__(self): pass def __abs__(self): pass # 加减乘除、取余,指数 def __add__(self, other): pass def __sub__(self, other): pass def __divmod__(self, other): pass def __mul__(self, other): pass def __mod__(self, other): pass def __pow__(self, power, modulo=None): pass # 逻辑运算符 def __and__(self, other): pass def __or__(self, other): pass def __xor__(self, other): pass # 位移 def __lshift__(self, other): pass def __rshift__(self, other): pass # 赋值语句 def __iadd__(self, other): pass def __imul__(self, other): pass def __isub__(self, other): pass def __idiv__(self, other): pass def __imod__(self, other): pass def __ipow__(self, other): pass def __ilshift__(self, other): pass def __irshift__(self, other): pass
-
__get__(),__set__(),__delete__()
实现了这些方法的类,称之为描述器类,这里不展开讲,仅给出一个实例
class Integer: """ 资料描述器和非资料描述器会产生不一样的优先级, """ def __init__(self, name): self.name = name def __get__(self, instance, cls): if instance is None: return self else: return instance.__dict__[self.name] def __set__(self, instance, value): if not isinstance(value, int): raise TypeError('Expected an int') instance.__dict__[self.name] = value def __delete__(self, instance): del instance.__dict__[self.name] class Point: x = Integer('x') y = Integer('y') def __init__(self, x, y): """ 特别注意因为Interger是资料描述器,所以self.x 其实指的就是x = Integer('x') """ self.x = x self.y = y p = Point(10, 11)
-
__iter__()
为for … in提供接口,返回一个迭代对象,并调用对象next()方法,直到StopIteration。以下这个例子来源廖雪峰python教程
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 > 10: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值 for i in Fib(): print(i)
-
__dir__()
为dir()函数提供接口,查看一个对象中有哪些属性和方法, 返回值应该是
iterable。
【关于Iterable放在后面讨论】,如返回一个list
,一般而言我们不去重写这个方法。首先我们看看dir函数:
>>> res = dir(str(123)) >>> print(type(res)) <class 'list'> >>> print(res) ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
我们自定义一个
__dir__()
方法试试:class A(object): def __dir__(self): print('不给你看这个函数有什么属性和方法,哼、、哼哼') return [] if __name__ == "__main__": a = A() result = dir(a) print(result) # 不给你看这个函数有什么属性和方法,哼、、哼哼 # <class 'list'>
-
__call__()
假设有对象obj, 则
obj()
可以调用__call__()
方法class A(object): def _pre_process(self): print('preoprocess data') def __call__(self, *args, **kwargs): self._pre_process() print('object called over') A()() # 调用了类中__call__()中的逻辑
-
数据类型转换
# 类似数值计算的魔法方法,实现以下方法就可以实现自定义的数据类型转换 class A(object): def __init__(self, num): self.x = num def __int__(self): pass def __float__(self): pass def __long__(self): pass def __complex__(self): pass def __bytes__(self): pass def __oct__(self): pass def __hex__(self): pass def __str__(self): pass def __nonzero__(self): """ 这里仅仅实现bool转换,其他的读者可举一反三 """ if self.x != 0: return True else: return False if __name__ == '__main__': print(bool(A(5)))
-
__enter__(),__exit__()
这两个魔法方法使对象是用于With会话管理,一般用于需要打开关闭的场景,如文件读写、数据库连接。
#! /usr/bin/python # _*_ coding: utf-8 _*_ class OpenTextFile(object): """ 实现打开一个文件使用with,这里仅仅是个例子 因为 open()本身就可以实现 with context """ def __init__(self, file_path): self.file = file_path def __enter__(self): self.f = open(self.file, 'r', encoding='utf-8') return self.f def __exit__(self, exc_type, exc_val, exc_tb): self.f.close() with OpenTextFile('./with_context.py') as f: print(f.readlines())
-
其他魔法方法
# 获取对象大小 __sizeof__() # 获取对象哈希值 __hash__() # 用于对象序列化和反序列化 __reduce__() __reduce_ex__() __instancecheck__(self, instance) # 检查一个实例是不是你定义的类的实例 __subclasscheck__(self, subclass) # 检查一个类是不是你定义的类的子类 __contains__(self,value) # 使用in操作测试关系时 __concat__(self,value) # 连接两个对象时 __index__(self) # 对象被当作list索引时
辅助知识
OOP实用函数
type()
查看对象类型issubclass()
查看一个对象是否属于某个类的子类is
vs==
vsisinstance
前者查看两个对象是否严格相同,后者查看某对象是否属于某类id
用于获取对象的内存地址
迭代器生成器
- 列表生成式
List Comprehensions, 是python内置的用来生成list的一种快捷高效的方式。
# 举例
[x * x for x in range(1, 11)]
[x * x for x in range(1, 11) if x % 2 == 0]
- 生成器
Generator,是列表生成式的一种优化方案,列表生成式的list会直接放在内存中,因此其大小必然会受到内存的限制;而生成器就是为了解决这种资源耗费的情况,能够做到先定义,边循环边计算。
# 注意区分生成器和列表生成式的定义方式
# 生成器是用()、列表生成式是用[]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x0000020A6184D0A0>
# 如果要一个一个打印出来,可以通过next()或则__next__()获得generator的下一个返回值
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> g.__next__()
16
# 在实际编程中,我们一般这样使用
for n in g:
print(n)
在实际应用中,我们经常会借助yield
关键字构造生成器函数,如斐波那契函数:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
>>> g = fib(6)
>>> g
<generator object fib at 0x0000020A619C6BF8>
>>> next(g)
1
>>> next(g)
1
>>> next(g)
2
>>> next(g)
3
>>> next(g)
5
>>> next(g)
8
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
- 迭代器
可以直接作用于for
循环的对象统称为可迭代对象(Iterable
),包括集合数据类型(如list
、tuple
、dict
、set
、str
)和 generator
(生成器、带yield
的generator function)。但是集合数据类型和generator有一个很大的区别:generator可以使用next()
不断调用,直至StopIteration
。在python中,可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
Tips: 如果一个对象是 Iterator(迭代器), 那么它必然是一个Iterable(可迭代对象);而Iterable不一定是一个Iterator,但是Iterable可以通过iter()函数变成Iterator;特殊的是generator既是Iterable又是Iterator。
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance('abc', Iterator)
False
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True