diff --git a/wiki/programming-language/Python/入门/面对对象.md b/wiki/programming-language/Python/入门/面对对象.md index 2d057632..8f53d1b1 100644 --- a/wiki/programming-language/Python/入门/面对对象.md +++ b/wiki/programming-language/Python/入门/面对对象.md @@ -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: + + . + . + . + - # __init__是一个特殊方法用于在创建对象时进行初始化操作 - # 通过这个方法我们可以为学生对象绑定name和age两个属性 - def __init__(self, name, age): - self.name = name - self.age = age - - def study(self, course_name): - print('%s正在学习%s.' % (self.name, course_name)) - - # 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() +# 实例 +class MyClass: + """一个简单的类实例""" + i = 12345 + def f(self): + return 'hello world' + +# 实例化类 +x = MyClass() + +# 访问类的属性和方法 +print("MyClass 类的属性 i 为:", x.i) +print("MyClass 类的方法 f 输出为:", x.f()) ``` -### 访问可见性问题 +### 类属性 -因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。 +属性引用使用和 Python 中所有的属性引用一样的标准语法:**obj.name**。类对象创建后,类命名空间中所有的命名都是有效属性名。 + +### 类方法 + +在类的内部,使用 **def** 关键字来定义一个方法,与一般函数定义不同,**类方法必须包含参数 self,** 且为第一个参数,self 代表的是类的实例。 ```python -class Test: - - 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() +#类定义 +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)) + +# 实例化类 +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): + + . + +# 实例 +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() -```