领先的免费Web技术教程,涵盖HTML到ASP.NET

网站首页 > 知识剖析 正文

python散装笔记——38: Classes - 类(2)

nixiaole 2025-01-13 16:45:59 知识剖析 17 ℃

3: 基本继承

Python 中的继承基于 Java、C++ 等其他面向对象语言中使用的类似思想。一个新类可以从一个现有类派生出来,如下所示。

class BaseClass(object):
  pass

class DerivedClass(BaseClass):
  pass

BaseClass 是已经存在的(父)类,而 DerivedClass 是继承(或子类化)了 BaseClass 属性的新(子)类。注:从 Python 2.2 开始,所有类都隐式地继承自对象类,对象类是所有内置类型的基类。

我们在下面的示例中定义了一个父类 Rectangle,它隐式地继承自 object:

class Rectangle():
  def __init__(self, w, h):
    self.w = w
    self.h = h
    
  def area(self):
    return self.w * self.h
    
  def perimeter(self):
    return 2 * (self.w + self.h)

矩形类可以作为定义正方形类的基类,因为正方形是矩形的特例。

class Square(Rectangle):
  def __init__(self, s):
    # call parent constructor, w and h are both s
    super(Square, self).__init__(s, s)
    self.s = s

Square 类将自动继承 Rectangle 类以及对象类的所有属性。super()用于调用 Rectangle 类的 __init__() 方法,本质上是调用基类的任何重载方法。

注意:在 Python 3 中,super() 不需要参数。

派生类对象可以访问和修改基类的属性:

r.area()
# Output: 12
r.perimeter()
# Output: 14

s.area()
# Output: 4
s.perimeter()
# Output: 8

与继承相关的内置函数

issubclass(DerivedClass, BaseClass):如果 DerivedClassBaseClass 的子类,则返回 True

isinstance(s, Class):如果 s 是 ClassClass 的任何派生类的实例,则返回 True

# subclass check
issubclass(Square, Rectangle)
# Output: True

# instantiate
r = Rectangle(3, 4)
s = Square(2)

isinstance(r, Rectangle)
# Output: True
isinstance(r, Square)
# Output: False

# A rectangle is not a square
isinstance(s, Rectangle)
# Output: True
# A square is a rectangle
isinstance(s, Square)
# Output: True

4: 猴子补丁

在这种情况下,"猴子补丁" 指的是在类被定义后为其添加一个新变量或方法。例如,我们把类 A 定义为

class A(object):
  def __init__(self, num):
    self.num = num
    
  def __add__(self, other):
    return A(self.num + other.num)

但现在我们想在代码后面添加另一个函数。假设这个函数如下

def get_num(self):
  return self.num

但我们如何将其添加为 A 中的方法呢?很简单,我们只需在 A 中加入赋值语句即可。

A.get_num = get_num

为什么会这样呢?因为函数和其他对象一样是对象,而方法是属于类的函数。

函数 get_num 应可用于所有现有的(已创建的)以及 A 的新实例。

类(或其子类)的所有实例自动使用这些新增功能。例如

foo = A(42)

A.get_num = get_num

bar = A(6);

foo.get_num() # 42

bar.get_num() # 6

需要注意的是,与其他一些语言不同的是,这种方法不适用于某些内置类型,而且也不被认为是好的风格。

5: 新样式类 vs. 旧样式类

Python 2.2 引入了新样式类来统一类和类型。它们继承自顶层的 object 类型。新样式类是用户定义的类型,与内置类型非常相似。

# new-style class
class New(object):
  pass

# new-style instance
new = New()

new.__class__
# <class '__main__.New'>
type(new)
# <class '__main__.New'>
issubclass(New, object)
# True

旧式类不继承于 object。旧式实例总是通过内置的 instance 类型来实现。

# old-style class
class Old:
  pass

# old-style instance
old = Old()

old.__class__
# <class __main__.Old at ...>
type(old)
# <type 'instance'>
issubclass(Old, object)
# False

在 Python 3 中,旧式的类被删除了。

Python 3 中的新式类隐式地继承自 object,因此不再需要指定 MyClass(object)

class MyClass:
  pass

my_inst = MyClass()

type(my_inst)
# <class '__main__.MyClass'>
my_inst.__class__
# <class '__main__.MyClass'>
issubclass(MyClass, object)
# True

6: 类方法:替代初始化器

类方法是构建类实例的另一种方法。举个例子来说明。

假设我们有一个相对简单的 Person 类:

class Person(object):
  def __init__(self, first_name, last_name, age):
    self.first_name = first_name
    self.last_name = last_name
    self.age = age
    self.full_name = first_name + " " + last_name
  def greet(self):
    print("Hello, my name is " + self.full_name + ".")

如果能提供一种方法来构建该类的实例,指定全名而不是分别指定姓和名,可能会很方便。一种方法是让 last_name 成为一个可选参数,并假设如果没有给出该参数,我们就传递了全名:

class Person(object):
  def __init__(self, first_name, age, last_name=None):
    if last_name is None:
      self.first_name, self.last_name = first_name.split(" ", 2)
    else:
      self.first_name = first_name
      self.last_name = last_name
      self.full_name = self.first_name + " " + self.last_name
      self.age = age
      
    def greet(self):
      print("Hello, my name is " + self.full_name + ".")

不过,这段代码有两个主要问题:

  1. 参数 first_namelast_name 现在会引起误解,因为您可以为 first_name 输入全名。此外,如果有更多的情况和/或更多的参数具有这种灵活性,if/elif/else 分支就会很快变得很烦人。
  2. 虽然不那么重要,但仍值得指出:如果 last_name 是 None,但 first_name 没有通过空格分割成两个或多个内容,该怎么办?我们又多了一层输入验证和/或异常处理...

输入类方法。我们将创建一个名为 from_full_name 的单独初始化器,并使用(内置的) classmethod 装饰器对其进行装饰,而不是使用单一的初始化器。

class Person(object):
  def __init__(self, first_name, last_name, age):
    self.first_name = first_name
    self.last_name = last_name
    self.age = age
    self.full_name = first_name + " " + last_name

  @classmethod
  def from_full_name(cls, name, age):
    if " " not in name:
      raise ValueError
    first_name, last_name = name.split(" ", 2)
    return cls(first_name, last_name, age)

  def greet(self):
    print("Hello, my name is " + self.full_name + ".")

注意 from_full_name 的第一个参数是 cls 而不是 self。类方法应用于整个类,而不是给定类的实例(self 通常表示实例)。因此,如果 cls 是我们的 Person 类,那么 from_full_name 类方法的返回值就是 Person(first_name, last_name, age),它会使用 Person__init__ 来创建 Person 类的实例。特别是,如果我们要创建 Person 的子类 Employee,那么 from_full_name 也会在 Employee 类中工作。

为了证明它的工作原理符合预期,让我们在不使用 __init__ 分支的情况下,用多种方法创建 Person 的实例:

>>> bob = Person("Bob", "Bobberson", 42)
>>> alice = Person.from_full_name("Alice Henderson", 31)
>>> bob.greet()
Hello, my name is Bob Bobberson.
>>> alice.greet()
Hello, my name is Alice Henderson.

其他参考资料:

最近发表
标签列表