面向对象的Python:类class(es)和对象object(s)
面向对象的编程是当今最广泛使用的编程范式,几乎所有的编程范式都提供了一种创建和管理对象的方法。下面是对象的含义。
面向对象编程中的对象的表示方法
大多数编程语言都提供了一个叫做 "类 "的关键字来创建一个对象,python也不例外。
那么,什么是类?
一个类定义了蓝图,它可以被实例化来创建对象(s)
一个类定义了可识别的特征和行为,对象将在此基础上被识别。关键字class
在Python中也有同样的作用。
然而,在我们深入了解类和对象之前,让我们先来谈谈Python语言的另一个内置数据结构--字典。
Python 字典 字典是Python的内置数据结构,它用 "键 "和 "值 "对来实现 "哈希 "函数。
哈希函数在键上工作,生成哈希值,并根据这些哈希值存储相应的值。存储取决于哈希函数,这就是为什么字典有时不按我们创建的顺序存储的原因。
字典也可以代表一个对象的静态状态,即不改变其行为的对象,其特点是提到的键(名称)和值对。
作为一个例子,让我们考虑当地社区中人们的邮政地址,我们可以将单个地址表示为
p1 = {'name' : "ABC" , 'street': "中央大街-1" }
p2 = {'name' : "DEF" , 'street': "中央街-2" }
p3 = {'name' : "AXY" , 'street': "中央街-1" }
这里p1, p2 & p3是住在同一条街上的不同人,这些字典描述了对象(即本例中的人)的静态可识别特征。
我们也可以使用Python类来存储对象的静态状态,让我们看看如何做到这一点。
Python类 与其他OOP语言类似,Python中的类提供了一个创建对象的蓝图。就像我们在上面用字典存储邮政地址一样,我们也可以用类的对象来存储它们。
我们可以先创建一个没有预定义蓝图的假类PostalAddress
。Python允许为对象添加运行时成员(Identifiable Characteristics
),我们将使用同样的方法来创建居住在同一地区的人的地址,类似于上面的字典p1和p2。
class PostalAddress:
pass
cP1 = PostalAddress()
# 为ABC这个人创建一个实例
cP1.name = "ABC"
cP1.street = "Central Street - 1"
# 为DEF创建一个实例
cP2 = PostalAddress()
cP2.name = "DEF"
cP2.street = "Central Street - 2"
所以,我们现在有两种方法来创建和保存PostAddress
,即通过字典或使用类的对象。顺便说一下,对象也可以用一个内置的函数__dict__
来表示。
print(cP1.__dict__)
print(cP2.__dict__)
# 输出将与p1和p2相同
{'name': 'ABC', 'street': 'Central Street - 1'}
{'name': 'DEF', 'street': 'Central Street - 2'}
这并不意味着 Python 将对象存储为字典,但我们可以有把握地得出结论,字典也可以表示静态状态的对象。一旦我们使用 __dict__
得到了字典,我们就可以用键来访问元素,就像我们对普通字典一样。
print(p1["name"])
# 从普通字典中打印出 ABC
print(cP1.__dict__["name"])
# 从对象中打印 ABC
根据我们目前所了解的情况,我们可能会形成一种观点,即至少在上述情况下,我们使用字典还是对象并不重要。
好吧,对于创建非常简单的静态对象来说,这可能是真的,但是类不仅提供了添加行为的能力(从而允许改变对象的状态),而且还提供了额外的功能,帮助程序员以简单和可维护的方式管理对象。
在我们研究类所提供的功能之前,让我们先看看我们上面写的代码的一个主要缺点。我们可以把上面写的代码改成
p1 = {'name' : "ABC" , 'street': "Central Street - 1" }
p2 = {'names' : "DEF" , 'street': "Central Street - 2", "NewVar" : "Not Needed" }
# and
class PostalAddress:
pass
cP1 = PostalAddress()
cP1.name = "ABC"
cP2 = PostalAddress()
cP2.names = "DEF"
cP2.NewVar = "Not Needed"
如果你没有注意到,键 "name "被错写成 "names",并且在 "p2 "和 "cP2 "中分别创建了额外的 "NewVar"。然而,两者都是有效的字典和对象。
这就是我们使用类来定义蓝图的地方,这样每个相同类型的对象都有相同的特征。
Python 提供了一个特殊的成员函数叫做 __init__
,每次我们创建一个类的实例时都会调用这个函数,这个函数被用来创建对象的蓝图。
有时 __init__
也被称为构造函数。
__init__
(...)函数 Python 为对象提供了特殊的函数,其前缀和后缀是"__"。
我们先前使用的函数 __dict__
也是一个类似的特殊函数。
当一个对象被创建时,函数 __init__
被调用。这个函数需要一个强制性的参数,称为self
,这里self指的是对象本身。
class PostalAddress:
def __init__(self):
pass
在这个
__init__
函数中,我们创建了局部实例变量,这些变量定义了由这个类构建的对象的状态(可识别的特征)。
class PostalAddress:
def __init__(self):
self.name = "ABC"
self.street = "Central Street - 1"
cP1 = PostalAddress()
print(cP1.__dict__)
self
在Python中不是一个关键字,它只是一个规范,用self这个词作为对自己的引用,然而,我们可以为它命名任何我们想要的东西,比如说
class PostalAddress。
def __init__(theClassInstance):
theClassInstance.name = "ABC"
theClassInstance.street = "Centeral Street - 1"
然而,self
的用法是如此的普遍,以至于它已经成为类中方法的一个事实上的关键词,很难找到一个程序员除了使用self
之外还使用其他的东西。在进一步的例子中,我也将使用self
来描述类的实例,即对象。
向类中添加函数
类中的函数提供了对象的行为方面。让我创建一个函数来打印对象的当前状态。这个函数将被称为prnInfo,看起来像
class PostalAddress:
def __init__(self):
self.name = "ABC"
self.street = "Centeral Street - 1"
def prnInfo(self):
print("Name =>", self.name, " Street =>", self.street)
cP1 = PostalAddress()
cP1.prnInfo()
就像 __init__
函数一样,所有的函数都把self
作为一个强制参数。然而,当我们调用函数时,我们不需要向它传递任何参数,因为 python 自动将对象实例作为 self
我们也可以使用类而不是对象来调用函数,然而,如果我们想通过类来调用方法,我们需要明确地传递实例,即 self,因为当我们通过类来调用实例时,Python 不知道该把哪个实例作为 self。
使用类的调用应该看起来像
cP1 = PostalAddress()
# 使用类来调用函数,我们提供实例
PostalAddress.prnInfo(cP1)
# 这与
cP1.prnInfo()
参数化的__init__
PostalAddress这个类类似于一个静态字典,因为我们已经硬编码了名称和街道。然而,我们想用不同的值来创建不同的实例对象。
为了达到同样的目的,在__init__
函数中把这些值作为输入参数,正如下面的代码所描述的那样
# PostalAddress将名字和街道作为输入参数
class PostalAddress:
def __init__(self, name, street):
self.name = name
self.street = street
def prnInfo(self):
print("Name =>", self.name, " Street =>", self.street)
cP1 = PostalAddress("ABC", "Central Street - 1")
cP1.prnInfo()
cP2 = PostalAddress("DEF", "Central Street - 2")
cP2.prnInfo()
__init__
的多种变化
不像其他面向对象的编程语言,如 "Java "和 "C++",Python 没有提供用不同参数集重载
__init__
的机制。任何后来的函数定义都会覆盖之前的函数。
避免这种情况的一个方法是为 __init__
函数提供默认参数,这样我们就可以使用变量参数创建实例。
class PostalAddress。
def __init__(self, name = "Default Name", street = "Central Street - 1")。
self.name = name
self.street = street
cP0 = PostalAddress();
# 0参数
cP1 = PostalAddress("ABC")
# 1个参数
cP2 = PostalAddress("DEF", "Central Street - 2")
# 2参数
在多个函数中创建实例变量
并不是说我们只能在__init__
函数中创建实例变量。我们可以在类的任何成员函数中做同样的事情。
唯一的问题是,这些实例变量只有在创建实例变量的函数被调用时才会被创建。
class PostalAddress:
def __init__(self, name = "Default Name", street = "Central Street - 1"):
self.name = name
self.street = street
def createMember(self):
self.newMember = "Temporary Value"
cP0 = PostalAddress();
print(cP0.__dict__);
# prints {'name': 'Default Name', 'street': 'Central Street - 1'}
cP0.createMember();
print(cP0.__dict__);
# print {'name': 'Default Name', 'street': 'Central Street - 1', 'newMember': 'Temporary Value'}
类变量
到目前为止,我们一直以居住在一个社区的人为例来编写我们的代码。大多数时候,一个小社区里的人的地址都有一些共同的信息,比如说邮政编码。
这意味着PostalAddress类的所有对象必须有相同的邮政编码。 实现这个目的的一个方法是在__init__方法中硬编码邮政编码,这样它就可以在这个类中创建的每一个对象中使用。
class PostalAddress:
def __init__(self, name = "Default Name", street = "Central Street - 1"):
self.name = name
self.street = street
self.postalcode = 12345 # 硬编码邮政编码
cP0 = PostalAddress();
print(cP0.__dict__); # 打印 {'postalcode': 12345, 'name': 'Default Name', ' street': 'Central Street - 1'}.
cP1 = PostalAddress("ABC")
print(cP1.__dict__); # prints {'postalcode': 12345, 'name': 'ABC', ' street': 'Central Street - 1'}
然而,如果我们以后想改变邮政编码,这就带来了一个问题。唯一的办法是为每个对象实例手动改变,如
cP0.postalcode = 54321
cP1.postalcode = 54321
...
...
cPn.postalcode = 54321
这不仅是繁琐的,而且容易出错。幸运的是,在这些情况下,我们可以通过类变量来拯救我们的生活。
类变量是在类中声明的变量,但不在类的任何方法中。
这个变量对PostalAddress的所有实例都可用。让我们看看我们如何定义类变量
class PostalAddress:
postalCode = 12345; # class Variable
def __init__(self, name = "Default Name", street = "Central Street - 1")。
self.name = name
self.street = street
cP0 = PostalAddress()
print(cP0.postalCode) # print 12345
在研究如何改变类变量以使类的每一个实例都被更新为新的postalCode之前,我们需要了解类变量和实例变量之间的一个主要区别
类变量被定义为类的一部分,而不是实例的一部分
如果我们使用 print(cP0.__dict__)
打印实例 cP0 的字典,我们不会在其中找到 'postalCode' 。
print(cP0.__dict__)
# printts {'street': 'Central Street - 1', 'name': ' Default Name'}
为了找到'postalCode',我们需要打印该类的字典,可以打印为
print(PostalAddress.__dict__)
# 打印 {'postalCode': 12345. .... }以及其他许多东西
这是 python 的查找序列,它首先查看 "实例",如果没有找到变量/函数,它就查看 "类"。
这就是我们可以使用实例对象打印cP0.postalCode
的原因。
类的方法 可以通过两种方式改变类的变量,要么使用类本身,要么使用类的函数。如果使用类本身来改变,我们需要写下以下代码
PostalAddress.postalCode = 54321
这不仅会改变现有实例的 "postalCode",也会改变这一行执行后创建的所有实例的 "postalCode"。
# 实例创建前
cP0 = PostalAddress()
cP1 = PostalAddress()
PostalAddress.postalCode = 54321
# 改变后创建的实例
cP2 = PostalAddress()
print(cP0.postalCode) # 打印出54321
print(cP1.postalCode) # 打印54321
print(cP2.postalCode) #打印54321
另一种实现的方法是使用Python中的类方法。类方法是一种特殊的方法,它以类为参数,而不是以实例即self为参数。
在定义类方法的时候,我们需要使用关键字@classmethod
明确地告诉Python这个方法是一个类方法。
class PostalAddress:
postalCode = 12345; # class Variable
def __init__(self, name = "Default Name", street = "Central Street - 1"):
self.name = name
self.street = street
@classmethod
def newPostalCode(cls, newcode):
cls.postalCode = newcode
实例方法将self作为强制参数,而类方法将class作为强制参数。
在这两种情况下,我们都不需要明确地传递Instance或Class,Python会自动处理它。
现在我们可以通过Instance或者Class来调用这个新方法newPostalCode。在这两种情况下,最终的结果都是一样的。下面是我们如何使用实例调用这个方法的
cP0 = PostalAddress()
cP1 = PostalAddress()
# 使用实例调用
cP0.newPostalCode(9999)
cP2 = PostalAddress()
print(cP0.postalCode) # 打印9999
print(cP1.postalCode) # 打印9999
print(cP2.postalCode) # 打印 9999
而我们可以使用类来调用
cP0 = PostalAddress()
cP1 = PostalAddress()
# 使用实例调用
PostalAddress.newPostalCode(9999)
cP2 = PostalAddress()
print(cP0.postalCode) # 打印9999
print(cP1.postalCode) # 打印9999
print(cP2.postalCode) #打印9999
这就是关于面向对象的Python的基本介绍,应该足以让读者理解类和对象之间的区别,并允许他们编写基本的面向对象的Python代码。
本文由 mdnice 多平台发布