失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Python面向对象 魔法方法

Python面向对象 魔法方法

时间:2023-10-20 14:44:57

相关推荐

Python面向对象 魔法方法

文章目录

写在篇前封装继承单继承Mixin抽象多态特殊方法&属性特殊属性魔法方法辅助知识OOP实用函数迭代器生成器

写在篇前

OOP(Object Oriented Programming),即面向对象的程序设计,不同于传统的面向过程的程序设计,它大大地降低了软件开发的难度,使编程就像搭积木一样简单,是当今编程以及模式设计一股势不可挡的潮流。OOP达到了软件工程的三个主要目标:重用性、灵活性和扩展性。面向对象编程的基础就是类,所谓“类生一,一生二,二生三,三生万物”,类是对现实世界事物的抽象,一个类包括了现实世界中一组对象的方法和属性。

封装

封装就是把数据和操作数据的方法绑定起来,通过已定义的接口实现对数据的访问以及修改,屏蔽繁杂的技术细节。编写一个类就是对数据和数据操作的封装,可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。如下示例代码,即是封装了一个Student类,包含类属性Student_number,对象属性__idnamescore,以及一个打印学生成绩的方法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()查看一个对象是否属于某个类的子类isvs==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),包括集合数据类型(如listtupledictsetstr)和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面向对象 魔法方法》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。