Python支持面向对象的三大特征:封装,继承和多态。
# 类和对象
类和对象是面向对象中的两个重要概念,可以把对象(object)看成是一个个存在的实体,它是实例(instance),而类是对象的蓝图,模板。
Python中的类名需要是一个合法的标识符,一般习惯是,类名第一个字母大写,类中各成员定义顺序没有影响,各成员之间可以相互调用,类主要包含变量和方法,Python是一门动态语言,类中所包含的类变量可以任何地方增加删除类变量,类方法跟普通函数差不多,但是它的第一个参数被绑定为该对象自身,一般用self指代(也可以自定义成其它名字):
class Test1:
pass
class Test2:
'这是一个测试类'
name="hello"
def GetName(self):
print(self.name)
def __init__(self,initName):
self.name=initName
print("Test2 class is inited")
#test2 = Test2()
test2 = Test2("测试类")
test2.GetName()
help(Test2)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
上面Test2类中的__init__是构造函数,它可以在类实例化时被调用。如果定义了构造函数,默认构造函数将不再起作用。定义一个类后可以重复创建该类对象,同一个类的多个对象具有相同的特征,而类则定义了多个对象的共同特征,它不是一个具体存在的实体,对象是一个具体存在的实体。
从上面使用class来定义类是一种方式,还有一种方式是使用type()函数:
class Test:
pass
test = Test()
print("type of class obj: %s" % type(test))
print("type of class: %s" % type(Test))
def GetName(self):
print("self name:%s" % self.name)
Person = type("Test2",(object,),dict(GetItName=GetName,name="li"))
p = Person()
print("type of type defined class obj: %s" % type(p))
print("type of type defined class: %s" % type(Person))
p.GetItName()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
从上面看出使用type定义的类和使用class定义的类并没有区别,任何一个类可以看成是一个type类对象产生的。
# 动态特性
同其他语言不一样的是,Python是动态语言,程序完全可以在运行时为对象动态增加或删除实例变量,注意动态增加方法,不会将方法的第一个参数绑定到调用者,但是可以使用types模块下的MethodType进行包装,来自动绑定第一个参数。
def getName(self):
print("in getName inner:%s" % self.name)
return
class Test3:
pass
test3 = Test3()
test3.name=["hello","world"]
print(test3.name)
test3.Name=getName
test3.Name(test3)
from types import MethodType
test3.newName=MethodType(getName,test3)
test3.newName()
del test3.name
print(test3.name)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面是通过对象来动态添加方法,只是对当前对象有效,如果希望对该类的所有对象有效,可以通过类来添加方法:
class Person:
def __init__(self,name):
self.name=name
def GetPersonName(self):
print("person's name is:",self.name)
p1 = Person("li")
p2 = Person("wang")
p1.GetName=GetPersonName
p1.GetName(p1)
#p2.GetName()
Person.GetPersonName=GetPersonName
p1.GetPersonName()
p2.GetPersonName()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
注意通过类名来添加方法不用指定第一个参数,它是自动绑定的。但是这给程序带了隐患,定义好的类可能在后面被其他程序修改,这让程序的安全性大打折扣,如果要限制给某个类动态添加的属性和方法,可以通过__slots__属性来指定,但是这个对使用类名添加的不管用,只能限制实例。
class Person:
#name="hi"
__slots__=('GetName','Age','name')
def __init__(self,name):
self.name=name
def GetPersonName(self):
print("person's name is:",self.name)
p1 = Person("li")
p2 = Person("wang")
p1.GetName=GetPersonName
p1.GetName(p1)
Person.GetPersonName=GetPersonName
p1.GetPersonName()
p2.GetPersonName()
class Student(Person):
def __init__(self):
print("student is initing!")
stu1=Student()
stu1.testName="hello"
print(stu1.testName)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
如果在__slots__中添加'dict',那么它就起不到限制作用了:
class Person:
#name="hi"
__slots__=('GetName','Age','name','__dict__')
def __init__(self,name):
self.name=name
p1=Person("li")
p1.TestData=20
print(p1.TestData)
2
3
4
5
6
7
8
9
如果希望创建的一批类中都具有某种特性,这个时候就可以使用metaclass,它可以在创建类时动态修改类的定义。
def GetName(self):
print(self.first,self.last)
class AddNameMetaClass(type):
def __new__(cls,name,bases,attrs):
attrs["GetName"]=GetName
attrs["first"]="hello"
return type.__new__(cls,name,bases,attrs)
class Person(metaclass=AddNameMetaClass):
last="li"
pass
class Student(metaclass=AddNameMetaClass):
last="wang"
pass
p = Person()
stu = Student()
p.GetName()
stu.GetName()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在定义Person和Student类时,metaclass的__new__方法会被调用,来修改这两个类。
通过类名也能调用类内方法,但是Python不会自动为方法第一个参数self绑定参数值,需要显式地为第一个参数传入值,注意这个传入值并不要求一定是该类的对象。
class Tiger:
name="TigerTiger"
def getName(self):
print(self.__class__)
tiger=Tiger()
tiger.getName()
Tiger.getName(tiger)
Tiger.getName("hello")
2
3
4
5
6
7
8
9
10
11
# 类方法和静态方法
Python支持定义类方法,该方法会自动将第一个参数(建议为cls)绑定到类本身,但是静态方法并不自动绑定第一个参数:
class Person:
name="temp"
def __init__(self,n):
self.name=n
@classmethod
def walk(cls):
print("the person is walking!",cls.name,cls)
def walk2(self):
print("the person is walking in another method!",self.name,self)
@staticmethod
def getName(p):
print("the name is:",p.name)
p = Person("li")
Person.walk()
Person.getName(p)
p.walk()
p.walk2()
p.getName(p)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
注意类方法和普通方法的区别,也就是上面walk和walk2的区别,类方法第一个参数绑定的是类本身,而普通方法第一个参数绑定的是调用该方法的对象。
# 函数装饰器
上面的@classmethod,@staticmethod是函数装饰器,其中classmethod和staticmethod都是Python内置的函数,除此之外,我们可以定义自己的函数装饰器:
def decorateFunc(fn):
print(fn)
fn()
return "world"
@decorateFunc
def normalFunc():
print("hello")
return "hehe"
print(normalFunc)
2
3
4
5
6
7
8
9
10
11
使用修饰函数的运行过程会是将被修饰函数作为参数传入修饰函数,被修饰函数被替换成修饰函数的返回结果,看一个更复杂的:
def decorateFunc(fn):
def result(*args):
print("----in result function----")
fn(*args)
return result
@decorateFunc
def normalFunc(*args):
print("====in normal function====")
print(args)
return "hehe"
print(normalFunc(1,2,3)
2
3
4
5
6
7
8
9
10
11
12
13
使用修饰函数能做很多有用的事,比如日志记录,权限检查,这种在被修饰函数之前,之后添加处理逻辑的方式是AOP(Aspect Orient Programming,面向切面编程)概念。
# 类命名空间
Python中的类更像命名空间,在这个空间中放置可执行代码,在全局作用域中也可以放置执行代码:
class MyClass:
for i in range(10):
print(i)
custom_lambda=lambda p: print("in MyClass lambda:",p)
global_lambda= lambda p: print("in global lambda:",p)
global_lambda("global")
my_class= MyClass()
my_class.custom_lambda()
2
3
4
5
6
7
8
9
10
11
12
# 成员变量
在类中定义的变量属于类变量,在类对象中又重新赋值的变量是实例变量,这点和其他语言中不太一样。来看下类变量和实例变量的操作基本方式:
class TestVar:
age=10
name="liming"
def getInfo(self):
print("in the class:",self.name,self.age)
print("in global,use class to get:",TestVar.name,TestVar.age)
obj1 = TestVar()
print("in global,use obj to get:",obj1.name,obj1.age)
print("-------use class to set the var-------")
TestVar.age=20
TestVar.name="wang"
obj1.getInfo()
print("in global,use class to get:",TestVar.name,TestVar.age)
print("in global,use obj to get:",obj1.name,obj1.age)
print("-------use obj to set the var-------")
obj1.age=30
obj1.name="zheng"
obj1.getInfo()
print("in global,use class to get:",TestVar.name,TestVar.age)
print("in global,use obj to get:",obj1.name,obj1.age)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
从上面的实例可以看出,使用类名来更改类内变量,也会影响由该类产生的对象,但如果只使用对象名来更改类内的变量,是不影响类变量的,这些变量会被重新创建,变为实例变量。这一点跟其他类C语言不太一样,其他语言是静态变量和普通变量,这个依旧有很大区别。
# 属性
这里的属性和其他语言有点类似,这种属性并不真正存储任何状态,它的值是通过指定操作得到的,当程序对这种属性赋值时,值会存储到其他实例变量中,可以为一个属性定义getter,setter访问器来控制输入输出操作,来看下面示例:
class TestProp:
name="not in init"
first=None
last=None
def setName(self,name):
name_split=name.rsplit(" ")
self.name=name
self.first=name_split[0]
self.last=name_split[1]
def getName(self):
print("first:%s,last:%s" % (self.first,self.last))
def delName(self):
print("del name prop!")
del self.name
Name = property(getName,setName,delName,"请输入一个名字吧!")
#Name = property(getName,setName,None,"请输入一个名字吧!")
print(TestProp.Name.__doc__)
help(TestProp.Name)
t1 = TestProp()
t1.Name="li ming"
t1.Name
del t1.Name
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
还可以使用装饰器来声明一个属性:
class TestProp:
name="not in init"
first=None
last=None
@property
def Name(self):
print("first:%s,last:%s" % (self.first,self.last))
@Name.setter
def Name(self,name):
name_split=name.rsplit(" ")
self.name=name
self.first=name_split[0]
self.last=name_split[1]
@Name.deleter
def Name(self):
print("del name prop!")
del self.name
t1 = TestProp()
t1.Name="li ming"
t1.Name
del t1.Name
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
和前面不同的一点是,使用这种方式定义属性的方法名需要和属性名相同。
# 面向对象
面向对象的三大特征是封装,继承和多态,下面就这三种特征来讨论Python的面向对象。
# 封装
封装(Encapsulation)指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对类内部信息的操作和访问。
良好的封装能够实现以下目的:
- 隐藏类的实现细节。
- 通过方法来限制使用者访问数据,避免对属性不合理的访问。
- 对数据进行检查,保证数据的完整性。
- 便于修改,提高可维护性。
要实现良好的封装,需要:
- 将对象的属性和相关变量隐藏起来,不允许外部直接访问。
- 暴露对这些属性或变量进行操作的相关方法。
注意,Python并没有类似其他语言的访问级别修饰符,并不能真正支持隐藏,但在Python中,只要将Python类的成员命名以双下划线开始,Python就能把它们隐藏起来:
class TestObj:
__name="None in init!"
obj1=TestObj()
print(obj1._TestObj__name)
obj1._TestObj__name="hello"
print(obj1._TestObj__name)
print(obj1.__name)
2
3
4
5
6
7
8
9
如果使用__name来访问,就会提示TestObj没有__name属性,但是Python并不是真正的隐藏了该变量,当我们通过上面的方法访问时,依旧能够操作__name变量。下面,我们使用这个特质来进行封装:
class TestObj:
__name="None!"
def getName(self):
return self.__name
def setName(self,name):
self.__name=name
Name = property(getName,setName)
obj1=TestObj()
obj1.Name="hello"
print(obj1.Name)
print(obj1.getName())
print(obj1._TestObj__name)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 继承
继承是子类可以复用父类的属性,字段和方法,它是实现软件复用的重要手段,Python中的继承是多继承,一个子类可以有多个直接父类,在定义子类时,将父类放在后面的括号里。在多继承时,如果父类有相同的方法,那么排名在前父类的方法会覆盖后面的,注意构造函数也遵循这一规则,如果想在子类构造函数中调用父类构造函数,可以使用super()。如果在定义一个Python类时并未显式指定这个类的直接父类,则这个类默认继承object类。
class Person:
def __init__(self):
print("Person init")
def Sleep(self):
print("a person must sleep")
def Move(self):
print("Person walk")
class Child:
def __init__(self):
print("Child init")
def Sleep(self):
print("Child sleep for long time")
def Eat(self):
print("Child eat much")
class Student(Person,Child):
pass
class Student2(Child,Person):
pass
class Student3(Person,Child):
def __init__(self):
print("student3 is constructing!")
super().__init__()
#super(Child).__init__(self)
Child.__init__(self)
print("======student1======")
stu = Student()
stu.Sleep()
print("======student2======")
stu2 = Student2()
stu2.Sleep()
print("======student3======")
stu3 = Student3()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
一个类可以查看它的子类及父类:
class classA:
pass
class classB:
pass
class classC(classA):
pass
class classD(classA,classB):
pass
print("classA 的子类 " , classA.__subclasses__())
print("classD 的父类 " , classD.__bases__)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 重写
子类会继承父类的方法,当子类包含同父类一样的方法名,这种现象被称为重写:
class Person:
sleepTime=10
def __init__(self):
print("Person init")
def Sleep(self):
print("a person sleep time %s" % self.sleepTime)
def Move(self):
print("Person walk")
class Student(Person):
def __init__(self):
print("Student init")
def Sleep(self):
print("a student sleep time %s" % self.sleepTime)
def Awake(self):
self.sleepTime+=30
self.Sleep()
Person.Sleep(self)
pass
stu = Student()
stu.Awake()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 多态
多态是面向对象的三大特征中的最后一个,当一个变量根据所引用不同的对象来表现不同的行为,这就是所谓的多态(Polymorphism),在其他语言中一般由其他基类引用来指向子类对象实例,然后调用方法,但是在Python这门弱语言中,多是一个变量在指向不同的对象时调用同样的方法,来看实例:
class Animal:
def Run(self):
print("This is a animal running!")
class Tiger(Animal):
def Run(self):
print("This is a tiger running!")
class Cat(Animal):
def Run(self):
print("This is a cat running!")
class Dog:
def Run(self):
print("This is a dog running!")
def AnimalRun(anim):
anim.Run()
AnimalRun(Animal())
AnimalRun(Tiger())
AnimalRun(Cat())
AnimalRun(Dog())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
当一个引用引用不同对象时,如何知道在某个时刻,该引用引用的是哪个对象呢?这个时候就需要用下面两个方法:
- issubclass(cls,classOrTuple):检查cls是否为后一个类,或元组包含多个类中任意类的子类。
- isinstance(obj,classOrTuple):检查obj是否为后一个类,或元组包含多个类中任意类的对象。
str1 = "hi"
print("str1 是否是str类的实例 :%s" % isinstance(str1,str))
print("str1 是否是object类的实例 :%s" % isinstance(str1,object))
print("str 是否是object类的子类 :%s" % issubclass(str,object))
list1 = [1,2]
print("list1 是否是list类的实例 :%s" % isinstance(list1,list))
print("list1 是否是object类的实例 :%s" % isinstance(list1,object))
print("list1 是否是(list,str)类的实例 :%s" % isinstance(list1,(tuple,str)))
2
3
4
5
6
7
8
9
10
# 枚举类
枚举是一系列有限值得集合。在Python中有两种方式定义枚举:
#方法一:使用enum类来创建
import enum
Color = enum.Enum('ColorEnum',('Red','Yellow','Black'))
print(type(Color))
print("color.red.name:%s,color.red.value:%s" %(Color.Red.name,Color.Red.value))
print(Color["Red"])
print(Color(3))
# 遍历一个枚举:
for name,value in Color.__members__.items():
print(name,"=>",value,",",value.value)
#方法二
class MyColor(enum.Enum):
Black='黑'
Red='红'
Yellow='黄'
def GetColor(self):
print('这是代表颜色的枚举',self.value)
print("color.red.name:%s,color.red.value:%s" %(MyColor.Red,MyColor.Red.value))
print(MyColor["Red"])
print(MyColor("红"))
MyColor.Yello.GetColor()
for name,value in MyColor.__members__.items():
print(name,"=>",value,",",value.value)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在使用自定义类来定义枚举时,可以使用构造器来进一步拆分枚举值,存储在不同的属性中:
import enum
class MyColor(enum.Enum):
Black='黑','夜晚的颜色'
Red='红','血液的颜色'
Yellow='黄','路灯颜色之一'
def __init__(self,val,desc):
self._val=val
self._desc=desc
@property
def desc(self):
return self._desc
@property
def val(self):
return self._val
def GetColor(self):
print('这是代表颜色的枚举',self.value)
print(MyColor.Red.name)
print(MyColor.Red.value)
print(MyColor.Red.val)
print(MyColor.Red.desc)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22