文章目录
写在篇前封装继承单继承Mixin抽象多态特殊方法&属性特殊属性魔法方法辅助知识OOP实用函数迭代器生成器写在篇前
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 += 1self.__id = sidself.name = nameself.score = scoredef 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) # 1print(Student.student_number) # 1lisa = Student('002', 'Lisa Simpson', 87)lisa.print_score()print(bart.student_number) # 2print(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 = majordef 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 = majorif __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):"""交通工具的基类"""passclass FlyMixin(object):"""Mixin 类,告诉其他人,这是一个"接口",能够扩充类的某一个功能"""def fly_able(self):passclass Airliner(Transportation, FlyMixin):passclass 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.abstractmethoddef load(self, _input):pass@abc.abstractmethoddef save(self, output, data):passclass 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.abstractmethoddef say(self):pass@classmethoddef __subclasshook__(cls, _cls):"""该方法意味着只要一个类实现了他的抽象方法,就会被认为是该类的子类"""print('class invoke ********')if cls is C:if any("say" in B.__dict__ for B in _cls.__mro__):return Truereturn NotImplementedclass 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 = namedef 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,见PEP3155MasterStudent(obj).__slots__ # 见下面解释MasterStudent(obj).method.__annotations__ # 注解,见下面解释
__slot__
用于限制类中的属性(只能是这些属性),使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。如果父类与子类中都定义了__slot__
,则结果为父类与子类__slot__
的合集。
import enforceclass Student(object):__slots__ = ('name', 'score')@enforce.runtime_validationdef __init__(self, name: str): # annotationsself.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 = Nonedef __new__(cls, *args, **kwargs):"""注意这实际上是一个类方法, cls 表示当前类:param args::param kwargs::return:"""if cls._instance is None:cls._instance = super().__new__(cls, *args, **kwargs)return cls._instances1 = Singleton()s2 = Singleton()if s1 is s2:print('yeah')
__repr__(),__str__()
分别为str()、repr()
函数提供接口,打印的更好看。
class Pair:def __init__(self, x, y):self.x = xself.y = ydef __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 Trueelse:return Falsep = 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] = valuedef __getitem__(self, name):"""当访问属性时会调用该方法"""print("__getitem__:Try to get attribute named '%s'" % name)if hasattr(self, name):return getattr(self, name)return Nonedef __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 = countdef __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) # 10print('**', 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 dictdel 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 = nameself._pin = pinself._balance = balancedef __lt__(self, other):return self._balance < other._balances1 = SavingsAccount("Ken", "1000", 0)s2 = SavingsAccount("Bill", "1001", 30)print(s1 < s2)
其实,类似的数值运算的魔法方法非常非常之多,好吧,我真的用了两个,现在是三个非常来形容,以下列出部分,希望读者根据上面的例子举一反三:
class Point(object):def __init__(self):self.x = -1self.y = 5# 正负,取反、绝对值def __pos__(self):passdef __neg__(self):passdef __invert__(self):passdef __abs__(self):pass# 加减乘除、取余,指数def __add__(self, other):passdef __sub__(self, other):passdef __divmod__(self, other):passdef __mul__(self, other):passdef __mod__(self, other):passdef __pow__(self, power, modulo=None):pass# 逻辑运算符def __and__(self, other):passdef __or__(self, other):passdef __xor__(self, other):pass# 位移def __lshift__(self, other):passdef __rshift__(self, other):pass# 赋值语句def __iadd__(self, other):passdef __imul__(self, other):passdef __isub__(self, other):passdef __idiv__(self, other):passdef __imod__(self, other):passdef __ipow__(self, other):passdef __ilshift__(self, other):passdef __irshift__(self, other):pass
__get__(),__set__(),__delete__()
实现了这些方法的类,称之为描述器类,这里不展开讲,仅给出一个实例
class Integer:"""资料描述器和非资料描述器会产生不一样的优先级,"""def __init__(self, name):self.name = namedef __get__(self, instance, cls):if instance is None:return selfelse:return instance.__dict__[self.name]def __set__(self, instance, value):if not isinstance(value, int):raise TypeError('Expected an int')instance.__dict__[self.name] = valuedef __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 = xself.y = yp = Point(10, 11)
__iter__()
为for … in提供接口,返回一个迭代对象,并调用对象next()方法,直到StopIteration。以下这个例子来源廖雪峰python教程
class Fib(object):def __init__(self):self.a, self.b = 0, 1 # 初始化两个计数器a,bdef __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 = numdef __int__(self):passdef __float__(self):passdef __long__(self):passdef __complex__(self):passdef __bytes__(self):passdef __oct__(self):passdef __hex__(self):passdef __str__(self):passdef __nonzero__(self):"""这里仅仅实现bool转换,其他的读者可举一反三"""if self.x != 0:return Trueelse:return Falseif __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_pathdef __enter__(self):self.f = open(self.file, 'r', encoding='utf-8')return self.fdef __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, 1while n < max:yield ba, b = b, a + bn = 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
如果觉得《Python面向对象 魔法方法》对你有帮助,请点赞、收藏,并留下你的观点哦!