1. 面向对象
在学习类之前,我们需要了解面向对象编程
, 面向对象就是将编程当成是一个事物
,对外界来说,事物是直接使用的,不用去管他内部的情况。而编程就是设置事物能够做什么事。面向对象核心是对象
,在python中一切皆对象,而在pythoh中对象
就是一系列数据
与功能
的集合。
2. 类
多个对象
有相似的数据与功能,我们可以加以分类,类
对一系列具有相同特征
和行为
的事物的统称,是一个抽象的概念
,不是真实存在的事物。可以把同一类对象相同的数据和方法放到类中
, 不需要每个对象定义一份相同的数据和方法,每个对象只需要存放自己特有的数据与方法,这要做的好处就是极大的节省了内存空间
,简单来说类就是用来存放多个对象相同的数据
与功能的容器
。
2.1 定义类
在程序中必须先定义类
, 然后再调用类产生对象
(调用类拿到的返回值就是对象), 产生对象的类和对象之间存在关联的是,对象可以访问类中共有的数据与功能
,所以类中的内任然是属于对象
, 类起到的作用是节省空间
, 减少代码冗余
。
语法
"""
class 类名():
代码块
......
"""
示例
我们就以生活中的学生类来做示例
# 定义类
class Student:
"""
注释
"""
school = '清华'
def study(self):
print('is learning')
def choice_course(self):
print('choose course')
# 查看类的名称空间,可以看到类的数据,行为,文档注释,还用其他的一些属性和方法
print(Student.__dict__)
# 结果: {'__module__': '__main__', '__doc__': '\n 注释\n\n ', 'school': '清华', 'study': <function Student.study at 0x109ab5f28>, 'choice_course': <function Student.choice_course at 0x109abb0d0>, ....}
# 访问名称空间的名字,查看属性
print(Student.school) # 类的数据属性
# 结果: 清华
print(Student.study) # 类的函数属性
# 结果: <function Student.study at 0x109ab5f28>
print(Student.choice_course)
# 结果: <function Student.choice_course at 0x109abb0d0>
学生身上有school
这个属性,和study, choice_course
行为
增加类的属性
# 添加数据
Student.country = 'China'
print(Student.__dict__)
# 结果: {..... 'country': 'China'}
print(Student.country)
# 结果: China
def run():
print('this is running')
# 添加行为属性
Student.run = run
Student.run()
# 结果: this is running
修改类的属性
# 修改属性
Student.school = '北大'
print(Student.school)
# 结果: 北大
删除类的属性
del Student.country
print(Student.country)
# 结果: AttributeError: type object 'Student' has no attribute 'country'
实例化类
# 实例化对对象
s1 = Student()
s2 = Student()
s3 = Student()
# 实例调用行为
s1.study()
s1.choice_course()
s2.study()
s2.choice_course()
此时s1
,和s2
实例,s1
和s2
拥有共同的数据和行为,而对象自己身上的自己特定数据和行为没有,需要在类中实现__init__
方法
定义类
class Student:
"""
注释
"""
school = '清华'
def __init__(self, name, age, sex):
"""
stu1, name='张三', age=18, sex='male'
在类调用时自动执行
"""
self.name = name
self.age = age
self.sex = sex
def study(self):
print('is learning')
def choice_course(self):
print('choose course')
实例化
s1 = Student(name='张三', age=18, sex='male')
print(s1)
# 结果: <__main__.Student object at 0x10f8ba828>
print(s1.__dict__)
# 结果: {'name': '张三', 'age': 18, 'sex': 'male'}
print(s1.name, s1.age, s1.sex)
# 结果: 张三 18 male
s2 = Student('李四', 10, 'male')
print(s2)
# 结果: <__main__.Student object at 0x1023a88d0>
print(s2.__dict__)
# 结果: {'name': '李四', 'age': 10, 'sex': 'male'}
print(s2.name, s2.age, s2.sex)
# 结果: 李四 10 male
s3 = Student('王五', 20, 'female')
print(s3)
# 结果: <__main__.Student object at 0x1023a8908>
print(s3.__dict__)
# 结果: {'name': '王五', 'age': 20, 'sex': 'female'}
print(s3.name, s3.age, s3.sex)
# 结果: 王五 20 female
实例产生的过程来说,调用类会先产生一个空对象
s1 然后将 s1 连同调用类时括号类的参数一起传给Student.__init__(s1, '张三', 18, 'male')
,其它实例也是一样的做法
2.2 属性的访问
在类中定义的数据和行为都是类的属性,具体可以 数据属性
和 函数属性
,可以通过 __dict__
方法访问,python提供了专门的属性访问方法
属性访问方式
x = 1
class Student:
"""
注释
"""
school = '清华'
def __init__(self, name, age, sex):
"""
stu1, name='张三', age=18, sex='male'
在类调用时自动执行
"""
self.name = name
self.age = age
self.sex = sex
def study(self, x, y):
print(f'{self.name} is learning {x + y}')
def choice_course(self):
print('choose course')
"""
1. 查找一个对象的属性顺序是,先找自己的名称空间obj.__dict__,
再找类的名称空间class.__dict__
"""
# 数据属性访问方式:
stu1 = Student(name='张三', age=18, sex='male')
print(stu1.name)
# 结果: 张三
print(stu1.school)
# 结果: 北大
print(stu1.x)
# 结果: AttributeError: 'Student' object has no attribute 'x', 类和对象的名称空间都没有 x 属性, 报错
# 函数属性反问方式:
stu1.study()
类的数据属性和函数属性数据属性
Student.school = '北大'
print(stu1.school, id(stu1.school))
# 结果: 北大 4537981744
print(stu2.school, id(stu2.school))
# 结果: 北大 4537981744
print(stu3.school, id(stu3.school))
# 结果: 北大 4537981744
类的数据属性是所有对象共享
,所有对象都指向同一个内存地址
函数属性
print(Student.study)
# 结果: <function Student.study at 0x106b820d0>
print(stu1.study)
# 结果: <bound method Student.study of <__main__.Student object at 0x106b78860>>
print(stu2.study)
# 结果: <bound method Student.study of <__main__.Student object at 0x106b78898>>
print(stu3.study)
# 结果: <bound method Student.study of <__main__.Student object at 0x106b788d0>>
stu1.study(1, 3) # 相当于 Student.study(stu1, 1, 3)
# 结果: 张三 is learning 4
stu2.study(1, 4)
# 结果: 李四 is learning 5
stu3.study(1, 5)
# 结果: 王五 is learning 6
类中定义的函数是绑定给对象使用
,不同的对象就是不同的绑定方法
, 绑定给谁,就应该由谁使用,谁来调用
就会把谁当作第一个参数
传给对应的函数
绑定到对象方法的这种自动传值的特征
,决定了在类中定义的函数都要默认写一个参数self
,self可以是任意名字,但命名为self是约定俗成的。
2.3 继承与派生
继承
是一种创建新类的方式,在python中,新建的类
可以继承 一个
或者 多个父类
,新建的类可称为 子类
或者 派生类
,父类又可以称为 基类
或 超类
,对象的继承指的是多个类之间的所属关系
,即子类默认继承父类的所有属性和方法
。
class Parent1:
pass
class Parent2:
pass
class Sub1(Parent1):
pass
class Sub2(Parent1, Parent2):
pass
print(Sub1.__bases__)
# 结果: (<class '__main__.Parent1'>,)
print(Sub2.__bases__)
# 结果: (<class '__main__.Parent1'>, <class '__main__.Parent2'>)
print(Parent1.__bases__)
# 结果: (<class 'object'>,)
print(Parent2.__bases__)
# 结果: (<class 'object'>,)
通过类的内置属性 __bases__
可以查看类继承的所有父类
子类父类的属性
class People:
school = '清华'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print(f'姓名:{self.name}年龄:{self.age}性别:{self.sex}')
class Student(People):
def learn(self):
print(f'{self.name} is learning')
def tell_info(self):
print(f'我是学生:姓名:{self.name}年龄:{self.age}性别:{self.sex}')
class Teacher(People):
def teach(self):
print(f'{self.name} is teaching')
stu1 = Student('action', 18, 'male')
# Student 类中有方法,不会往父类中找
stu1.tell_info()
# 结果: 我是学生:姓名:action年龄:18性别:male
print(stu1.__dict__)
# 结果: {'name': 'action', 'age': 18, 'sex': 'male'}
print(Student.__dict__)
# 结果: {'__module__': '__main__', 'learn': <function Student.learn at 0x109eaf1e0>, 'tell_info': <function Student.tell_info at 0x109eaf2f0>, '__doc__': None}
teacher = Teacher('cross', 20, 'female')
# Teacher 类中没有该方法,往父类中查找
teacher.tell_info()
# 结果: 姓名:cross年龄:20性别:female
Teacher
类 和Student
类中并没有定义__init__
方法,但是会从父类中找到__init__
因而任然可以正常实例化。
class Foo:
def f1(self):
print('foo Foo.f1')
def f2(self): # self=obj
print('from Foo.f2')
self.f1() # obj.f1()
class Bar(Foo):
def f1(self):
print('from Bar.f1')
obj = Bar()
obj.f2()
# 结果如下:
"""
from Foo.f2
from Bar.f1
"""
有了继承关系,python 对象在查找属性时,先从对象自己的__dict__
中找,如果没有则去子类中找,然后再去父类中找
b.f2()
会在父类Foo
找到 f2 先打印from Foo.f2
, 然后执行self.f1()
即b.f1()
仍然按照,对象本身 -> 类Bar -> 父类Foo 的顺序依次找下去,在类Bar
中找到f1
,因而打印结果为from Bar.f1
继承的实现原理
对于定义的类, python都会 计算得出一个方法解析顺序(MRO)列表
,该(MRO)列表就是一个简单的所有基类的 线性顺序列表
print(Bar.mro())
# 结果: [<class '__main__.Bar'>, <class '__main__.Foo'>, <class 'object'>]
在python中字类可以同时继承多个父类,在子类继承了多个父类时,经典类
与新式类
会有不同的MRO,分别对属性的两种查找方式,深度优先和广度优先
,经典类为深度优先
,新式类为广度优先
经典类和新式类
在python2中有 经典类
和 新式类
的区别,经典类就是 没有继承 object 的类,以及该类的子类
,新式类就是 指的就是继承 object 类的类, 以及该类子类
, python3 统一为新式类。
派生与方法重写
子类可以派生出自己新的属性,在进行属性查找时,子类中的属性名会优先高于父类被查找,例如每个老师还有职称这一属性,我们就需要在Teacher类中定义该类自己的 __init__
覆盖父类的 __init__
属性派生
class People:
school = '清华'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print(f'姓名:{self.name}年龄:{self.age}性别:{self.sex}')
class Student(People):
def __init__(self, name, age, sex, course, stu_id):
People.__init__(self, name, age, sex)
self.course = course
self.stu_id = stu_id
def learn(self):
print(f'{self.name} is learning')
def tell_info(self):
print('我是学生', end='')
People.tell_info(self)
class Teacher(People):
def teach(self):
print(f'{self.name} is teaching')
stu1 = Student('action', 18, 'male', 'python', 1)
print(stu1.__dict__)
# 结果: {'name': 'action', 'age': 18, 'sex': 'male', 'course': 'python', 'stu_id': 1}
stu1.tell_info()
# 结果: 我是学生姓名:action年龄:18性别:male
super方法重写父类方法
class People:
school = '清华'
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
def tell_info(self):
print(f'姓名:{self.name}年龄:{self.age}性别:{self.sex}')
class Student(People):
def __init__(self, name, age, sex, course, stu_id):
super().__init__(name, age, sex)
self.course = course
self.stu_id = stu_id
def learn(self):
print(f'{self.name} is learning')
def tell_info(self):
print('我是学生', end='')
super().tell_info()
class Teacher(People):
def teach(self):
print(f'{self.name} is teaching')
def tell_info(self):
print('我是老师', end='')
# People.tell_info(self)
super().tell_info()
stu1 = Student('action', 18, 'male', 'python', 1)
print(stu1.__dict__)
stu1.tell_info()
调用super()
会得到一个特殊的对象,该对象专用来引用父类的属性,且严格按照 MRO规定的顺序向后查找。
组合
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
def tell_birth(self):
print(f'出生生日: {self.year}-{self.month}-{self.day}')
class Course:
def __init__(self, name, price, period):
self.name = name
self.price = price
self.period = period
def tell_info_course(self):
print(f'课程详细信息: 课程名称: {self.name}, 课程价格:{self.price}, 周期: {self.period}')
class People:
school = '清华'
def __init__(self, name, age, sex, date_obj):
self.name = name
self.age = age
self.sex = sex
self.birth = date_obj
def tell_info(self):
print(f'姓名: {self.name} 年龄: {self.age} 性别: {self.sex}')
class Student(People):
def __init__(self, name, age, sex, stu_id, date_obj):
People.__init__(self, name, age, sex, date_obj)
self.stu_id = stu_id
self.courses = []
def learn(self):
print(f'{self.name} is learning')
def tell_info(self):
print('我是学生', end='')
People.tell_info(self)
class Sale(People):
def __init__(self, name, age, sex, kpi, date_obj):
People.__init__(self, name, age, sex, date_obj)
self.kpi = kpi
def tell_info(self):
print('我是销售', end='')
People.tell_info(self)
class Teacher(People):
def __init__(self, name, age, sex, level, salary, date_obj):
People.__init__(self, name, age, sex, date_obj)
self.level = level
self.salary = salary
self.courses = []
def teach(self):
print(f'{self.name} is teaching')
date_obj = Date(1995, 7, 23)
sale1 = Sale('张三', 20, 'male', 5000, date_obj)
sale1.tell_info()
# 结果: 我是销售姓名: 张三 年龄: 20 性别: male
sale1.birth.tell_birth()
# 结果: 出生生日: 1990-6-23
python = Course('python', 10000, '3month')
linux = Course('Linux', 5000, '2month')
stu1 = Student('王五', 18, 'female', 1, date_obj)
stu1.courses.append(python)
stu1.courses.append(linux)
print(stu1.courses)
# 结果: [<__main__.Course object at 0x108762128>, <__main__.Course object at 0x108762160>]
for course in stu1.courses:
course.tell_info_course()
# 结果:
"""
课程详细信息: 课程名称: python, 课程价格:10000, 周期: 3month
课程详细信息: 课程名称: Linux, 课程价格:5000, 周期: 2month
"""
teacher = Teacher('李四', 20, 'male', 3, 50000, date_obj1)
teacher.courses.append(python)
teacher.courses.append(linux)
for course in teacher.courses:
course.tell_info_course()
# 结果:
"""
课程详细信息: 课程名称: python, 课程价格:10000, 周期: 3month
课程详细信息: 课程名称: Linux, 课程价格:5000, 周期: 2month
"""
2.4 封装
对象
的数据和行为的 整合
就是封装,对封装到对象或者类中的属性,我们还可以严格控制对他们的访问,分两步骤实现:隐藏
与 开放接口
隐藏属性与开放接口
python类中采用 __
开头的方式将属性隐藏起来,(设置成私有的
),但其实这仅仅只是一种变形操作
,类中所有的双下划线开头的属性都会在类定义阶段,检测语法时自动编程 _类名__属性性名
的形式。
将数据隐藏起来就是为了限制类外部对数据的直接操作,然后 类内应提供相应的接口允许外部间接操作数据
,接口之上可以附加额外的逻辑来对数据的类型进行严格的控制
class People:
def __init__(self, name, age):
self.__name = name
self.__age = age
def tell_info(self):
print(f'姓名: {self.__name}, 年龄:{self.__age}')
def set_info(self, name, age):
if not isinstance(name, str):
raise TypeError('name must str')
if not isinstance(age, int):
raise TypeError('age must int')
self.__name = name
self.__age = age
p = People('action', 18)
p.tell_info()
# 结果: 姓名: action, 年龄:18
p.set_info('cross', 30)
p.tell_info()
# 结果: 姓名: cross, 年龄:30
p.set_info('10', 123)
p.tell_info()
# 结果: 姓名: 10, 年龄:123
print(p.__age)
# 结果: AttributeError: type object 'People' has no attribute '__age'
print(p.__dict__)
# 结果: {'_People__name': '10', '_People__age': 123}
print(p._People__age)
# 结果: 123
p.__age = 12
print(p.__dict__)
# 结果: {'_People__name': '10', '_People__age': 123, '__age': 12}
在类内部是可以直接访问双下划线开头的属性
,比如self.__age
,因为在类定义阶段内部双下划线开头的属性统一发生了变行
, 变形操作只在类定义阶段发生一次
,在类定义之后赋值操作,不会变形
隐藏属性与开放接口,
本质
就是为了明确地区分内外
,类内部可以修改封装内的东西而不影响外部调用者的代码
,而类外部只需要拿到一个接口,只要接口名,参数不变,则无论设计者如何改变内部实现代码
,使用者均无需改变代码,
property
python 专门提供了一个装饰器 property
可以将类中的函数 伪装成
对象的数据类型,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果, 使用property
有效的保证了属性的访问的一致性,另外 property
还提供了 设置
和 删除属性
的的功能
class People:
def __init__(self, name, age, height, weight):
self.__name = name
self.__age = age
self.__height = height
self.__weight = weight
@property
def bmi(self):
return self.__weight / (self.__height ** 2)
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
if not isinstance(name, str):
raise TypeError('name must str')
self.__name = name
@name.deleter
def name(self):
raise TypeError('name is not allow delete')
cross = People('cross', 30, 1.80, 75)
print(cross.name)
# 结果: cross
cross.name = 'acton'
print(cross.name)
# 结果: acton
del cross.name
print(cross.name)
# 结果:抛出异常: TypeError: name is not allow delete
2.5 多态与鸭子类型
多态性是指一类事物的多种形态, 可以在不用考虑对象的具体类型的情况下而直接使用对象
,这就需要在设计时,把对象的使用方法统一成一种
不依赖继承,只需要制造出外观和行为相同对象
,同样可以实现不考虑对象类型而使用对象
,这正是python的鸭子类型
,比起继承,鸭子类型在某种程度上实现了程序的松耦合度
class Dog(object):
def work(self): # 父类提供统一的方法,哪怕是空方法
print('指哪打哪...')
class ArmyDog(Dog): # 继承Dog类
def work(self): # 子类重写父类同名方法
print('追击敌人...')
class DrugDog(Dog):
def work(self):
print('追查毒品...')
class Person(object):
def work_with_dog(self, dog): # 传入不同的对象,执行不同的代码,即不同的work函数
dog.work()
ad = ArmyDog()
dd = DrugDog()
p = Person()
p.work_with_dog(ad)
# 结果: 追击敌人...
p.work_with_dog(dd)
# 结果: 追查毒品...
多态性的好处在于增强了程序的灵活性和可扩展性
,比如通过继承Dog
类创建一个新的类,实例化得到的对象 obj,可以使用相同的方式使用obj.work()。
多态性的本质在于不同的类中定义相同的方法名
,这样我们就可以不考虑类而统一用一种方式去使用对象
,可以通过父类引用抽象类
的概念来硬性显示字类必须有某些方法名
import abc
# 指定metaclass属性将类设置为抽象类,抽象类本身只是用来约束子类的,不能被实例化
class Animal(metaclass=abc.ABCMeta):
# 该装饰器限制子类必须定义有一个名为talk的方法
@abc.abstractmethod
def talk(self):
# 抽象方法中无需实现具体的功能
pass
class Cat(Animal):
# 但凡继承Animal的子类都必须遵循Animal规定的标准
def talk(self):
pass
cat = Cat()
# 若子类中没有一个名为talk的方法则会抛出异常TypeError,无法实例化
鸭子类型
import abc
class Animal:
def eat(self):
pass
@abc.abstractmethod
def speak(self):
pass
class Pig:
def speak(self):
print('say heng')
class Dog:
def speak(self):
print('say wang')
class People:
def speak(self):
print('say hello')
class Radio:
def speak(self):
print('say radio')
people = People()
pig = Pig()
dog = Dog()
people.speak()
# 结果: say hello
pig.speak()
# 结果: say heng
dog.speak()
# 结果: sat wang
2.6 类方法和静态方法
类方法
需要用装饰器 @classmethod
来标识其为类方法,对于类方法,第一个参数必须是类对象
,一般以 cls
作为第一个参数
class Dog(object):
__tooth = 10
@classmethod
def get_tooth(cls):
return cls.__tooth
wangcai = Dog()
result = wangcai.get_tooth()
print(result) # 10
当方法中 需要使用类对象
(如访问私有类属性等)时,定义类方法,类方法一般和类属性配合使用
静态方法
需要通过装饰器 @staticmethod
来进行修饰,静态方法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)
, 静态方法 也能够通过 实例对象
和 类对象
去访问。
class Dog(object):
@staticmethod
def info_print():
print('这是一个狗类,用于创建狗实例....')
wangcai = Dog()
# 静态方法既可以使用对象访问又可以使用类访问
wangcai.info_print()
Dog.info_print()
当方法中既不需要使用实例对象
(如实例对象,实例属性),也不需要使用类对象
(如类属性、类方法、创建实例等)时,定义静态方法取消不需要的参数传递
,有利于减少不必要的内存占用和性能消耗
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。