[TOC]
----
# Python3 面向对象
## 什么是面向对象:
面向对象是现代语言必备的一个特性。传统的面向过程编程(模块封装都是通过函数实现的,类似于C语言),不符合人类思考的一种方式。比如人吃饭这种行为,如果使用面向过程编程,那么是首先定义一个吃饭的行为,然后再把这个人传过去。这明显是不符合人类的思考方式的,人类正常的思考方式应该是,吃饭这个行为是属于人类的,应该由人的这个对象自己去执行。这种思考方式的转变,就是面向对象。面向对象把所有属于某种物体(比如人)的属性和行为全都定义在这个物体上,比如年龄,身高,体重这些是属性,比如招手,跑步,吃饭这些是行为。以后要知道一个人的年龄,应该查看这个人的年龄属性,要让这个人奔跑,应该调用这个人的奔跑方法。这种思考方式就是面向对象。
## 面向对象技术简介:
* 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
* 方法:类中定义的函数。唯一的不同在于我们还拥有一个额外的 `self` 变量。
* 类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
* 数据成员:类变量或者实例变量用于处理类及其实例对象的相关的数据。
* 方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
* 局部变量:定义在方法中的变量,只作用于当前实例的类。
* 实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
* 继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
* 实例化:创建一个类的实例,类的具体对象。
* 对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
## self 代表对象本身(实例化到后的对象)
类方法与普通函数只有一种特定的区别——前者必须多加一个参数在参数列表开头,这个名字必须添加到参数列表的开头,但是你不用在你调用这个功能时为这个参数赋值,Python 会为它提供。这种特定的变量引用的是对象本身,按照惯例,它被赋予 self 这一名称。
尽管你可以为这一参数赋予任何名称,但是强烈推荐你使用 self 这一名称——其它的任何一种名称绝对会引人皱眉。使用一个标准名称能带来诸多好处——任何一位你的程序的读者能够立即认出它,甚至是专门的 IDE(Integrated Development Environments,集成开发环境)也可以为你提供帮助,只要你使用了 self 这一名称。
>Python 中的 self 相当于 C++ 中的 this 指针以及 Java 与 C# /php中的 this 引用。
你一定会在想 Python 是如何给 self 赋值的,以及为什么你不必给它一个值。一个例子或许会让这些疑问得到解答。假设你有一个 MyClass 的类,这个类下有一个实例 myobject。当你调用一个这个对象的方法,如 myobject.method(arg1, arg2) 时,Python 将会自动将其转换成 MyClass.method(myobject, arg1, arg2)——这就是 self 的全部特殊之处所在。
这同时意味着,如果你有一个没有参数的方法,你依旧必须拥有一个参数——self。
## 类和对象的使用:
1. 类:定义一个类的语法是使用class关键字,开发者自己定义的类,必须继承自object类,object类是Python中所有的类的基类。类中所有的方法都要以self作为第一个参数传递进去,这是规定。self代表的是当前的对象。
```python
#第一个类的例子
class Dog():
name = 'xiaohui'
age = 2
def getGogInfo(self):
print('name:%s' % self.name)
print('age:%s' % self.age)
```
2. 对象:对象是对类的一个实例化,对象的创建方式如下(比如要实现一个Person的对象):
```python
# 实例化类
dog=Dog()
dog.getGogInfo()
# 打印结果:
# name:xiaohui
# age:2
```
## 构造函数和实例属性:
构造函数是Python类在初始化对象的时候会调用的方法。一般在这个方法中给一些属性进行初始化。
类有一个名为 __init__() 的特殊方法(构造方法),该方法在类实例化时会自动调用,像下面这样:
当然, __init__() 方法可以有参数,参数通过 __init__() 传递到类的实例化操作上。例如:
self代表类的实例,而非类
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是 self。
一般就是用来初始化实例属性的:
```python
class Complex:
def __init__(self, realpart, imagpart):
self.r = realpart
self.i = imagpart
x = Complex(3.0, -4.5)
print(x.r, x.i) # 输出结果:3.0 -4.5
```
给person1这个对象绑定name属性
```python
class Person():
def __init__(self,name,age):
self.name = name
self.age = age
# 给person1这个对象绑定name属性
person1 = Person('zhiliao',18)
print(person1.name,person1.age) #输入结果: zhiliao 18
```
给对象添加实例属性非常简单,以上是通过self.name的方式实现的,其实也可以通过以下方式实现,原理都是一样的。
```python
# 类,对象
# 实例化
# 类最基本的作用:封装
#第一个类的例子
class Student():
sum = 0 #类变量
name = '' #实例化变量赋值
age = 0 #实例化变量赋值
# 构造函数
def __init__(self,name,age):
# 实例化变量
# 初始化对象的属性
self.name = name #给实例化变量赋值
self.age = age
def getGogInfo(self):
print('name:%s' % self.name) #调用实例化类变量
print('age:%s' % self.age) #调用实例化类变量
self.__class__.sum += 1 #操类变量计数
print('sum的值%a'%self.__class__.sum)
student = Student('lisi',18)
student.getGogInfo()
# 打印:
# name:lisi
# age:18
# sum的值1
# name:wangwu
# age:20
# sum的值2
```
## 类变量与对象变量
我们已经讨论过了类与对象的功能部分(即方法),现在让我们来学习它们的数据部分。数据部分——也就是字段——只不过是绑定(Bound)到类与对象的命名空间(Namespace)的普通变量。这就代表着这些名称仅在这些类与对象所存在的上下文中有效。这就是它们被称作“命名空间”的原因。
字段(Field)有两种类型——`类变量`与`对象变量`,它们根据究竟是类还是对象拥有这些变量来进行分类。
**类变量(Class Variable)**
是共享的(Shared)——它们可以被属于该类的所有实例访问。该类变量只拥有一个副本,当任何一个对象对类变量作出改变时,发生的变动将在其它所有实例中都会得到体现。
**对象变量(Object variable)**
由类的每一个独立的对象或实例所拥有。在这种情况下,每个对象都拥有属于它自己的字段的副本,也就是说,它们不会被共享,也不会以任何方式与其它不同实例中的相同名称的字段产生关联。上面一个例子可以帮助你理解.
**例子:**
```python
class Dog():
name = 'xiaohui'
age = 2
def getGogInfo(self):
print('name:%s' % self.name)
print('age:%s' % self.age)
#实例一
dog = Dog()
print(dog.age) # 实例调用
Dog.age+=2 # 类调用
print(Dog.age) #类调用
#实例二
dog2 = Dog()
print(dog2.age)
Dog.age+=2 #类调用
print(Dog.age) # 类调用
# 打印结果:
# 2
# 4
# 4
# 6
```
## 类方法(cls代表类本身)与对象方法
**类方法** 是类里用装饰器`@classmethod`声明的方法,指的是公用的方法
**对象方法** 是实例化类时,实例里的普通方法.此实例里自己的方法
```tython
class Student():
#类变量
sum = 0
name = ''
age = 0
# 构造函数
def __init__(self,name,age):
# 实例化变量
# 初始化对象的属性
self.name = name
self.age = age
#实例方法
def getGogInfo(self):
print('name:%s' % self.name)
print('age:%s' % self.age)
self.sum += 1
print('sum的值%a'%self.sum)
#通过装饰器来定义类方法
@classmethod
def plus_sum(cls):
cls.sum += 1
print(cls.sum)
student1 = Student('lisi',18)
# 类调用类方法
Student.plus_sum() # 输出: 1
# 实例调用类方法(不推荐这么使用,不容易理解)
student2 = Student('wangwu',20)
student2.plus_sum() # 输出: 2
```
## 类的外部调用和内部调用
**类的外部** 实例化对象后,调用.
**内部调用** 类内部调用属性和方法.
```python
class Student():
#构造函数
def __init__(self,name,age):
# 实例化变量
# 初始化对象的属性
self.name = name
self.age = age
self.score = 0
def marking(self,score):
if score < 0:
return '不能给别人打负分'
self.score = score
self.get_name() # 我是内部调用类内的变量name:lisiname 类的内部调用
return '%s本次考试的分数为:%d'%(self.name,self.score)
def get_name(self):
print('我是内部调用类内的变量name:%sname' % self.name)
student1 = Student('lisi',18)
result=student1.marking(60) #外部调用
print(result) #lisi本次考试的分数为:60
result=student1.marking(-1)
print(result) #不能给别人打负分
#不推荐这样在外部直接修改类内部的变量,要通过类内部的方法进行验证之后再去操作相应的变量
student1.score = -1
print(student1.score) # -1
# 输出:
# 我是内部调用类内的变量name:lisiname
# lisi本次考试的分数为:60
# 不能给别人打负分
# -1
```
## 访问限制:
1. 受保护的属性和方法:
有时候在类中的属性或者是方法不想被外界调用,但还是可以被外界所调用,那么就叫做受保护的属性或者方法。受保护的属性或者方法,使用一个下划线开头:
```python
class Person(object):
def __init__(self):
self._age = 19
p1 = Person()
# 以下代码可以打印出_age,但这样做是违背开发者意愿的。
print(p1._age) #19
```
2. 私有属性和方法:
有时候在类中的属性或者方向不让外界调用,那么就可以使用定义成私有属性或者私有方法。私有属性或者方法使用两个下划线开头:
```python
class Person(object):
def __init__(self):
self.__age = 19
p1 = Person()
# 以下代码将报错
print(p1.__age)
```
3. 更多:
* 私有方法或者属性不是说100%不能访问,以上方式,可以通过_Person__age来访问,但这样做是不推荐的。
* __init__这些方法不是私有方法,是特殊变量或方法。
## 析构函数和引用计数:
1. `Python`中的类也有析构函数,也即`__del__`方法.
只要这个对象在内存中即将被消灭的时候,就会调用这个方法。
```python
class Person(object):
def __del__(self):
print('我即将被消灭了~')
p1 = Person()
# 调用完这个代码后,就会去执行Person类中的__del__方法
# 打印: 我即将被消灭了~
```
2. 引用计数:
Python中的对象是使用引用计数的方式实现的。也即如果没有任何对象引用到一块内存,那么Python将会把这块内存回收。看以下代码:
```python
class Person(object):
def __del__(self):
print('我即将被消灭了~')
p1 = Person()
p2 = p1
del p1
# 先会打印=====
print('======')
# 再会去执行__del__方法
# 打印:
# ======
# 我即将被消灭了~
```
## 继承和重写父类的方法:
**继承**
可以使用其他类当作自己的父类,那么父类中的方法和公有属性都可以被子类使用。继承的好处是可以让子类节省代码,实现更多的功能。
**重写父类的方法**
有些时候,父类中的方法不一定适合子类,那么这时候可以重写父类的方法,而使用子类自己定义的方法。
子类`demo1.py`
```python
from demo2 import Human #引入父类里的Human类
class Student(Human): #继承父类Human
#类变量
sum = 0
#构造函数
def __init__(self,school,name,age):
# 实例化变量
# 初始化对象的属性
Human.__init__(self,name,age) #类调用实例方法灯给父类的初使化传值
def get_name(self):
self.name='wangwu'
print(self.name)
print(Student.sum) # 输出0 继承父类里的sum 通过类来调用变量
student1 = Student('人民大学','lisi',18) #实例化时要给父类里的init传值
print(student1.sum) # 输出0 继承父类里的 通过对象调用变量
print(student1.name) # 输出lisi 继承父类里的 通过对象调用变量
print(student1.age) # 输出18 继承父类里的 通过对象调用变量
student1.get_name() # 输出wangwu 继承并重写父类里的方法 通过对象调用方法
```
父类`demo2.py`
```python
class Human():
sum = 0
def __init__(self,name,age):
self.name = name
self.age =age
def get_name(self):
print(self.name)
```