Python 面向对象
1. 面向对象编程介绍
2. 类的定义及 self 的理解
3. “魔法”方法
4. 类属性和实例属性
5. 类方法和静态方法
6. 私有属性、私有化、属性 property
7. 继承
8. 多态
1. 面向对象编程介绍
面向对象和面向过程都是解决问题的一种思路。
- 面向过程:面向过程编程最易被初学者接受,其往往用一长段代码来实现指定功能,开发过程的思路是将数据与函数按照执行的逻辑顺序组织在一起,数据与函数分开考虑。
- 面向对象:将数据与功能(即函数)绑定到一起,进行封装,以增强代码的模块化和重用性,这样能够减少重复代码的编写过程,提高开发效率。
面向对象编程的两个重要概念:类和对象
类
- 类是具有相同属性和行为的事物的统称(或统称为抽象)。
- 类是抽象的,在使用的时候通常会找到这个类的一个具体的存在来使用。
- 一个类可以找到多个对象。
对象
- 某一个具体事物的存在,在现实世界中可以是看得见摸得着的。
- 可以直接使用。
类和对象之间的关系:类就是创建对象的模板。
类也是对象
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在Python中这一点仍然成立,但 Python 中的类还远不止如此,类同样也是一种对象,只要你使用关键字 class,Python 解释器在执行的时候就会在内存中创建一个对象。
这个对象(类对象)拥有创建对象(实例对象)的能力,但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:
- 你可以将它赋值给一个变量。
- 你可以拷贝它。
- 你可以为它增加属性。
- 你可以将它作为函数参数进行传递。
2. 类的定义及 self 的理解
类(Class)由3个部分组成:
- 类的名称:类名
- 类的属性:一组数据
- 类的方法:允许对数据进行操作的方法(行为)
定义类并创建对象
- 定义类时有 2 种方式:新式类和经典类。其区别为:
-
凡是继承了 object 的类都是新式类;没有继承的则是经典类。
-
Python3 中都是新式类(默认继承 object);经典类目前在 Python3 中基本没有应用。
-
Python2 中继承 object 的是新式类;没有父类的是经典类。
-
- 类名的命名规则按照“大驼峰”。
示例:定义一个 Car 类
1 >>> # 定义类 2 >>> class Car: 3 ... # 定义方法 4 ... def move(self): 5 ... print("车在奔跑") 6 ... 7 ... def stop(self): 8 ... print("刹车") 9 ... 10 >>> # 创建一个对象,并用变量 bmw 来保存它的引用 11 >>> bmw = Car() 12 >>> bmw.color = "red" # 给对象添加属性 13 >>> bmw.move() 14 车在奔跑 15 >>> bmw.stop() 16 刹车 17 >>> print(bmw.color) 18 red 19 >>> print(bmw) # 查看对象的内存地址 20 <__main__.Car object at 0x03108358>
- bmw = Car(),这样就产生了一个 Car 的实例对象,它拥有属性(数据)和方法(函数)。
- 第一次使用 bmw.color = "red" 时表示给 bmw 这个对象添加属性,如果后面再出现 bmw.color = ... 时表示对该属性进行修改。
- 当需要创建一个对象时,就是用一个模具,来创造一个实物。
self 理解
- 所谓的 self,可以理解为自己,即当前对象的内存地址。
- 某个对象调用其方法时,python 解释器就会把这个对象作为第一个参数传递给 self,所以开发者只需要传递后面的参数即可。
3. “魔法”方法
在 Python 中,方法名如果是 __xxx__() 的,那么就是有特殊的功能,叫做“魔法”方法。
__init__():构造方法
class 类名: # 初始化方法,用来完成一些默认设定 def __init__(self): pass
- __init__(self) 方法在创建一个对象时默认被调用,不需要手动调用。
- __init__(self) 方法中默认有1个参数名称为 self,如果在创建对象是传递了两个实参,那么在 __init__(self) 中除了 self 作为第一个形参外还需要两个形参,例如 __init__(self, x, y)。
- __init__(self) 中的 self 参数,不需要开发者传递,python 解释器会自动把当前的对象引用传递进去。
示例:
1 class Car: 2 3 # 添加固定值的属性 4 # def __init__(self): 5 # self.name = "bmw" 6 # self.color = "red" 7 8 # 在创建对象时传参作为属性值 9 def __init__(self, name, color): 10 self.name = name 11 self.color = color 12 13 def move(self): 14 print("{}的{}在奔跑".format(self.color, self.name)) 15 16 17 bmw = Car("宝马", "红色") 18 bmw.move() # 红色的宝马在奔跑
__del__():析构方法
- 当创建一个对象时,python 解释器默认调用 __init__() 方法。
- 当删除一个对象时,python 解释器也会默认调用一个方法,即 __del__()。
对象的引用计数
- 当有1个变量保存了对象的引用时,此对象的引用计数就会加1。
- 当使用 del 删除变量指向的对象时,如果对象的引用计数不为1,比如3,那么此时只会让这个引用计数减1,即变成2,当再次调用 del 时,变为1,如果再调用一次 del,此时才是真的把对象进行删除,才会调用 __del__() 方法。
1 class Animal: 2 3 def __init__(self, name): 4 self.name = name 5 6 def __del__(self): 7 print("'{}'对象要被干掉了".format(self.name)) 8 9 10 dog = Animal("哈皮狗") 11 del dog # '哈皮狗'对象要被干掉了 12 13 cat1 = Animal("蓝猫") # 对象引用计数为1 14 cat2 = cat1 # 对象引用计数为2 15 cat3 = cat2 # 对象引用计数为3 16 17 del cat3 # 对象引用计数为3-1=2 18 del cat2 # 对象引用计数为2-1=1 19 del cat1 # '蓝猫'对象要被干掉了
__new__() 方法
示例1:
1 class Animal: 2 3 def __new__(cls, name): # cls后的形参个数需与__init__()的self后的形参个数保持一致 4 print("这是new方法") 5 return object.__new__(cls) # 返回Animal的实例对象 6 7 def __init__(self, name): 8 print("这是init方法") 9 self.name = name 10 11 def __del__(self): 12 print("%s has benn killed" % self.name) 13 # 程序结束时也会自动被执行 14 15 16 a = Animal("dog")
执行结果:
这是new方法
这是init方法
dog has benn killed
示例2:若__new__()方法没有返回值
1 >>> class Animal: 2 ... def __init__(self): 3 ... print("init方法") 4 ... def __new__(cls): 5 ... print("new方法") 6 ... 7 >>> a = Animal() 8 new方法 # 注意,未执行init方法 9 >>> print(a) 10 None # 未创建出实例对象
总结:
- 当创建对象时,会默认先调用 __new__() 方法,再调用 __init__() 方法。
- __new__() 方法是在生成对象之前的动作,至少需要一个参数 cls(代表要实例化的类),此参数在实例化时由 python 解释器自动提供。
- __new__() 方法必须要有返回值,返回实例化出来的实例对象,即实例对象的生成是在__new__()中return所返回的。这点在自己实现 __new__() 方法时要特别注意,可以 return 父类 __new__() 的实例,或者直接是 object 的 __new__() 的实例。
- __init__() 方法有一个参数 self,就是这个 __new__() 返回的实例,即__init__()是在对象生成之后完善对象的属性/功能。__init__() 在 __new__() 的基础上可以完成一些其他初始化的动作,__init__() 不需要返回值。
__str__() 方法
当使用 print() 输出对象的时候,只要自己定义了 __str__() 方法,那么就会打印在这个方法中 return 的数据。
1 class Car: 2 3 def __init__(self, name, color): 4 self.name = name 5 self.color = color 6 7 def __str__(self): 8 msg = "嘿,我的车款是%s,颜色是%s" % (self.name, self.color) 9 return msg 10 11 12 bmw = Car("宝马", "红色") 13 print(bmw) # 嘿,我的车款是宝马,颜色是红色
4. 类属性和实例属性
- 在前面的示例中接触到的就是实例属性(对象属性)。
- 而类属性就是类对象所拥有的属性,它被所有的类对象和实例对象所共有,在内存中只存在一个副本,这个和C++中类的静态成员变量类似。
- 对于公有的类属性,在类外可以通过类对象和实例对象访问。
- 对于实例属性,无法通过类对象访问。
示例:类属性
1 >>> class People: 2 ... name = "Tom" # 类属性 3 ... 4 >>> p = People() 5 >>> print(p.name) # 通过实例对象访问类属性 6 Tom 7 >>> print(People.name) # 通过类对象访问类属性 8 Tom
示例:实例属性
1 >>> class People: 2 ... def __init__(self): 3 ... self.address = "shenzhen" 4 ... 5 >>> p = People() 6 >>> print(p.address) # 只能通过实例对象访问实例属性 7 shenzhen 8 >>> print(People.address) # 无法通过类对象访问实例属性 9 Traceback (most recent call last): 10 File "<stdin>", line 1, in <module> 11 AttributeError: type object 'People' has no attribute 'address'
类属性与实例属性的修改
- 如果需要在类外修改类属性,必须通过类对象去引用然后进行修改。
- 如果通过实例对象去引用,会产生一个同名的实例属性,这种方式修改的是实例属性,不会影响到类属性,并且之后如果通过实例对象去引用该名称的属性,实例属性会强制屏蔽掉类属性,即引用的是实例属性,除非删除了该实例属性。
1 >>> class People: 2 ... name = "Tom" 3 ... 4 >>> p = People() 5 >>> p.name = "Jerry" # 实则修改实例属性 6 >>> print(p.name) # 实例属性屏蔽掉同名的类属性 7 Jerry 8 >>> print(People.name) 9 Tom 10 >>> del p.name # 删除实例属性,即删除 Jerry,恢复 Tom 11 >>> print(p.name) 12 Tom 13 >>> People.name = "Jack" # 在类外,通过类对象修改类属性 14 >>> print(People.name) 15 Jack 16 >>> print(p.name) 17 Jack
5. 类方法和静态方法
类方法
- 类方法是类对象所拥有的方法,需要用修饰器 @classmethod 来标识其为类方法。
- 对于类方法,第一个参数必须是类对象,一般以 cls 作为第一个参数(可以用其他名称,但业内习惯以“cls”命名)。
- 类方法能够通过实例对象和类对象去访问。
1 >>> class People: 2 ... country = "China" 3 ... @classmethod 4 ... def getCountry(cls): 5 ... return cls.country 6 ... 7 >>> p = People() 8 >>> p.getCountry() # 通过实例对象访问类方法 9 'China' 10 >>> People.getCountry() # 通过类对象访问类方法 11 'China'
静态方法
需要通过修饰器 @staticmethod 进行修饰,静态方法可以不定义形参。
当既不需要访问实例属性和实例方法,也不需要访问类属性和类方法时,就可以使用静态方法。
1 >>> class People: 2 ... country = "China" 3 ... @staticmethod 4 ... def getCountry(): 5 ... return People.country 6 ... 7 >>> People.getCountry() # 通过类对象访问静态方法 8 'China' 9 >>> p = People() 10 >>> p.getCountry() # 通过实例对象访问静态方法 11 'China'
总结
从类方法、实例方法以及静态方法的定义形式可以看出:
- 类方法的第一个参数是类对象 cls,那么通过 cls 引用的必定是类对象的属性和方法。
- 实例方法的第一个参数是实例对象 self,那么通过 self 引用的是实例对象的属性或方法。
- 静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类对象来引用。
6. 私有属性、私有化、属性 property
私有属性
如果有一个对象,当需要对其进行修改属性时,有 2 种方法:
- 对象名.属性名 = 数据 ——> 直接修改。
- 对象名.方法名() ——> 间接修改,能够制定修改规则。
为了更好的保存属性安全,即不能随意修改,一般的处理方式是第 2 种:将属性定义为私有属性,并添加一个可以调用的方法,供调用。
- 私有的属性,不能通过外部对象直接访问,但是可以通过方法访问;私有的方法,不能通过对象直接访问。
- 一般情况下,私有的属性、方法都是不对外公布的,往往用来做内部的事情,起到安全的作用。
- Python 中没有像 C++ 中用 public 和 private 这些关键字来区别公有属性和私有属性。它是以属性命名方式来区分,如果在属性名前面加了两个下划线 '__',则表明该属性是私有属性(方法也是一样,方法名前面加了两个下划线的话表示该方法是私有的)。
示例:对私有的实例属性进行修改
1 class People: 2 3 def __init__(self, name): 4 self.__name = name 5 6 def getName(self): 7 return self.__name 8 9 def setName(self, newName): 10 if len(newName) > 5: 11 self.__name = newName 12 else: 13 print("error:名字长度需要大于5位") 14 15 16 xiaoming = People("xiaoming") 17 xiaoming.setName("xiao") # error:名字长度需要大于5位 18 print(xiaoming.getName()) # xiaoming 19 xiaoming.setName("xiaodan") 20 print(xiaoming.getName()) # xiaodan
示例:对私有的类属性进行修改
1 class People: 2 3 __country = "China" # 私有的类属性 4 5 @classmethod 6 def getCountry(cls): 7 print(cls.__country) 8 9 @classmethod 10 def setCountry(cls, country): 11 cls.__country = country 12 13 14 People.getCountry() # China 15 People.setCountry("Fairy") 16 People.getCountry() # Fairy
私有化
- xx:公有变量。
- _xx:单前置下划线,私有化变量,表示 from module import * 时禁止导入,而 import module 时可以导入。
- __xx:双前置下划线,私有化属性或方法,无法在类的外部直接访问,也无法被子类继承。
- __xx__:双前后下划线,用户名字空间的魔法对象或属性。例如:__init__ ,不要自己发明这样的名字。
- xx_:单后置下划线,用于避免与 Python 关键词的冲突。
示例:
1 class Person: 2 3 def __init__(self, name, age, hobby): 4 self.name = name 5 self._age = age 6 self.__hobby = hobby 7 8 def showPerson(self): 9 print(self.name) 10 print(self._age) 11 print(self.__hobby) 12 13 def dowork(self): 14 self._work() 15 self.__off() 16 17 def _work(self): 18 print("_work") 19 20 def __off(self): 21 print("__off") 22 23 24 class Student(Person): 25 26 def changeStudent(self, name, age, hobby): 27 self.name = name 28 self._age = age 29 self.__hobby = hobby 30 31 def showStudent(self): 32 print(self.name) 33 print(self._age) 34 print(self.__hobby) 35 36 @staticmethod 37 def testBug(): 38 _Bug.showBug() 39 40 41 # 模块内可以访问,当 from module import * 时,则不导入 42 class _Bug: 43 44 @staticmethod 45 def showBug(): 46 print("showBug") 47 48 49 s = Student("jack", 25, "football") 50 s.showPerson() # 子类可以通过父类方法访问父类的私有属性 51 print("-------------------") 52 # s.showStudent() 子类无法继承和直接访问父类的__hobby,因此会报错 53 s.changeStudent("rose", 21, "piano") 54 s.showPerson() # rose 55 print("-------------------") 56 s.showStudent() # rose;此时访问不报错,是因为子类创建了 __hobby 变量 57 print("-------------------") 58 Student.testBug()
执行结果:
E:\python_pra>python test.py jack 25 football ------------------- rose 21 football ------------------- rose 21 piano -------------------
总结:
- 父类中属性名为 __名 的,子类无法继承也无法直接访问(可以通过父类方法间接访问)。
- 如果在子类中向 __名 赋值,那么会在子类中定义的一个与父类相同名字的属性。
- _名的变量、函数、类在使用 from xxx import * 时都不会被导入。
名字重整
通过 name mangling 机制就可以访问私有的属性或方法(即名字重整,目的就是以防子类意外重写基类的方法或者属性,如:_Class__object)。
属性 property
使用方式一: 变量名 = property(get方法, set方法)
1 class Money: 2 3 def __init__(self): 4 self.__money = 0 5 6 def setMoney(self, value): 7 if isinstance(value, int): 8 self.__money = value 9 10 def getMoney(self): 11 return self.__money 12 13 # 该变量名供赋值或取值使用 14 money = property(getMoney, setMoney) 15 16 m = Money() 17 18 # 传统方式:调用方法进行赋值和取值 19 m.setMoney(100) 20 print(m.getMoney()) # 100 21 22 # 使用property方式,相当于把方法进行了封装,开发者在对属性设置数据的时候更方便 23 m.money = 200 # 相当于调用了m.setMoney(200) 24 print(m.money) # 相当于调用了getMoney(),结果为200
使用方式二:使用装饰器
1 class Money: 2 3 def __init__(self): 4 self.__money = 0 5 6 # 注意两个方法名相同,并供赋值或取值使用 7 # 相当于get方法 8 @property 9 def money(self): 10 return self.__money 11 12 # 相当于set方法 13 @money.setter 14 def money(self, value): 15 if isinstance(value, int): 16 self.__money = value 17 18 19 m = Money() 20 21 m.money = 200 22 print(m.money) # 200
7. 继承
在程序中,继承描述的是事物之间的所属关系。
例如在现实生活中猫和狗都属于动物,在程序中则可以描述为猫和狗继承自动物;同理,波斯猫和巴厘猫都继承自猫,而沙皮狗和斑点狗都继承自狗,如下所示:
注意:私有属性/方法不会被子类继承。
单继承
1 class Animal: 2 3 def __init__(self, name="动物", color="白色"): 4 self.__name = name 5 self.color = color 6 7 def __test(self): 8 print(self.__name) 9 print(self.color) 10 11 def test(self): 12 print(self.__name) 13 print(self.color) 14 15 16 # 子类 Dog,继承自父类 Animal 17 class Dog(Animal): 18 19 def dogTest1(self): 20 # 无法继承父类的私有属性 21 # print(self.__name) 22 print(self.color) 23 24 def dogTest2(self): 25 # 无法继承父类的私有方法 26 # self.__test() 27 self.test() 28 29 30 a = Animal() 31 print(a.color) # 白色 32 33 d = Dog(name="旺财", color="黄色") 34 d.dogTest1() # 黄色 35 d.dogTest2() # 旺财,黄色
多继承
所谓多继承,即子类有多个父类,并且具有它们的特征。
1 # 父类A 2 class A: 3 def printA(self): 4 print("---A---") 5 6 # 父类B 7 class B: 8 def printB(self): 9 print("---B---") 10 11 # 子类C,继承自父类A、B 12 class C(A, B): 13 def printC(self): 14 print("---C---") 15 16 17 c = C() 18 c.printA() # 子类调用父类A的方法 19 c.printB() # 子类调用父类B的方法 20 c.printC()
问:在多继承中,如果多个父类中,有一个同名的方法,那么通过子类去调用的时候,调用哪个?
1 class base: 2 def test(self): 3 print("---base---") 4 5 # 继承base类 6 class A(base): 7 def test(self): 8 print("---A---") 9 10 # 继承base类 11 class B(base): 12 def test(self): 13 print("---B---") 14 15 16 # 继承A、B类 17 class C(A, B): 18 pass 19 20 21 c = C() 22 c.test() 23 print(C.__mro__) # 可以查看C类的对象搜索方法时的先后顺序
输出结果:
---A--- (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.base'>, <class 'object'>)
重写父类的方法
所谓重写,就是子类中,有一个和父类相同名字的方法,在子类中的方法会覆盖掉父类中同名的方法。
1 class base: 2 def say(self): 3 print("---Im base---") 4 5 6 class A(base): 7 def say(self): 8 print("---Im A---") 9 10 11 a = A() 12 a.say() # ---Im A---
调用父类的方法
如果子类中存在与父类相同名字的方法,那么可以使用如下方法来调用父类的该同名方法。
1 class base: 2 def say(self): 3 print("---Im base---") 4 5 6 class A(base): 7 def say(self): 8 # 调用父类的同名方法 9 super().say() 10 11 12 a = A() 13 a.say() # ---Im base---
8. 多态
- 所谓多态,就是定义时的类型和运行时的类型不一样,此时就成为多态。
- 多态的概念是应用于 Java 和 C# 这一类强类型语言中,而 Python 崇尚“鸭子类型”。
示例:Python 伪代码实现 Java 或 C# 的多态
1 class base(object): 2 def show(self): 3 print('base.show') 4 5 6 class A(base): 7 def show(self): 8 print('A.show') 9 10 11 class B(base): 12 def show(self): 13 print('B.show') 14 15 16 # 由于在Java或C#中定义函数参数时,必须指定参数的类型,如base 17 # 为了让Func函数既可以执行A对象的show方法,又可以执行B对象的show方法,所以,定义了一个A和B类的父类base 18 # 而实际传入的参数是:A对象和B对象 19 def Func(base obj): 20 "Func函数需要接收一个base类型或者base子类的类型" 21 print obj.show() 22 23 a_obj = A() 24 Func(a_obj) # 在Func函数中传入A类的对象a_obj,执行a的show方法,结果:a.show 25 26 b_obj = B() 27 Func(b_obj) # 在Func函数中传入B类的对象b_obj,执行b的show方法,结果:b.show
示例:Python的 “鸭子类型”
1 class base(object): 2 def show(self): 3 print('base.show') 4 5 6 class A(base): 7 def show(self): 8 print('A.show') 9 10 11 class B(base): 12 def show(self): 13 print('B.show') 14 15 16 # 形参为 obj 即可 17 def Func(obj): 18 "传入的obj实例对象具有show()方法即可" 19 obj.show() 20 21 a_obj = A() 22 Func(a_obj) # A.show 23 24 b_obj = B() 25 Func(b_obj) # B.show