類方法替代初始化器

類方法提供了構建類例項的替代方法。為了說明,讓我們看一個例子。

假設我們有一個相對簡單的 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_nameNone,但 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 + ".")

注意 cls 而不是 self 作為 from_full_name 的第一個引數。類方法應用於整個類,而不是給定類的例項(這是 self 通常表示的)。所以,如果 cls 是我們的 Person 類,那麼 from_full_name 類方法的返回值是 Person(first_name, last_name, age),它使用 Person__init__ 來建立 Person 類的例項。特別是,如果我們要建立 Person 的子類 Employee,那麼 from_full_name 也可以在 Employee 類中工作。

為了表明這是按預期工作的,讓我們以不止一種方式建立 Person 的例項,而不需要在 __init__ 中進行分支:

In [2]: bob = Person("Bob", "Bobberson", 42)

In [3]: alice = Person.from_full_name("Alice Henderson", 31)

In [4]: bob.greet()
Hello, my name is Bob Bobberson.

In [5]: alice.greet()
Hello, my name is Alice Henderson.

其他參考: