Python 学习笔记(OOP)

mac2024-01-31  52

Python 学习笔记:面对对象 (一)


文章目录

Python 学习笔记:面对对象 (一)@[toc]1. 类声名一个类属性和方法构造函数和析构函数封装和保护UML 类图 2. 对象什么是对象初始化和删除访问成员 3. 例子声名一个车辆类(Vehicule)使用车辆类常见错误 4. 类的补充多个构造函数对象的比较对象的输出聚会还是组合?

1. 类

声名一个类


一个类将一个具体概念抽象化(如轿车可以抽象为一个车辆类,一辆轿车便是车辆类的一个实例)

整理重组: 各种数据(默认数据类型或另一个类),使其适应各种该类的实列各种数据的处理方法(方法),使其通用于所有该类的实例 允许: 限制外部对类的字段的访问控制外部对类的字段的修改

术语


类中的各种数据称为类的属性类中对数据的各种处理方法称为类的方法属性和方法共称类的字段

UML 图表示


图中的大方框由几个小隔间组成 隔间1:类的名称(一般其首字母大写)隔间2:属性隔间3:方法可以加入更多的隔间(如 exception,…) 对细节的描述可以有不同的级别 可以忽略其中的属性和/或方法

语法


关键字:class类的名字(约定俗成是类名的首字母大写)一个类的字段可以视为一整个代码块,所以其声名要以 : 引号结束

声名一个类


class Maclasse : # 不用对其属性声名 # 声名方法 def uneMethode( self ) : ...

注意


在 Python 中,类的各种属性一般在类的构造函数中声名

属性和方法


属性的 UML 图表示


可访问性 : 名称: 类型+ integer# real~ String- Maclasse

属性的名称和类型必须写出

注意 1:类型


UML 图是独立于编程语言的,其属性的类型不一定强制对应于 Python 中的数据类型

Ex :integer( UML ) = int( Python ), real( UML ) = float( Python )

注意 2:多重性


一个属性可以表示同类型的多个对象,如下3种情况:

我们知道元素数量的最大值和最小值:

+nomAttribut :typeAttribut[valMin..valMax]

+nomAttribut: typeAttribut[val](最大值与最小值相等)

我们知道元素数量的最小值:

+nomAttribut :typeAttribut[valMin..*]

不知道元素的个数:

nomAttribut :typeAttribut[*]

矩阵的表示:nomAttribut :typeAttribut[lMin..lMax, cMin..cMax]

注意 3


类的方法的名称和括号以及其中的参数必须写出方法的返回值和其参数的写法同属性

类的方法


声名一个类的方法与声名传统的函数的方式一样与函数一样,方法名称的首字母一般用小写类的方法必须有一个额外的第一个参数名称,按惯例使用 self self代表的是该类的实例self不是 Python 关键字,也可以使用其他字符代替,如cxk

UML 图表示方法


-calculerAire( ) : real ⇒ \Rightarrow 一个方法 calculerAire 可见性为私有,无参数,返回值为实数

+afficherSommet( x : integer ) ⇒ \Rightarrow 一个方法 afficherSommet 可见性为公有,有一个为整型 x 的参数,无返回值

示例


# 私有方法 calculerAire 无参数 def __calculerAire( self ) : # ... return aire # 在 Python 中类型为 float # 公有方法 afficherSommet 有一个 int 型参数 x def afficherSommet( self, x ) : # ...

构造函数和析构函数


初始化和删除一个对象的通用系统机制构造函数在每次创建/实例化一个新对象时被调用析构函数在每次删除一个对象时被调用

注意 :


Python 中删除操作是由 Garbage Collector 自动管理的(隐式的析构器)然而我们可以显式的定义一个析构函数(__del__ 方法)

UML 图表示构造函数


为了表示一个构造函数:

我们可以套用类的方法的模板给构造函数命名为类的名称

语法:+ <<create>> NomClasse( parametres )

声名一个构造函数


同类的方法一样,但是其名称为 __init__用处: 初始化它所影响的所有属性 属性的值可以通过构造函数的参数传递这些值也可以是默认的(如今天的日期,空的列表…) 构造函数没有任何返回值!

示例


UML:

+ <<creat>> Vehicule( ) ⇒ \Rightarrow Vehicule 类的无参数构造函数

+ <<creat>> Personne( nom : String, prenom : String ) ⇒ \Rightarrow Personne 类的构造函数,参数为两个字符串

Python:

class Vehicule : # 无参数构造函数 def __init__( self ) : # ... class Personne : # 参数为两个字符串的构造函数 def __init__( self, nom, prenom ) : # ...

以上两种类完整的 UML 图和代码:

Vehicule 类:

2个属性:anne 和 marque2个方法:afficher 和 calculerAge

import datetime class Vehicule : def __init__( self, anne, marque ) : self.anne = anne self.marque = marque def afficher( self ) : print( "Voiture de marque ", self.marque, " fabrique en ", self.anne ) def calculerAge( self ) : now = datetime.datetime.now( ) return now.year - self.anne

Personne 类:

3个属性:nom,prenom 和 dateNaissance2个方法:afficher 和 calculerAge import datetime class Personne : def __init__( self, nom, prenom, dateNaissance ) : self.nom = nom self.prenom = prenom self.dateNaissance = dateNaissance def afficher( self ) : print( self.prenom, " ", self.nom ) def calculerAge( self ) : now = datetime.datetime.now( ) return now.year - self.dateNaissance.year

注意 :

在类的方法中访问类的属性或调用类中其他方法可以使用 self.

封装和保护


保护数据和其处理方法


封装概括为:

尽可能隐藏类字段以限制其被外部访问。

目的:

保护和控制类的属性的值 ⇒ \Rightarrow 隐藏一些数据和一些处理方法(封装)以避免类外的方法可以修改/使用它们

名称UMLPython可访问性公共+无无限制保护#名称前加 _只能其本身与子类进行访问私有-名称前加 __只允许这个类本身访问

可访问性:

我们可以通过区分符自己定义一个类每一部分的可访问性可访问性的选择依靠各属性和方法的性质 如车辆类中,方法如“发动” → \rightarrow +(公共)方法如“点燃火花塞”,“加油” → \rightarrow -(私有) 公共方法 = 功能接口 / 对象的可见部分:负责访问其他的部分

访问属性


封装的主要原则:

对属性的访问(读或写)是否要受到控制或禁止 ⇒ \Rightarrow 私有属性: 汽车的变速杆的位置必须在1至6档之间学生的分数必须在0到100分之间登录银行账户的密码 如果属性的内部实现与它的表示不对应 ⇒ \Rightarrow 私有属性: 某压强以单位 mmHg 保存,但是要以单位 Pa 输出

注意 :

对象本身(即其中包含的方法)始终可以直接访问其属性(即使它们是私有的)。

Getter,Setter 和 Property:

好处: 受控的从外部访问私有属性如果内部实现发生改变而不会影响其他类(如:在 Vehicule 类中不储存车辆的生产年份,而是保存1900年与生产日期之差)在读/写后加入调用所需的其他函数(如:在一个复数更新了它的实部后计算其极坐标表达式) 它们的实现不是必须的(如:不会读/写一个银行密码)

Getter 方法:

以只读的形式访问(即显示)没有任何的参数输入(除了self)在声名方法前一行加装饰器:@property

Setter 方法:

受控的以写的方式访问(即修改)有一个参数输入(除了 self)在声名方法前一行加装饰器:@nomAttribut.setter

注意:

在 Python 中,getter 和 setter 都叫 property约定俗成的,它们的名称为其对应的属性

示例:

""" 这种写法很不 Python """ class Vehicule : def __init__( self, annee, marque ) : # 内部实现:与1900之差 if ( annee > 1900 ) and ( annee <= now.year ) : self.__annee = annee - 1900 self.marque = marque

注意 :以上的写法不是 Python 的风格(不要这么写!)

# 很 Python 的写法 class Vehicule : def __init__( self, annee, marque ) : # 内部实现:与1900之差 self.annee = annee # 私有属性并调用 setter # 访问不受控制 => 公共属性 self.marque = marque @property def annee( self ) : # 将其转为同名只读属性的 getter 方法 return self.__annee + 1900 @annee.setter def annee( self, annee ) : if ( annee > 1900 ) and ( annee <= now.year ) : self.__annee = annee - 1900

优点:如果可见的 annee 属性在公共区域被改变,仍无需修改构造函数

一些其他的例子:

class Velo : # ... @property def numVitesse( self ) : return self.__numVitesse @numVitesse.setter def numVitesse( self, num ) : if ( num > 0 ) and ( num < 7 ) : self.__numVitesse = num class Date : # ... @property def jour( self ) : return self.__jour @jour.setter def jour( self, jour ) : if ( jour > 0 ) and ( jour < 31 ) : self.__jour = jour return True # 修改成功 return False # 修改失败

UML 类图


UML 类图


目的:

描述一个应用中的各种类和它们之间的静态关系

类之间的关系


关联(association):当一个类知道另一个类时,可以用关联。

如企鹅需要知道气候的变化,需要了解气候规律,企鹅和气候就是关联关系。关联关系用实线箭头来表示

聚合(aggregation): 聚合表示一种弱的拥有关系,体现的是A对象可以包含B对象,但B对象不是A对象的一部分

如大雁和雁群,每只大雁都是属于一个雁群,一个雁群可以有多只大雁聚合关系用空心的菱形 + 实线箭头来表示

组合(composition): 是一种强的拥有关系,体现了严格的部分和整体的关系,部分和整体的生命周期一样

如鸟和其翅膀就是组合关系,因为它们是部分和整体的关系,并且翅膀和鸟的生命周期是相同的组合关系用实习的菱形 + 实线箭头来表示 注意:

合成关系的连线两端还有一个数字1和数字2,这被称为基数。表明这一端的类可以有几个实例,很显然,一个鸟应该有两只翅膀

2. 对象

什么是对象


对象定义:

对抽象描述的具体实现一个对象(可类比为一个变量) = = = 一个类(可类别为基本数据类型)的实例一个对象(如具体的一辆车)是由一个类(如车这个大类)创建(实例化)而来的每个类都有一个“建造蓝图”(即构造函数)用以实例化它的各个对象

类和对象的区别: 一个对象的存在周期:

创建:调用构造函数:var = NomClasse( parametres )使用:调用方法,读/写其中的属性删除:调用析构函数 此步骤可省略(隐式的调用析构函数)显式的调用析构函数:del var

初始化和删除


为了创建/实例化一个对象,必须:

将通过调用构造函数创建的对象存储在一个变量中:该变量指向新对象

示例:

v1 = Vehicule( ) # 创建一个 Vehicule 类的对象 # 创建一个 Personne 类的对象(以及一个 Date 类的) p1 = Personne( "Norris", "Chuck", Date( 10, 3, 1940 ) )

隐式的析构函数:Garbage Collector

原则:

在 Python 中,当不再有对一个对象的引用时,可以确定该程序将不再能够访问它

⇒ \Rightarrow 对象将进入回收序列, 内存空间将被释放(但并不总是立即释放)

示例:

""" 我们可以直接显式的调用析构函数来释放内存 """ v = Vehicule( 2014, "Renault" ) # 使用 v 的代码 # ... # 删除对象 v del v # ...

访问成员


访问对象的成员(属性和方法)可以通过使用 .操作符实现语法:nomObjet.nomChamp

示例:

v1 = Vehicule( ) v1.marque = "Renault" # 此操作支持对公共属性进行 v1.annee = 2014 # 或有 setter 方法的私有属性 v1.afficher( ) # 显示:"Vehicule de marque Renault fabrique en 2014"

3. 例子

声名一个车辆类(Vehicule)


示例:车辆类:

对于一辆车,我们有如下:

数据 品牌生产年份 数据处理 计算车龄(现在年份 - 生产年份)输出显示和车辆有关的信息(品牌,生产年份) 限制 车的生产年份必须在1900年到今年

分析:

抽象为一个车辆类2个属性 marque:字符串,公共annee:整数,私有 2个方法(不算构造函数) calculerAge:无输入参数,返回一个整型数,公共afficher:无输入参数,无返回值,公共

画出 UML 图: 在 Python 中声名 Vehicule 类:

# 导入包含类中需要用到的函数的模块 from datetime import datetime class Vehicule : # 1)声名构造函数 def __init__( self, marque, annee = datetime.now( ).year ) : self.marque = marque self.annee = annee # 年份为默认的今年 # 2)声名 property @property def annee( self ) : return self.__annee + 1900 @annee.setter def annee( self, annee ) : anneeNow = datetime.now( ).year if ( annee > 1900 ) and ( annee <= anneeNow ) : self.__annee = annee - 1900 # 3)声名方法 def afficher( self ) : res = "Vehicule" res += " de marque " + self.marque res += " fabrique en " + str( self.annee ) print( res ) def calculerAge( self ) : anneeNow = datetime.now( ).year return anneeNow - self.annee

使用车辆类


# 创建一辆车 v1 = Vehicule ( " Peugeot ", 2014 ) # 显示它的所有信息 v1. afficher ( ) # 显示它的车龄 print ( "v1 a", v1. calculerAge ( ), " ans ." ) # 创建另一辆车 v2 = Vehicule ( " Renault " ) # 修改它的生产年份 v2. annee = 2012

常见错误


v1.afficher( ) # 使用了还未创建的对象 v2 = Vehicule( " Renault ", 2012 ) afficher( v2 ) # 调用方法的语法错误 del v2 v2.afficher( ) # 调用了一个已经删除的对象的方法 v3 = Vehicule( ) # 调用构造函数的方法错误

4. 类的补充

多个构造函数


在 Python 中,可以定义多个同名方法当一个类中存在多个构造函数该如何管理? 如前述 Vehicule 类,只给构造函数品牌而不给年份

解决方法:

使用默认值(如前述 Vehicule 类中默认年份为今年)使用类方法

一个类方法是一种可以被一个类或一个对象(不推荐!)调用的方法:

要在声名类方法的前一行使用装饰器:@classmethod类方法的第一个参数为 cls 而不是 self对于构造函数,它调用该类的构造函数,并将构造函数的结果(即新的对象)返回

注意: 因为它适用于该类,所以它不能访问对象的属性

示例:

from datetime import datetime class Vehicule : # "正常的"构造函数:所有参数都已知 def __init__( self, annee, marque ) : self.annee = annee self.marque = marque # 类方法(第二种构造函数) @classmethod def formMarqueOnly( cls, marque ) : annee = datetime.now( ).year # annee 的值为默认今年(缺啥补啥) return cls( annee, marque ) # 调用类的构造函数 # ... v1 = Vehicule.formMarqueOnly( "Renault" ) v1.afficher( ) # 输出为:Vehicule de marque Renault de 2019

对象的比较


默认有,操作符 == 比较两个对象的地址,但不比较两者的值/属性

v1 = Vehicule( "Peugeot", 2014 ) v2 = Vehicule( "Peugeot", 2014 ) print( v1 == v2 ) # 输出 "False"

__eq__ 方法:

所有的对象都有 __eq__ 方法,它可以比较两个对象是否一样:

语法:def __eq__( self, other )

相等性测试通过一下步骤:

先确保比较的对象是存在的(即不能是None )且要和被比较的对象是同类(关键字:isinstance)比较两个对象是否指向同一个地址(地址相同则是同一个对象,关键字:is)比较两个对象的各种属性

注意:

如果比较的属性类型是类,则还必须在这些类中实现 __eq__ 方法只有当一个类中实现了 __eq__方法,它才能调用== 操作符

示例:

def __eq__( self , other ) : # 1/ 我们先比较该对象是否为 Vehicule 类 if other not isinstance( Vehicule ) : return False # 2/ 地址相同 => 相同的对象 if self is other : return True # 到此处我们已知该对象是 Vehicule 类 # 3/ 我们比较两个对象的属性 ( marque 和 annee ) if self.marque != other.marque : return False if self.annee != other.annee : return False # 所有属性都相等 => 对象相等 return True v1 = Vehicule( "Peugeot", 2014 ) v2 = Vehicule( "Peugeot", 2014 ) print( v1 == v2 ) # 输出 "True"

对象的输出


v1 = Vehicule( " Peugeot ", 2014 ) print( v1 ) # "< __main__ . Vehicule object at 0 x7fa3cf95c400 >" 通过 print 函数显示对象,显示的是其被储存的地址如何显示一个车的品牌和它的生产年份(即属性)而不调用 afficher( ) 方法?

__str__ 方法:

所有的对象都有 __str__ 方法,它可以将对象转化为字符串:语法:def __str__( self )如果被转换的属性也是一个类,那个类中同样也要实现 __str__ 方法在 UML 图中,我们使用 +toString( ) : String 表示该方法

示例:

def __str__( self ) : res = " Vehicule " res += " de marque " + self.marque res += " fabrique en " + str( self.annee ) return res v1 = Vehicule( 2014 , " Peugeot " ) print( v1 , "qui a", v1.calculerAge( ), " ans " ) # " Vehicule de marque Peugeot fabrique en 2014 qui a 5 ans "

聚会还是组合?


聚合:构造函数可以采用已经创建的对象(在这种情况下为共享对象),也可以自己创建对象(通过调用构造函数)

组合:为了防止属性被共享,构造函数总是调用构造函数(即该类的一个属性是个类)来创建其属性。(同生共死)

示例: 聚合:

class Violoniste : def __init__( self , violon ) : self.violon = violon # violon 是一个已经被创建的对象 @classmethod def fromViolon( cls , d, l ) : violon = Violon( d, l ) # 创建了一个新的 violon return cls( violon ) # 调用 violoniste 的构造函数

组合:

class Violon : def __init__( self , d, l ) : # 直接调用属性的构造函数(同生共死) self.cordes = [ Corde( d[i], l[i] ) for i in range(4) ]

Getter & Setter:

个人意见:仅针对组合为了防止属性被共享,不要创建 getter 和 setter
最新回复(0)