让你的模子更好用:类进阶

类属性和类方法

之前介绍类的时候,我们学习了对象属性和对象方法。对象属性和对象方法是绑定在对象这个层次上的,也就是说需要先创建对象,然后才能使用对象的属性和方法。

即:

对象 = 类()
对象.属性
对象.方法()

除此之外,还有一种绑定在类这个层面的属性和方法,叫作类属性和类方法。使用类属性和类方法时,不用创建对象,直接通过类来使用。

类属性和类方法的使用方式:

类.属性
类.方法()

类属性的定义

类属性如何定义呢?

只要将属性定义在类之中方法之外即可。如下面的 属性1属性2

class 类:
    属性1 = X
    属性2 = Y

    def 某方法():
        pass

举个例子:

class Char:
    letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    digits = '0123456789'

这里定义了类 Char,有两个类属性,这两个类属性分别包含所有大写字母和所有数字。可以通过类名来使用这两个类属性,此时无需创建对象:

>>> Char.letters
’ABCDEFGHIJKLMNOPQRSTUVWXYZ’
>>> Char.digits
’0123456789’

当然,类所创建出来的对象也能使用类属性:

>>> char = Char()
>>> char.letters
’ABCDEFGHIJKLMNOPQRSTUVWXYZ’
>>> char.digits
’0123456789’

类方法的定义

再来看下类方法的定义方法。类方法的定义需要借助于装饰器,装饰器具体是什么后续文章中会介绍,目前只要知道用法即可。

定义类方法时,需要在方法的前面加上装饰器 @classmethod。如下:

class 类:
    @classmethod
    def 类方法(cls):
        pass

注意与对象方法不同,类方法的第一个参数通常命名为 cls,表示当前这个类本身。我们可以通过该参数来引用类属性,或类中其它类方法。

类方法中可以使用该类的类属性,但不能使用该类的对象属性。因为类方法隶属于类,而对象属性隶属于对象,使用类方法时可能还没有对象被创建出来。

在之前 Char 类的基础上,我们加上随机获取任意字符的类方法。代码如下:

import random

class Char:
    letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    digits = '0123456789'

    @classmethod
    def random_letter(cls):
        return random.choice(cls.letters)

    @classmethod
    def random_digits(cls):
        return random.choice(cls.digits)

方法 random_letter() 可以从属性 letters 随机获取一个大写字母;方法 random_digits() 可以从属性 digits 随机获取一个数字。它们函数体中的 random.choice() 可从指定序列中随机获取一个元素。

>>> Char.random_digits()
‘8’
>>> Char.random_letter()
‘X’

扩展:import 语句不仅可用于模块的开头,也可用于模块的任意位置,如函数中。

静态方法

与类方法有点相似的是静态方法,静态方法也可直接通过类名来调用,不必先创建对象。不同在于类方法的第一个参数是类自身(cls),而静态方法没有这样的参数。如果方法需要和其它类属性或类方法交互,那么可以将其定义成类方法;如果方法无需和其它类属性或类方法交互,那么可以将其定义成静态方法。

定义静态方法时,需要在方法的前面加上装饰器 @staticmethod。如下:

class 类:
    @staticmethod
    def 静态方法():
        pass

之前的例子中,我们可以从类属性 lettersdigits中随机获取字符,如果想要自己来指定字符的范围,并从中获取一个随机字符,可以再来定义一个静态方法 random_char()。如:

import random

class Char:
    letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    digits = '0123456789'

    @classmethod
    def random_letter(cls):
        return random.choice(cls.letters)

    @classmethod
    def random_digits(cls):
        return random.choice(cls.digits)

    @staticmethod
    def random_char(string):
        if not isinstance(string, str):
            raise TypeError('需要字符串参数')

        return random.choice(string)

静态方法 random_char从传入的字符串中随机挑选出一个字符。之所以定义成静态方法,是因为它无需与类属性交互。

>>> Char.random_char(‘imooc2019’)
‘0’
>>> Char.random_char(‘imooc2019’)
‘m’

私有属性、方法

类属性 letters 和 digits

是为了提供给同一个类中的类方法使用,但我们可以通过类或对象从类的外部直接访问它们。比如:

Char.letters
Char.digits
>>> Char.letters
’ABCDEFGHIJKLMNOPQRSTUVWXYZ’
>>> Char.digits
’0123456789’

有时我们不想把过多的信息暴露出去,有没有什么方法来限制属性不被类外部所访问,而是只能在类中使用?

答案是有的,我们只需要在命名上动动手脚,将属性或方法的名称用 __(两个下划线)开头即可。如:

import random

class Char:
    __letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    __digits = '0123456789'

    @classmethod
    def random_letter(cls):
        return random.choice(cls.__letters)

    @classmethod
    def random_digits(cls):
        return random.choice(cls.__digits)

从类外部访问这两个属性看看:

>>> Char.__letters
Traceback (most recent call last):
     File “”, line 1, in
AttributeError: type object ‘Char’ has no attribute ‘__letters’
>>> Char.__digits
Traceback (most recent call last):
     File “”, line 1, in
AttributeError: type object ‘Char’ has no attribute ‘__digits’

可以看到,修改过后的属性不能直接被访问了,解释器抛出 AttributeError 异常,提示类中没有这个属性。

但位于同一个类中的方法还是可以正常使用这些属性:

>>> Char.random_letter()
‘N’
>>> Char.random_digits()
‘4’

像这样以 __(两个下划线)开头的属性我们称为私有属性。顾名思义,它是类所私有的,不能在类外部使用。

上述是以类属性作为示例,该规则对类方法、对象属性、对象方法同样适用。只需在名称前加上 __(两个下划线)即可。

我们也可以使用 _(一个下划线)前缀来声明某属性或方法是私有的,但是这种形式只是一种使用者间的约定,并不在解释器层面作限制。如:

class Char:
    _letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    _digits = '0123456789'

上面的 _letters_digits 也可看作私有属性,只不过是约定上的私有,通过名称前缀 _(一个下滑线)向使用者告知这是私有的。但你如果非要使用,依然可以用。

>>> Char._letters
’ABCDEFGHIJKLMNOPQRSTUVWXYZ’
>>> Char._digits
’0123456789’

特殊方法

类中以 __ 开头并以 __结尾的方法是特殊方法,特殊方法有特殊的用途。它们可以直接调用,也可以通过一些内置函数或操作符来间接调用,如之前学习过的 __init__()__next__()

特殊方法很多,在这里我们简单例举几个:

  • 1.__init__()

__init__() 是非常典型的一个特殊方法,它用于对象的初始化。在实例化类的过程中,被自动调用。

  • 2.__next__()

在迭代器章节中我们讲过,对迭代器调用next() 函数,便能生成下一个值。这个过程的背后,next() 调用了迭代器的 __next__() 方法。

  • 3.__len__()

你可能会好奇,为什么调用 len() 函数时,便能返回一个容器的长度?原因就是容器类中实现了 __len__() 方法,调用 len() 函数时将自动调用容器的 __len__() 方法。

  • 4.__str__()

在使用 print() 函数时将自动调用类的 __str__() 方法。如:

class A:
    def __str__(self):
        return '这是 A 的对象'
>>> a = A()
>>> print(a)
这是 A 的对象`
  • 5.__getitem__()

诸如列表、元素、字符串这样的序列,我们可以通过索引的方式来获取其中的元素,这背后便是 __getitem__() 在起作用。

'abc'[2] 即等同于 'abc'.__getitem__(2)

>>> ‘abc’[2]
‘c’
>>> ‘abc’.__getitem__(2)
‘c’

继承

如果想基于一个现有的类,获取其全部能力,并以此扩展出一个更强大的类,此时可以使用类的继承。被继承的类叫作父类(或基类),继承者叫作子类(或派生类)。

定义时,子类名称的后面加上括号并写入父类。如下:

class 父类:
    父类的实现

class 子类(父类):
    子类的实现

例如:

class A:
    def __init__(self):
        self.apple = 'apple'

    def have(self):
        print('I hava an', self.apple)

class B(A):
    def who(self):
        print('I am an object of B')
>>> b = B()

>>> b.who()
I am an object of B

>>> b.apple
’apple’

>>> b.have()
I hava an apple

可以看到,虽然类 B 中什么都没定义,但由于 B 继承自 A,所以它拥有 A 的属性和方法。

子类 B 中当然也可以定义自己的属性。

class B(A):
    def __init__(self):
        super().__init__()
        self.banana = 'banana'
>>> b = B()
>>> b.banana
’banana’

我们在 B 中定义 __init__() 方法,并在其中定义了 B 自己的属性 banana。

super().__init__() 这一句代码是什么作用?由于我们在子类中定义了 __init__() 方法,这会导致子类无法再获取父类的属性,加上这行代码就能在子类初始化的同时初始化父类。super() 用在类的方法中时,返回父类对象。

子类中出现和父类同名的方法会怎么样?答案是子类会覆盖父类的同名方法。

class A:
    def __init__(self):
        self.apple = 'apple'

    def have(self):
        print('I hava an', self.apple)

class B(A):
    def __init__(self):
        super().__init__()
        self.banana = 'banana'

    def have(self):
        print('I hava an', self.banana)
>>> b = B()
>>> b.have()
I hava an banana

继承链

子类可以继承父类,同样的,父类也可以继承它自己的父类,如此一层一层继承下去。

class A:
    def have(self):
        print('I hava an apple')

class B(A):
    pass

class C(B):
    pass
>>> c = C()
>>> c.have()
I hava an apple

在这里 A 是继承链的顶端,B 和 C 都是它的子类(孙子类)。

其实 A 也有继承,它继承自 object。任何类的根源都是 object 类。如果一个类没有指定所继承的类,那么它默认继承 object。

A 中也可以显式指明其继承于 object :

class A(object):
    def have(self):
        print('I hava an apple')

如果想要判断一个类是否是另一个类的子类,可以使用内置函数 issubclass() 。用法如下:

>>> issubclass(C, A)
True
>>> issubclass(B, A)
True
>>> issubclass(C, B)
True

多继承

子类可以同时继承多个父类,这样它便拥有了多份能力。

定义时,子类名称后面加上括号并写入多个父类。如下:

class A:
    def get_apple(self):
        return 'apple'

class B:
    def get_banana(self):
        return 'banana'

class C(A, B):
    pass
>>> c = C()
>>> c.get_apple()
‘apple’
>>> c.get_banana()
‘banana’

此时 C 便同时拥有了 A 和 B 的能力。

results matching ""

    No results matching ""