Python 和 JavaScript一样即是面向过程语言,也是面向对象语言,动态语言。大多数面向对象语言里,Class是必不可少的。面向对象有三大特性:封装, 继承,多态。在Python中Class到底是怎样的呢?
1、Class组成
先来看一个示例:
class Person(object): id='' name = '' age = 3 # 等同于Java中的<init>,即构造器 def __init__(self, id, name, age): print("init a Person instance") self.id = id self.name = name self.age = age def show(this): print(this) # print(this.toString()) #def toString(self): # return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同于Java中的toString def __str__(self): # return self.toString() return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同于Java中的finalize方法,del 实例时调用 def __del__(self): print("finally a Person instance") self.id = None self.name = None self.age = None self = None
下面是以对比Java的方式,来说明Python中的类:
1)Class中,包括属性、方法,它们都是public的。在Python的Class中,是不存在private,protected等修饰符的。
2)__init__是构造函数,调用构造器时,会自动调用__init__。它相当于Java中的<init>。在创建一个Python对象时,不需要像Java那样使用new。
3)__del__是析构函数,当del instance时,会自动调用__del__。它相当于Java中的finalize
4)需要获取对象的字符串表示时,会调用__str__。它就相当于Java中的toString。
5)类的方法的第一个参数,都是self,相当于Java中的this。其实Java中的实例方法的第一个参数也是this,只是被隐藏了而已,如果你了解JVM 运行时的话,或者使用过 javassist或者asm等字节码工具的话,会知道的。上面的示例中的p1.show()运行时,可以理解为执行的是Person.show(p1)。 并不是说必须得写成self,你也可以写完this或者其他任何的满足变量命名的形式。但通常大家都约定俗称的写为self,保持编码风格的统一,有利于方便他人理解代码。
6)__init__,__del__,__str__不是必须的。
7)所有属性都要有初始值
2、Class getter,setter
Java,JavaScript ES6都支持setter,getter。Python中也是支持的。
class Person(object): id='' name = '' age = 3 # 等同于Java中的<init>,即构造器 def __init__(self, id, name, age): print("init a Person instance") self.id = id self.name = name self.age = age def show(this): print(this) # print(this.toString()) #def toString(self): # return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同于Java中的toString def __str__(self): # return self.toString() return "id:{}, name:{}, age:{}".format(self.id, self.name, self.age) # 等同于Java中的finalize方法,del 实例时调用 def __del__(self): print("finally a Person instance") self.id = None self.name = None self.age = None self = None def __get__(self, name): print("invoke in __get__") print(self.__dict__) return 1111 def __getattr__(self, name): print("invoke in __getattr__") return 1111 def __getattribute__(self, name): print("invoke in __getattribute__") print(object.__getattribute__(self, name)) print("after invoke in __getattribute__") return object.__getattribute__(self, name)
对于旧式类,访问属性的顺序是:
1)直接访问属性
2)访问__getattr__
对于新式类,访问属性的顺序是:
1)__getattribute
2)直接访问属性
3)__getattr__
切记,如果写了__getattribute__,最后一句话必须是object.__getattribute(self,name) 否则就会出现:’XxxType’ object is not callable 。它的存在,更像是作为拦截器使用。
3、Class继承
C++是多继承的,Java是单继承的,Python借鉴了C++的多继承方式。
在继承结构下,访问属性,方法时,与Java中一样的,先自己的,自己没有时,才去从父类找。
由于支持多继承,所以当一个属性、方法,在多个父类中都存在时,会访问到哪个呢?
要解答这个问题,就得先知道搜索属性、方法的顺序。采用的搜索算法是深度优先搜索算法。
也就是一个class A(S1,S2,S3):pass; 要调用一个属性时,会先从A里找,找不到再从S1,如果还找不到再从S2,依次类推。
那么在多继承情况下,如果A,S1,S2,S3都重写了__getattr__方法,那会有什么区别呢?查找顺序大体不变的:
1) A的直接属性,找不到然后是A.__getattr__
2) S1的直接属性,找不到然后是S1.__getattr__
3) S2的直接属性,找不到然后是S2.__getattr__
4) S3的直接属性,找不到然后是S3.__getattr__
对于构造函数__init__,在构造实例时,不会像Java那样,先去调用父类的__init__。
class Student(Person): def __init__(self, id, name, age,email): print("invoke in Student __init__") super(Student, self).__init__(id, name, age) self.email = email
Java中有super(),Python中是否有呢?能否利用super呢?如果不能自动调用父类的构造器,那么在重写__init__又得给所有的属性都分配一下,这个好麻烦的,该怎么办呢?只能以编程的方式自己调用了。
super(类,instance),这个函数的作用是找到指定的类的下一个父类的指定方法,将该方法在指定的实例上调用。
例如上面的例子中,继承关系是这样的:Student > Person > object。self是Student对象,super(Student, self).__init__(id, name, age)该语句的意思就是:找到Student的下一个父类(即Person)的__init__方法,然后在self上调用该__init__方法。
需要注意的是:
1)super函数的两个参数,不能有误。需要满足两个条件,第二个instance应该是第一个参数所代表的类的实例(或者是其子类的实例)。
2)Super只能在新式类中使用。
4、运算符重写
在.Net和Scala编程语言中,都是支持运算符重写的。如果没有接触过这些东西,可能会费解的。其实只要把运算符看着是方法就可以了。从这个角度来理解的话,Java也是支持运算符重写的,只不过呢,该特性并没有暴露给用户罢了。例如字符串拼接 + ,它其实是调用的StringBuilder.append方法,遍历集合的foreach,它其实调用的是iterator。既然提到了运算符重写,那么Python中也必然是支持的,并且它把这个特性暴露出来了。
Python String 就重写了几个运算符:
+ 字符串拼接 * copy多份 == 比较字符串内容 > 字符串比较 < 字符串比较
那么怎样实现运算符重写呢?
上面已经说明,将运算符看作是一个方法。那么重写运算符,就是就是重写方法了。
下面列出了常见的运算符重载:
重载方法 |
说明 |
调用 |
__init__ |
构造器 |
对象建立,X=Class() |
__del__ |
析构方法 |
对象回收,或者del X |
__add__ |
运算符 + |
X+Y, X+=Y |
__iadd__ |
增强的 + |
X+Y, X+=Y |
__radd__ |
左侧+ |
Noninstance + Y |
__or__ |
运算符 | (位 OR) |
X|Y, X|=Y |
__repr__,__str__ |
打印,转换 |
print(X), repr(X),str(X) |
__getattr__ |
点号运算符 |
获取属性 |
__setattr__ |
赋值运算符 |
设置属性 |
__call__ |
函数调用 |
Y() |
__len__ |
长度 |
len(X) |
__cmp__, __lt__, __eq__ |
比较 |
X < Y X==Y |
__getitem__ |
索引运算符 |
X[name] |
__setitem__ |
索引赋值 |
X[Y]=Z |
__iter__ |
迭代 |
循环,迭代等 |
使用__getitem__可以使得对象具备索引的方式来访问?
测试如下:对上面的Student如下:
class Student(Person): def __init__(self, id, name, age,email): print("invoke in Student __init__") super(Student, self).__init__(id, name, age) self.email = email def __getitem__(self, name): return self.__dict__[name] + "_suffix"
执行测试:
import model Student = model.Student s = Student('0001', 'fjn', 20, 'fs1194361820@163.com') s.show() print(s["email"])
发现结果是:fs1194361820@163.com_suffix
5、模拟私有属性
通过上面的学习,了解到Python的属性、方法都是public, 这点和JavaScript太 一样了。写Java时间久了,私有属性的好处,怎么可以浪费了呢。那么如何模拟出私有的?
有两种方式可以模拟的,都与JavaScript里的理念是类似的。
方式一:使用对象时,直接加属性。
方式二:利用对象的__dict__
6、Static Method
在java中,会将实例方法与类方法区分,具体做法是在 class 方法上加上static修饰符。在python中是没有static的,那么如何实现static方法。
前面也讲了,实例方法声明时,第一个参数是self。在python中,实例方法本质就是调用类方法的。
p1=Person(‘a’,’b’,23)
Person.show(p1)
在方法前加上注解:@staticmethod