c

codeye

V1

2022/10/15阅读:20主题:默认主题

面向对象的Python:类(classes)和对象object(s)

面向对象的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代码。

分类:

后端

标签:

后端

作者介绍

c
codeye
V1