Python中如何创建自定义对象

在本文中,学习如何创建对象,还将学习多态、封装、方法、属性、超类和继承。

1 对象

对象大致意味着一系列数据(属性)以及一套访问和操作这些数据的方法。使用对象而非全局变量和函数的原因有多个,下面列出了使用对象的最重要的好处。

  • 多态:可对不同类型的对象执行相同的操作,而这些操作就像“被施了魔法”一样能够正常运行。

  • 封装:对外部隐藏有关对象工作原理的细节。

  • 继承:可基于通用类创建出专用类。

1.1 多态(polymorphism)

这大致意味着即便你不知道变量指向的是哪种对象,也能够对其执行操作,且操作的行为将随对象所属的类型(类)而异。这跟R中的对象是一致的,比如通用函数plot()可以对多种不同种类的对象进行操作.

1.2 多态和方法

与对象属性相关联的函数称为方法.比如对于字符串,列表和字典都有很多饭囊发.

'abc'.count('a')
## 1
[1, 2, 'a'].count('a')
## 1

方法count既可以用用于字符串对象,也可以用于列表对象.

同样的,如果我们创建了一个包含count方法的对象,我们也可以像字符串和列表一样使用count方法.

还有,比如运算符+,既可以用于整数也可以用于字符串.另外像函数len(),也都可以用于不同的对象.

新的函数: repr: 将对象转换为供解释器读取的形式.参考下面连接:https://www.runoob.com/python/python-func-repr.html

1.3 封装(encapsulation)

封装(encapsulation)指的是向外部隐藏不必要的细节。

一个简单的例子:

o = OpenObject() # 对象就是这样创建的
o.set_name('Sir Lancelot')
o.get_name()

如何将名称“封装”在对象中呢?没问题,将其作为一个属性即可。属性是归属于对象的变量,就像方法一样。实际上,方法差不多就是与函数相关联的属性.

对象的方法可能修改这些属性,因此对象将一系列函数(方法)组合起来,并赋予它们访问一些变量(属性)的权限,而属性可用于在两次函数调用之间存储值。

1.4 继承

如果你已经有了一个类,并要创建一个与之很像的类(可能只是新增了几个方法),该如何办呢?创建这个新类时,你不想复制旧类的代码,将其粘贴到新类中。

例如,你可能已经有了一个名为Shape的类,它知道如何将自己绘制到屏幕上。现在你想创建一个名为Rectangle的类,但它不仅知道如何将自己绘制到屏幕上,而且还知道如何计算其面积。你不想重新编写方法draw,因为Shape已经有一个这样的方法,且效果很好。那么该如何办呢?让Rectangle继承Shape的方法,使得对Rectangle对象调用方法draw时,将自动调用Shape类的这个方法.

2

2.1 类到底是什么?

本书前面反复提到了类,并将其用作类型的同义词。从很多方面来说,这正是类的定义–一种对象。每个对象都属于特定的类,并被称为该类的实例。

例如,如果你在窗外看到一只鸟,这只鸟就是“鸟类”的一个实例。鸟类是一个非常通用(抽象)的类,它有多个子类:你看到的那只鸟可能属于子类“云雀”。你可将“鸟类”视为由所有鸟组成的集合,而“云雀”是其一个子集。一个类的对象为另一个类的对象的子集时,前者就是后者的子类。因此“云雀”为“鸟类”的子类,而“鸟类”为“云雀”的超类。

通过这样的陈述,子类和超类就很容易理解。但在面向对象编程中,子类关系意味深长,因为类是由其支持的方法定义的。类的所有实例都有该类的所有方法,因此子类的所有实例都有超类的所有方法。有鉴于此,要定义子类,只需定义多出来的方法(还可能重写一些既有的方法)。 例如,Bird类可能提供方法fly,而Penguin类(Bird的一个子类)可能新增方法eat_fish。创建Penguin类时,你还可能想重写超类的方法,即方法fly。鉴于企鹅不能飞,因此在Penguin的实例中,方法fly应什么都不做或引发异常(参见第8章)。

2.2 创建自定义类

一个简单的例子.

__metaclass__ = type # 如果你使用的是Python 2,请包含这行代码
class Person:
  def set_name(self, name):
    self.name = name
  def get_name(self):
    return self.name
  def greet(self):
    print("Hello, world! I'm {}.".format(self.name))

这个示例包含三个方法定义,它们类似于函数定义,但位于class语句内。Person当然是类的名称.使用class语句来创建自定义类,并且创建了独立的命名空间,用于在其中定义函数.

那么参数self是什么呢?他指向对象本身.那么那个是对象呢?

下面通过创建两个实例来说明这一点.

foo = Person()
bar = Person()
foo.set_name('Luke Skywalker')
bar.set_name('Anakin Skywalker')
foo.greet()
## Hello, world! I'm Luke Skywalker.
bar.greet()
## Hello, world! I'm Anakin Skywalker.

对foo调用set_name和greet时,foo都会作为第一个参数自动传递给它们。我将这个参数命名为self,这非常贴切。实际上,可以随便给这个 参数命名,但鉴于它总是指向对象本身,因此习惯上将其命名为self。

显然,self很有用,甚至必不可少。如果没有它,所有的方法都无法访问对象本身——要操作的属性所属的对象。与以前一样,也可以从外部访问这些属性。

foo.name
## 'Luke Skywalker'
bar.name
## 'Anakin Skywalker'

2.3 属性,函数和方法

实际上,方法和函数的区别表现在前一节提到的参数self上。方法(更准确地说是关联的方法)将其第一个参数关联到它所属的实例,因此无需提供这个参数。无疑可以将属性关联到一个普通函数,但这样就没有特殊的self参数了。

我们再看一个简单的例子:

class Class:
  def method(self):
    print('I have a self!')

def function():
  print("I don't...")

我们首先创建了一个叫做Class的类,然后给其创建了一个叫做method的方法.另外,我们还创建了一个函数,叫做function.

instance = Class()
instance.method()
## I have a self!

然后创建一个该类的实例,然后调用method方法.

我们还可以把该实例的方法修改为另外的方法.

instance.method = function
instance.method()
## I don't...

2.4 隐藏

默认情况下,可从外部访问对象的属性.

class Bird:
  name = 'Bird'
  song = 'Squaawk!'
  def sing(self):
    print(self.song)


test = Bird()    
test.name
## 'Bird'
test.song
## 'Squaawk!'

可以看到,在class下面,可以通过上面的方式来定义类的属性.那么如何将类的属性定义为私有,从而在外部无法访问呢?私有属性不能从对象外部访问,而只能通过存取器方法(如get_name和set_name)来访问。

要让方法或属性成为私有的(不能从外部访问),只需让其名称以两个下划线打头即可。

举个简单例子:

class Secretive:
  def __inaccessible(self):
    print("Bet you can't see me ...")
  def accessible(self):
    print("The secret message is:")
    self.__inaccessible()

现在从外部不能访问__inaccessible,但在类中(如accessible中)依然可以使用它。

s = Secretive()
s.accessible()
## The secret message is:
## Bet you can't see me ...
s._Secretive__inaccessible()
## Bet you can't see me ...

总之,你无法禁止别人访问对象的私有方法和属性,但这种名称修改方式发出了强烈的信号,让他们不要这样做。

2.5 类的命名空间

下面两条语句大致等价:

def foo(x): return x * x
foo = lambda x: x * x

它们都创建一个返回参数平方的函数,并将这个函数关联到变量foo。可以在全局(模块)作用域内定义名称foo,也可以在函数或方法内定义。定义类时情况亦如此:在class语句中定义的代码都是在一个特殊的命名空间(类的命名空间)内执行的,而类的所有成员都可访问这个命 名空间。类定义其实就是要执行的代码段,并非所有的Python程序员都知道这一点,但知道这一点很有帮助。

每个实例都可访问这个类作用域内的变量,就像方法一样。

2.6 指定超类

本章前面讨论过,子类扩展了超类的定义。要指定超类,可在class语句中的类名后加上超类名,并将其用圆括号括起。

class Filter:
  def init(self):
    self.blocked = []
  def filter(self, sequence):
    return [x for x in sequence if x not in self.blocked]

class SPAMFilter(Filter): # SPAMFilter是Filter的子类
  def init(self): # 重写超类Filter的方法init
    self.blocked = ['SPAM']

2.7 深入探讨继承

要确定一个类是否是另一个类的子类,可使用内置函数issubclass

如果你有一个类,并想知道它的基类,可访问其特殊属性__bases__

同样,要确定对象是否是特定类的实例,可使用isinstance

如果你要获悉对象属于哪个类,可使用属性__class__

Avatar
Xiaotao Shen
Postdoctoral Research Fellow

Metabolomics, Multi-omics, Bioinformatics, Systems Biology.

Related

Next
Previous
comments powered by Disqus