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是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时的解析顺序,(, , )
MasterStudent(obj).__qualname__ # 也是类名,不是很清楚
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.__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)
# 不给你看这个函数有什么属性和方法,哼、、哼哼
#
__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索引时
type()
查看对象类型issubclass()
查看一个对象是否属于某个类的子类is
vs ==
vs isinstance
前者查看两个对象是否严格相同,后者查看某对象是否属于某类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 "" , 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