Python 和 JavaScript一样即是面向过程语言,也是面向对象语言,动态语言。大多数面向对象语言里,Class是必不可少的。面向对象有三大特性:封装, 继承,多态。在Python中Class到底是怎样的呢?
- 1、Class组成
- 2、Class getter, setter
- 3、Class继承
- 4、运算符重写
- 5、模拟私有属性
- 6、static method
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