12.面向对象编程
面向对象技术简介
- 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- **方法:**类中定义的函数。
- **类变量:**类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- **数据成员:**类变量或者实例变量用于处理类及其实例对象的相关的数据。
- **方法重写:**如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
- **局部变量:**定义在方法中的变量,只作用于当前实例的类。
- **实例变量:**在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- **继承:**即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
- **实例化:**创建一个类的实例,类的具体对象。
- **对象:**通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
类的定义
class Person:
name = "张三"
age = 3 # 定义在这里的属性叫类属性,所有这个类的实例对象都会共有这个属性相当于java中的静态变量
def sayHello(self):
print("hello")
person = Person()
print(person.name)
print(person.age)
person.sayHello()
p2 = Person()
print(p2.name)
print(p2.age)
类属性的访问与修改
class Person:
name = "张三"
age = 3
def sayHello(self):
print("hello")
# 类属性可以通过类名.属性名的方式来访问和修改,实例化对象要想对类属性进行修改要通过
# 实例对象想要修改类属性必须通过 person.__class__.name 的方式进行访问
Person.name = "诸葛亮"
person = Person()
person.__class__.name = "张飞"
print(Person.name)
实例的属性
class Person:
def __init__(self, name, age): # __init__方法是一个魔术方法,当调用改造方法时会自动调用该方法
self.name = name # 通过self.定义的属性都是实例属性,self指实例对象本身相当于java中的this关键字
self.age = age
person1 = Person("张三", 3)
person2 = Person("李四", 5)
person3 = Person("王五", 9)
print(person1.name, person1.age)
print(person2.name, person2.age)
print(person3.name, person3.age)
类方法
class Person:
@classmethod # 通过这个装饰器装饰的方法都是类方法,相当于Java中的静态方法,可以通过 类名.方法 的形式调用
def sayHello(self):
print("hello")
Person.sayHello()
虚假的私有属性与君子约束
class Person:
def __init__(self, name, age):
self.__name = name # 通过 self.__变量名定义的属性是私有属性,无法直接通过.直接方法,需要定义set和get方法
self.__age = age
def getName(self):
return self.__name
def getAge(self):
return self.__age
def setName(self, name):
self.__name = name
def setAge(self, age):
self.__age = age
person = Person("张三", 3)
print(person.getName())
print(person.getAge())
"""
其实在python中并没有真正的私有属性
虽然不能通过.属性名的方式访问
但是可以通过 obj._类名__属性名 的形式进行直接访问,所以python中的私有属性只是一种君子约束,一般我们只用一个下划线表示不希望被修改即可
"""
person._Person__name = "诸葛亮"
print(person.getName())
通过装饰器对get方法和Set方法优化
class Person:
def __init__(self):
pass
@property # 通过这个装饰器可以将方法属性化
def name(self):
return self.__name
@property
def age(self):
return self.__age
@name.setter # 可以设置 setter deleter getter ...
def name(self, name):
print("setName方法被调用")
self.__name = name
@age.setter
def age(self, age):
print("setAge方法被调用")
self.__age = age
person = Person()
person.name = "张三" # 这样就可以通过像设置属性一样调用方法了
person.age = 3
print(person.name)
print(person.age)
继承(重点)
- Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def func1(self):
print("我是Person")
class Student(Person):
def __init__(self, name, age, num):
Person.__init__(self, name, age) # 调用父类构造器
self._num = num # 子类自己的属性
def study(self): # 子类自己的方法
print("好好学习天天向上")
student = Student("张三", 3, 123456)
print(student._name, student._age, student._num)
student.func1()
student.study()
- 此外与java不同的是,python是一门多继承的语言
- python的多继承,多继承的构造方法可以根据需要进行重写
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
def func1(self):
print("我是Person")
class Son(Person):
def __init__(self, name, age, gender):
Person.__init__(self, name, age)
self._gender = gender
def son(self):
print("在老子面前永远是儿子")
class Student(Person):
def __init__(self, name, age, num):
Person.__init__(self, name, age) # 调用父类构造器
self._num = num # 子类自己的属性
def study(self): # 子类自己的方法
print("好好学习天天向上")
class Me(Son, Student):
# 如果不写__init__方法就会默认基础第一个继承类的构造
def __init__(self, name, age, gender, num, gift):
Son.__init__(self, name, age, gender)
Student.__init__(self, name, age, num)
self._gift = gift
def me(self):
print("我就是我颜色不一样的烟火")
me = Me("孙悟空", 756, "男", 123, "七十二变")
print(isinstance(me, Person)) # isinstance(a,b) 判断a是不是b的实例
print(isinstance(me, Student))
print(isinstance(me, Son))
print(me._name)
print(me._age)
print(me._gender)
print(me._gift)
me.func1()
me.study()
me.son()
me.me()
方法重写
- 如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:
\#!/usr/bin/python3
class Parent: # 定义父类
def myMethod(self):
print ('调用父类方法')
class Child(Parent): # 定义子类
def myMethod(self):
print ('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
类的那些内置的魔术方法
类的专有方法:
- _init_ : 构造函数,在生成对象时调用
- _del_ : 析构函数,释放对象时使用
- _repr_ : 打印,转换
- _setitem_ : 按照索引赋值
- _getitem_: 按照索引获取值
- _len_: 获得长度
- _cmp_: 比较运算
- _call_: 函数调用
- _add_: 加运算
- _sub_: 减运算
- _mul_: 乘运算
- _truediv_: 除运算
- _mod_: 求余运算
- _pow_: 乘方
重写这些方法会在特定的条件下被调用,例如常用的_del_ 会在对生命周期结束时被回收时调用
_len_ 在调用len()函数时会被调用等
如果一个类同时继承了多个类有多个方法相同应该继承谁的呢?(难点)
如图所示是一个非常复杂的类继承关系图,代码展示如下
class A:
def __init__(self,a):
self.a = a
def Hello(self):
print("helloA")
class B:
def __init__(self,a):
self.a = a
def Hello(self):
print("helloB")
class C:
def __init__(self,a):
self.a = a
def Hello(self):
print("helloC")
class D(A):
def __init__(self,a,b):
A.__init__(self,a)
self.b = b
def Hai(self):
print("HiD")
class E(B,C):
def __init__(self,a,b):
C.__init__(self,a)
self.b = b
def Hello(self):
print("HelloC")
def Hai(self):
print("HiE")
class F(D,E,C):
def __init__(self,a,b,c):
D.__init__(self,a,b)
self.c = c
- 此时F的调用构造方法,首先它会在自己的类中找有没有重写 __init__ 方法,如果有则调用该方法
- 然后实例分别调用Hello()和Hai() 方法该调用谁的方法呢?
- 首先这个实例会在F类里找,看看有没有重写这两个方法,发现没有重写这两个方法,这时候会到D类里找
- 如果D类里恰巧有这两个方法,则会直接使用D类的这两个方法,如果D类不存在该重写方法,则会去D类的父类也就是A类中寻找,如果在A类中找到了对应的方法就会使用A类的方法,如果没找到就会去object类里找。
- 如果在Object类中没有找到才会去E类中找,如果E类中没找到就会去E类的父类中找,E类的第一个父类中没找到就会去找第二个父类,然后一直找到Object
- 全部都没找到才会去C类中找,知道找到为止,如果找完了所有的类都没有找到该方法,则会报错
- F类找方法的路线解析:(在寻找路线的任任何一个类中找到了匹配的方法将不会继续往后边寻找。)
- F => D => A => Object => E => B => C => Object => C => Object