1
0

Python:面对对象

This commit is contained in:
周中平 2022-11-21 08:39:29 +08:00
parent 3c06eb8d4b
commit f442c15e38
No known key found for this signature in database
GPG Key ID: B1DF9DD42D8E00DC

View File

@ -1,83 +1,108 @@
---
id: 面对对象
title: 面对对象
description: Python 面对对象
keywords:
- Python
- 面对对象
tags:
- Python
sidebar_position: 6
data: 2022年1月28日
author: 7Wate
date: 2022-11-20
---
把一组数据结构和处理它们的方法组成对象object把相同行为的对象归纳为类class通过类的封装encapsulation隐藏内部细节通过继承inheritance实现类的特化specialization和泛化generalization通过多态polymorphism实现基于对象类型的动态分派
面对对象把一组数据结构和处理它们的方法组成对象object把相同行为的对象归纳为类class通过类的封装encapsulation隐藏内部细节通过继承inheritance实现类的特化specialization和泛化generalization通过多态polymorphism实现基于对象类型的动态分派
## 类和对象
类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。
类是对象的蓝图和模板,而对象是类的实例。类是抽象的概念,而对象是具体的东西。
### 类的定义
在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。
- **类(Class)** 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
- **方法:**类中定义的函数。
- **类变量:**类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
- **数据成员:**类变量或者实例变量用于处理类及其实例对象的相关的数据。
- **方法重写:**如果从父类继承的方法不能满足子类的需求可以对其进行改写这个过程叫方法的覆盖override也称为方法的重写。
- **局部变量:**定义在方法中的变量,只作用于当前实例的类。
- **实例变量:**在类的声明中,属性是用变量来表示的,这种变量就称为实例变量,实例变量就是一个用 self 修饰的变量。
- **继承:**即一个派生类derived class继承基类base class的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如有这样一个设计一个Dog类型的对象派生自Animal类这是模拟"是一个is-a"关系例图Dog是一个Animal
- **实例化:**创建一个类的实例,类的具体对象。
- **对象:**通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。
### 类定义
Python中可以使用`class`关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。
```python
class Student(object):
# 语法
class ClassName:
<statement-1>
.
.
.
<statement-N>
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
# 实例
class MyClass:
"""一个简单的类实例"""
i = 12345
def f(self):
return 'hello world'
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# 实例化类
x = MyClass()
# PEP 8要求标识符的名字用全小写多个单词用下划线连接
# 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
def watch_movie(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看岛国爱情大电影.' % self.name)
def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('骆昊', 38)
# 给对象发study消息
stu1.study('Python程序设计')
# 给对象发watch_av消息
stu1.watch_movie()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_movie()
if __name__ == '__main__':
main()
# 访问类的属性和方法
print("MyClass 类的属性 i 为:", x.i)
print("MyClass 类的方法 f 输出为:", x.f())
```
### 访问可见性问题
### 类属性
因为在很多面向对象编程语言中我们通常会将对象的属性设置为私有的private或受保护的protected简单的说就是不允许外界访问而对象的方法通常都是公开的public因为公开的方法就是对象能够接受的消息。在Python中属性和方法的访问权限只有两种也就是公开的和私有的如果希望属性是私有的在给属性命名时可以用两个下划线作为开头下面的代码可以验证这一点。
属性引用使用和 Python 中所有的属性引用一样的标准语法:**obj.name**。类对象创建后,类命名空间中所有的命名都是有效属性名。
### 类方法
在类的内部,使用 **def** 关键字来定义一个方法,与一般函数定义不同,**类方法必须包含参数 self,** 且为第一个参数self 代表的是类的实例。
```python
class Test:
#类定义
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: 'Test' object has no attribute '__foo'
print(test.__foo)
if __name__ == "__main__":
main()
# 实例化类
p = people('runoob',10,30)
p.speak() # runoob 说: 我 10 岁。
```
### 访问可见性
因为在很多面向对象编程语言中我们通常会将对象的属性设置为私有的private或受保护的protected简单的说就是不允许外界访问而对象的方法通常都是公开的public因为公开的方法就是对象能够接受的消息。
在Python中属性和方法的访问权限只有两种也就是公开的和私有的如果希望属性是私有的在给属性命名时可以用两个下划线作为开头。
#### 私有属性
**__private_attrs**:以两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 **self.__private_attrs**
#### 私有方法
**__private_method**:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类的外部调用。在类内部的方法中使用时**self.__private_methods**。
#### 强制访问
但是Python并没有从语法上严格保证私有属性或方法的私密性它只是给私有的属性和方法换了一个名字来妨碍对它们的访问事实上如果你知道更换名字的规则仍然可以访问到它们下面的代码就可以验证这一点。
```python
@ -101,9 +126,192 @@ if __name__ == "__main__":
main()
```
### 专有方法
```python
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__ : 按照索引获取值
__len__ : 获得长度
__cmp__ : 比较运算
__call__ : 函数调用
__add__ : 加运算
__sub__ : 减运算
__mul__ : 乘运算
__truediv__ : 除运算
__mod__ : 求余运算
__pow__ : 乘方
```
### 继承
面对对象的三大特征其一继承:可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。
子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为[里氏替换原则](https://zh.wikipedia.org/wiki/里氏替换原则)。下面我们先看一个继承的例子。
```python
# 语法
class DerivedClassName(modname.BaseClassName):
# 实例
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
#单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
s = student('ken',10,60,3)
s.speak()
```
#### 多继承
Python 同样有限的支持多继承形式。多继承的类定义形如下例:
```python
# 语法
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
<statement-N>
# 实例
class people:
#定义基本属性
name = ''
age = 0
#定义私有属性,私有属性在类外部无法直接进行访问
__weight = 0
#定义构造方法
def __init__(self,n,a,w):
self.name = n
self.age = a
self.__weight = w
def speak(self):
print("%s 说: 我 %d 岁。" %(self.name,self.age))
# 单继承示例
class student(people):
grade = ''
def __init__(self,n,a,w,g):
#调用父类的构函
people.__init__(self,n,a,w)
self.grade = g
#覆写父类的方法
def speak(self):
print("%s 说: 我 %d 岁了,我在读 %d 年级"%(self.name,self.age,self.grade))
# 多重继承之前的准备
class speaker():
topic = ''
name = ''
def __init__(self,n,t):
self.name = n
self.topic = t
def speak(self):
print("我叫 %s我是一个演说家我演讲的主题是 %s"%(self.name,self.topic))
# 多重继承
class sample(speaker,student):
a =''
def __init__(self,n,a,w,g,t):
student.__init__(self,n,a,w,g)
speaker.__init__(self,n,t)
test = sample("Tim",25,80,4,"Python")
test.speak() #方法名同,默认调用的是在括号中参数位置排前父类的方法
```
### 多态
面对对象的三大特征其一继承子类在继承了父类的方法后如果你的父类方法的功能不能满足你的需求可以对父类已有的方法给出新的实现版本这个动作称之为方法重写override。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本当我们调用这个经过子类重写的方法时不同的子类对象会表现出不同的行为这个就是多态poly-morphism
```python
class Parent:
def myMethod(self):
print ('调用父类方法')
class Child(Parent):
def myMethod(self):
print ('调用子类方法')
c = Child() # 子类实例
c.myMethod() # 子类调用重写方法
super(Child,c).myMethod() #用子类对象调用父类已被覆盖的方法
```
### 类的关系
简单的说类和类之间的关系有三种is-a、has-a和use-a关系。
- is-a关系也叫继承或泛化比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系通常称之为关联比如部门和员工的关系汽车和引擎的关系都属于关联关系关联关系如果是整体和部分的关联那么我们称之为聚合关系如果整体进一步负责了部分的生命周期整体和部分是不可分割的同时同在也同时消亡那么这种就是最强的关联关系我们称之为合成关系。
- use-a关系通常称之为依赖比如司机有一个驾驶的行为方法其中的参数使用到了汽车那么司机和汽车的关系就是依赖关系。
## 面向对象进阶
### @property装饰器
### **slots** 魔法
如果我们需要限定自定义类型的对象只能绑定某些属性可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效对子类并不起任何作用。
```python
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
```
### @property 装饰器
如果想访问属性可以通过属性的 getter访问器和 setter修改器方法进行对应的操作。要做到这点就可以考虑使用@property包装器来包装getter和setter方法使得对属性的访问既安全又方便代码如下所示。
@ -148,48 +356,7 @@ if __name__ == '__main__':
main()
```
### __slots__魔法
如果我们需要限定自定义类型的对象只能绑定某些属性可以通过在类中定义__slots__变量来进行限定。需要注意的是__slots__的限定只对当前类的对象生效对子类并不起任何作用。
```python
class Person(object):
# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)
def main():
person = Person('王大锤', 22)
person.play()
person._gender = '男'
# AttributeError: 'Person' object has no attribute '_is_gay'
# person._is_gay = True
```
### 静态方法
### @staticmethod 静态方法
可以使用`@staticmethod`注解定义静态方法,通过直接调用类使用方法。
@ -235,7 +402,7 @@ if __name__ == '__main__':
main()
```
### 类方法
### @classmethod 类方法
Python 还可以在类中定义类方法,类方法的第一个参数约定名为 cls它代表的是当前类相关的信息的对象类本身也是一个对象有的地方也称之为类的元数据对象通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象代码如下所示。
@ -286,140 +453,3 @@ def main():
if __name__ == '__main__':
main()
```
### 类之间的关系
简单的说类和类之间的关系有三种is-a、has-a和use-a关系。
- is-a关系也叫继承或泛化比如学生和人的关系、手机和电子产品的关系都属于继承关系。
- has-a关系通常称之为关联比如部门和员工的关系汽车和引擎的关系都属于关联关系关联关系如果是整体和部分的关联那么我们称之为聚合关系如果整体进一步负责了部分的生命周期整体和部分是不可分割的同时同在也同时消亡那么这种就是最强的关联关系我们称之为合成关系。
- use-a关系通常称之为依赖比如司机有一个驾驶的行为方法其中的参数使用到了汽车那么司机和汽车的关系就是依赖关系。
### 继承
可以在已有类的基础上创建新类,这其中的一种做法就是让一个类从另一个类那里将属性和方法直接继承下来,从而减少重复代码的编写。提供继承信息的我们称之为父类,也叫超类或基类;得到继承信息的我们称之为子类,也叫派生类或衍生类。子类除了继承父类提供的属性和方法,还可以定义自己特有的属性和方法,所以子类比父类拥有的更多的能力,在实际开发中,我们经常会用子类对象去替换掉一个父类对象,这是面向对象编程中一个常见的行为,对应的原则称之为[里氏替换原则](https://zh.wikipedia.org/wiki/里氏替换原则)。下面我们先看一个继承的例子。
```python
class Person(object):
"""人"""
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
return self._name
@property
def age(self):
return self._age
@age.setter
def age(self, age):
self._age = age
def play(self):
print('%s正在愉快的玩耍.' % self._name)
def watch_av(self):
if self._age >= 18:
print('%s正在观看爱情动作片.' % self._name)
else:
print('%s只能观看《熊出没》.' % self._name)
class Student(Person):
"""学生"""
def __init__(self, name, age, grade):
super().__init__(name, age)
self._grade = grade
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, grade):
self._grade = grade
def study(self, course):
print('%s的%s正在学习%s.' % (self._grade, self._name, course))
class Teacher(Person):
"""老师"""
def __init__(self, name, age, title):
super().__init__(name, age)
self._title = title
@property
def title(self):
return self._title
@title.setter
def title(self, title):
self._title = title
def teach(self, course):
print('%s%s正在讲%s.' % (self._name, self._title, course))
def main():
stu = Student('王大锤', 15, '初三')
stu.study('数学')
stu.watch_av()
t = Teacher('骆昊', 38, '砖家')
t.teach('Python程序设计')
t.watch_av()
if __name__ == '__main__':
main()
```
### 多态
子类在继承了父类的方法后可以对父类已有的方法给出新的实现版本这个动作称之为方法重写override。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本当我们调用这个经过子类重写的方法时不同的子类对象会表现出不同的行为这个就是多态poly-morphism
```python
from abc import ABCMeta, abstractmethod
class Pet(object, metaclass=ABCMeta):
"""宠物"""
def __init__(self, nickname):
self._nickname = nickname
@abstractmethod
def make_voice(self):
"""发出声音"""
pass
class Dog(Pet):
"""狗"""
def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)
class Cat(Pet):
"""猫"""
def make_voice(self):
print('%s: 喵...喵...' % self._nickname)
def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()
if __name__ == '__main__':
main()
```