Python面对对象相关知识总结
很有一段时间没使用python了,前两天研究微信公众号使用了下python的django服务,感觉好多知识都遗忘了,毕竟之前没有深入的实践,长期不使用就忘得快。本博的主要目的就是对Python中我认为重要的面对对象知识进行总结,一是加深记忆,而是方便日后遗忘了好迅速的捡起来。
目录索引:
(1)隐式的基类object
(2)python中类和对象的区别
(3)实现python风格类所用特殊方法
(a)__all__和__slots__
(b)__format__()、__repr__()和__str__()
(c)__init__()方法
(d)__new__()和__del__()
(e)__call__()方法
(f)属性访问、特性和修饰符
(g)@classmethod和@staticmethod
(h)关于私有属性的说法
(4)抽象基类和继承
1. 隐式的基类object
每个Python类的定义都回隐式继承自object类,它的定义非常简单,几乎什么行为都不包括。
可以看到类定义就是对type类的一个对象的类型声明,基类为object,相应的派生自object类中的对象方法也将继承各自相应的默认实现,在某些情况下,基类中一些特殊方法的默认行为也正是我们想要的,对于一些特殊情况,就需要重写这些方法。
Python2 没有默认继承object
Python3 默认全部继承object类,都是新式类
2. Python中类和对象的区别
(1)类属性和对象属性的区别
对象可以通过 对象名.属性名 调用对象属性和类属性
类可以通过 类名.属性名 调用类的属性,但是不能调用对象的属性
class People(object): # 类属性是指定义在类的内部而且在方法的外部的属性 money = 10000 def __init__(self,name,age,gender=1): # 对象属性是指定义在方法的内部的属性,例如本例中 # name,age和gender都是对象属性 self.name = name self.age = age self.gender = gender student1 = People("张三",20) student2 = People("李四",25) print(student2.name) print(student2.money) print(People.money) print(People.name)
再注意看下面代码输出结果:
print(id(student1.money))
print(id(student2.money))
print(id(People.money))
说明:对象student1、student2和类People的属性money的内存地址都是相同的
继续往下看
student1.money -= 1000
print("student1.money:", student1.money)
print("student1.money id:",id(student1.money))
print("student2.money:", student2.money)
print("student2.money id:",id(student2.money))
print("People.money id:",id(People.money))
说明:student1引用的money属性的内存地址已经和另外两个的不一样了而另外两个的内存地址却还是一样的
原因:在经过表达式student1.money -= 1000 的过程时,会先在对象中查找是否有money这个属性,如果有的话,则直接进行运算如果没有,则会去类中查找是否有money属性,如果在类中找到money属性,那么student1就会创建一个对象属性money,在第二次调用的时候就会调用自己的对象属性,而不是类People中的属性了,而student2因为没有经过运算,所以不会创建自己的money属性,而是引用类People的属性,所以student2和People引用的还是同一个属性
3. 实现python风格类所用特殊方法
(a)__all__和__slots__
关于__all__ = []总结两点:
A:在__init__.py文件中
表示形式: __all__=["module_a","module_b"] 在使用 from package_name import * 时 , 表示import 该package 中的 两个module及 两个module相关的类、方法等。
B:在普通的*.py中
表示形式: __all__=["class_name","function_name"] 在使用 from module_name import * 时,表示import 该module中的__all__中所列出的。
__slots__使用:
如果我们想限制一个类的属性怎么办?比如只允许对Student实例添加name和age属性,为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量来限制class能添加的属性
>>> class Student(object): ... __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称 ... >>> s = Student() # 创建新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # 绑定属性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
由于score没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError错误
注意:__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的
>>> class GraduateStudent(Student): ... pass ... >>> g = GraduateStudent() >>> g.score = 9999
除非在子类中也定义__slots__,这样子类允许定义的属性就是自身的__slots__加上父类的__slots__
(b)__format__()、__repr__()和__str__()
上面的三个方法都是为了为Python对象提供一个很好的字符串表示
通常str()方法表示的对象对用户更加友好,这个方法由__str__()实现
repr()方法的表示通常会被更加技术化,这个方法由__repr__()实现
{!r} 调用__repr__方法
{!s} 调用__str__方法
string.format()和内置的format()函数都使用__format()__方法,都是为了获得给定对象的一个符合要求的字符串表示
直接上例子能更好的进行说明:
class People: def __init__(self,name,age,sex): self.name = name self.age = age self.sex = sex def __str__(self): #return "{__class__.__name__}(name={0},age={1},sex={2})" .format(self.name,self.age,self.sex,__class__=self.__class__) return "__str__:(name={name},age={age},sex={sex})" .format(__class__=self.__class__,**self.__dict__) def __repr__(self): return "__repr__:{__class__.__name__}(name={name},age={age},sex={sex})" .format(__class__=self.__class__,**self.__dict__) def __format__(self, format_spec): if format_spec == "": return str(self) return format_spec.replace("%n",self.name).replace("%s",self.sex) if __name__ == "__main__": people = People("john",18,"man") print("{0!s}".format(people)) print("{0!r}".format(people)) print("format:",format(people,"%n-%s")) print("__format__:",people.__format__("%n-%s")) print("format:",format(people))
(c)__init__()方法
__init__(self)就如同类的构造函数,尽量在构造函数中实现属性清单,这里就不多做介绍
下面主要总结下Python继承中super()方法的使用:
super实现原理:通过c3算法,生成mro(method resolution order)列表,根据列表中元素顺序查询调用新式类调用顺序为广度优先,旧式类为深度优先
super类似于嵌套的一种设计,当代码执行到super实例化后,先去找同级父类,若没有其余父类,再执行自身父类,再往下走,简单说就是子类在父类前,所有类不重复调用,从左到右(见下面例子的D.mro()打印)
Python2 super调用 super(开始类名,self).函数名()
Python3 super().函数名()
上例子更能说明上述问题:
class A(): def go(self): print ("go A go!") def stop(self): print ("stop A stop!") def pause(self): raise Exception("Not Implemented") class B(A): def go(self): super(B, self).go() print ("go B go!") class C(A): def go(self): super(C, self).go() print ("go C go!") def stop(self): super(C, self).stop() print ("stop C stop!") class D(B,C): def go(self): super(D, self).go() print ("go D go!") def stop(self): super(D, self).stop() print ("stop D stop!") def pause(self): print ("wait D wait!") class E(B,C): pass a = A() b = B() c = C() d = D() e = E() # 说明下列代码的输出结果 d.go() print('--------') e.go() print('--------') d.stop() print('--------') e.stop() print(D.mro()) a.pause() b.pause() c.pause() d.pause() e.pause()
(d)__new__()和__del__()
__ new__ ()在__ init__()之前被调用,用于生成实例对象.利用这个方法和类属性的特性可以实现设计模式中的单例模式.单例模式是指创建唯一对象吗,单例模式设计的类只能实例化一个对象.
class Singleton(object): __instance = None # 定义实例 def __init__(self): pass def __new__(cls, *args, **kwd): # 在__init__之前调用 if Singleton.__instance is None: # 生成唯一实例 Singleton.__instance = object.__new__(cls, *args, **kwd) return Singleton.__instance
__new__()方法的使用和Python元编程相关,可以查看我之前的博客:Python元编程
__del__()方法涉及到Python对象销毁,Python文档中用不稳定性来描述__del__()方法的这种行为,并且提供了额外的关于异常处理的注释,总之,该函数的使用要慎重之慎重。
__ del__称作析构方法
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为在Python中,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。在程序执行结束之后,执行此方法
(e)__call__()方法
Python可调用对象:函数,方法,使用 yield 关键字的函数或方法,类的实例
__call__方法将类的实例变成可调用对象
>>>class Reader(): def __init__(self,name,nationality): self.name = name self.nationality = nationality def __call__(self): print('Reader: %s Nationality: %s' % (self.name, self.nationality)) >>>r = Reader('Annie','Chinese') >>>r() Reader:Annie Nationality: Chinese
(f)属性访问、特性和修饰符
之前的博客Python之属性、特性和修饰符作了详细说明
(g)@classmethod和@staticmethod
classmethod 修饰符对应的函数不需要实例化,不需要 self 参数,但第一个参数需要是表示自身类的 cls 参数,可以来调用类的属性,类的方法,实例化对象等。
#!/usr/bin/python # -*- coding: UTF-8 -*- class A(object): bar = 1 def func1(self): print ('foo') @classmethod def func2(cls): print ('func2') print (cls.bar) cls().func1() # 调用 foo 方法 A.func2() # 不需要实例化
staticmethod修饰符实现静态方法,该方法不强制要求传递参数
#!/usr/bin/python # -*- coding: UTF-8 -*- class C(object): @staticmethod def f(): print('runoob'); C.f(); # 静态方法无需实例化 cobj = C() cobj.f() # 也可以实例化后调用
(h)关于私有属性的说法
Python并没有真正的私有化支持,但可用下划线得到伪私有
个人习惯:
(1)_XXX 单下划代表protected
(2)__XXX 双下划线开始的且不以_结尾表示private(下面说明)
(3)__XXX__系统定义的属性和方法
看下面的例子:
class People: __name="zhanglin" def __init__(self): self.__age = 16 print(People.__dict__) p = People() print(p.__dict__)
会发现__name和__age属性名都发生了变化,都变成了(_类名+属性名),只有在__XXX这种命名方式下才会发生变化,所以以这种方式作为伪私有说明
4. 抽象基类和继承
该主题内容请查看之前的博客:Python之抽象基类