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)
1
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()
1
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)
1
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()
1
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)
1
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)
1
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()
1
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")
1
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)
1
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)
1
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)
1
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()
1
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)
1
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
1
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
1
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)指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对类内部信息的操作和访问。

良好的封装能够实现以下目的:

  • 隐藏类的实现细节。
  • 通过方法来限制使用者访问数据,避免对属性不合理的访问。
  • 对数据进行检查,保证数据的完整性。
  • 便于修改,提高可维护性。

要实现良好的封装,需要:

  1. 将对象的属性和相关变量隐藏起来,不允许外部直接访问。
  2. 暴露对这些属性或变量进行操作的相关方法。

注意,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)

1
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)
1
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()
1
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__)

1
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()
1
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())
1
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)))
1
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)

1
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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22