title用法 - subplot title python




@classmethod和@staticmethod對初學者的意義? (8)

有人能向我解釋@classmethod@staticmethod在python中的含義嗎? 我需要知道其中的差異和意義。

據我所知, @classmethod告訴一個類,它是一種應該被繼承到子類或者...的方法。 但是,這有什麼意義呢? 為什麼不定義類方法而不添加@classmethod@staticmethod或任何@定義?

tl; dr:我應該什麼時候使用它們, 為什麼要使用它們,我應該如何使用它們?

我用C ++很先進,所以使用更高級的編程概念應該不成問題。 如果可能,請隨意給我一個相應的C ++示例。


@classmethod@staticmethod含義?

  • 方法是對象名稱空間中的一個函數,可以作為屬性訪問。
  • 常規(即實例)方法將實例(我們通常稱它為self )作為隱含的第一個參數。
  • 方法將類(我們通常稱之為cls )作為隱式的第一個參數。
  • 靜態方法不會獲得隱含的第一個參數(如常規函數)。

我應該什麼時候使用它們,為什麼要使用它們,我應該如何使用它們?

不需要任何裝飾器。 但根據你應該盡量減少函數參數數量的原則(參見Clean Coder),它們對於做這件事很有用。

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

對於實例方法和類方法,不接受至少一個參數是TypeError,但不理解該參數的語義是用戶錯誤。

(定義some_function的,例如:

some_function_h = some_function_g = some_function_f = lambda x=None: x

這將起作用。)

虛線查找實例和類:

按照此順序對實例進行虛線查找 - 我們尋找:

  1. 類名稱空間中的數據描述符(如屬性)
  2. 實例中的數據__dict__
  3. 類名稱空間(方法)中的非數據描述符。

請注意,實例上的虛線查找是這樣調用的:

instance = Example()
instance.regular_instance_method 

和方法是可調用的屬性:

instance.regular_instance_method()

實例方法

爭論, self ,通過虛線查找隱含給出。

您必須從類的實例中訪問實例方法。

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

類方法

參數cls隱含地通過虛線查找給出。

您可以通過實例或類(或子類)來訪問此方法。

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

靜態方法

沒有任何參數是隱含給出的。 這個方法就像在模塊名字空間中定義的任何函數一樣,除了它可以被查找

>>> print(instance.a_static_method())
None

再次,我應該什麼時候使用它們,我為什麼要使用它們?

它們中的每一個在它們通過方法和實例方法的信息中越來越嚴格。

當你不需要這些信息時使用它們。

這使得你的函數和方法更容易推理和單元測試。

哪個更容易推理?

def function(x, y, z): ...

要么

def function(y, z): ...

要么

def function(z): ...

參數較少的函數更容易推理。 他們也更容易進行單元測試。

這些類似於實例,類和靜態方法。 請記住,當我們有一個實例時,我們也有它的課程,再次問自己,這更容易推理?:

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

內置的例子

以下是我最喜歡的一些內置示例:

str.maketrans靜態方法是string模塊中的一個函數,但它可以更方便地從str命名空間訪問。

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

dict.fromkeys類方法返回一個從一個迭代鍵實例化的新字典:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

當子類化時,我們看到它將類信息作為類方法獲得,這非常有用:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

我的建議 - 結論

不需要類或實例參數時使用靜態方法,但函數與對象的使用相關,並且函數在對象的名稱空間中很方便。

當您不需要實例信息時使用類方法,但需要類信息可能用於其他類或靜態方法,或者可能本身作為構造函數。 (你不會硬編碼這個類,以便在這裡使用子類。)


Rostyslav Dzinko的回答非常合適。 我想我可以突出顯示另一個原因,當你創建額外的構造函數時,你應該選擇@classmethod over @staticmethod

在上面的例子中,Rostyslav使用@classmethod from_string作為Factory來從不可接受的參數創建Date對象。 @staticmethod也可以做到這一點,如下面的代碼所示:

class Date:
  def __init__(self, month, day, year):
    self.month = month
    self.day   = day
    self.year  = year


  def display(self):
    return "{0}-{1}-{2}".format(self.month, self.day, self.year)


  @staticmethod
  def millenium(month, day):
    return Date(month, day, 2000)

new_year = Date(1, 1, 2013)               # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object. 

# Proof:
new_year.display()           # "1-1-2013"
millenium_new_year.display() # "1-1-2000"

isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True

因此, new_yearnew_year都是Date類的實例。

但是,如果仔細觀察,Factory過程是硬編碼的,無論如何創建Date對象。 這意味著即使Date類是子類,子類仍然會創建普通的Date對象(沒有子類的任何屬性)。 請看下面的例子:

class DateTime(Date):
  def display(self):
      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)


datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False

datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class

datetime2不是DateTime的實例嗎? WTF? 那是因為使用了@staticmethod裝飾器。

在大多數情況下,這是不受歡迎的。 如果你想要的是一個Factory方法,它知道調用它的類,那麼@classmethod就是你需要的。

Date.millenium重寫為(這是上述代碼的唯一變化部分)

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

確保class不是硬編碼的,而是學到的。 cls可以是任何子類。 得到的object將正確地成為cls一個實例。 我們來測試一下。

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True


datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"

原因是,就像你現在知道的那樣, @classmethod被用來代替@staticmethod


@staticmethod函數不過是一個在類中定義的函數。 它不需要首先實例化類就可以調用。 它的定義通過繼承是不可變的。

  • Python不必為對象實例化綁定方法
  • 它簡化了代碼的可讀性:看到@staticmethod ,我們知道該方法不依賴於對象本身的狀態;

@classmethod函數也可以在沒有實例化類的情況下調用,但其定義如下Sub類,而不是Parent類,通過繼承,可以被子類重寫。 這是因為@classmethod函數的第一個參數必須始終是cls (class)

  • 工廠方法 ,用於使用例如某種預處理為類創建實例。
  • 調用靜態方法的靜態方法 :如果在多個靜態方法中拆分靜態方法,則不應該對類名稱進行硬編碼,而應使用類方法

here是這個主題的好鏈接。


一點彙編

@staticmethod一種在類中編寫方法而不參考它所調用的對象的方法。 所以不需要傳遞像self或cls這樣的隱含參數。 它的寫法與在類之外編寫的完全相同,但它在python中沒有用處,因為如果需要在類中封裝方法,因為此方法需要成為該類的一部分@staticmethod便於使用案件。

@classmethod當你想寫一個工廠方法並且通過這個自定義屬性(s)可以附加到一個類中時,這很重要。 該屬性可以在繼承的類中重寫。

這兩種方法的比較如下


儘管classmethodstaticmethod非常相似,但兩種實體的用法略有不同: classmethod必須對類對象的引用作為第一個參數,而staticmethod根本沒有參數。

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')

說明

讓我們假設一個類的例子,處理日期信息(這將是我們的樣板烹飪):

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

這個類顯然可以用來存儲有關某些日期的信息(沒有時區信息;假設所有日期都以UTC表示)。

在這裡,我們有__init__ ,它是Python類實例的典型初始化方法,它接收參數作為典型的instancemethod ,具有第一個非可選參數( self ),該參數保存對新創建實例的引用。

類方法

我們有一些使用classmethod可以很好地完成的任務。

我們假設我們想創建大量的Date類實例,它們將來自外部源的日期信息編碼為下一個格式('dd-mm-yyyy')的字符串。 我們必須在項目中源代碼的不同位置執行此操作。

所以我們在這裡必須做的是:

  1. 將字符串解析為接收日,月和年作為三個整型變量或由該變量組成的三項目元組。
  2. 通過將這些值傳遞給初始化調用來實例化Date

這看起來像:

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

為此,C ++具有重載等功能,但Python缺乏這種功能 - 所以這裡是classmethod適用的情況。 讓我們創建另一個“ 構造函數 ”。

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

date2 = Date.from_string('11-09-2012')

讓我們仔細看看上面的實現,並回顧一下我們在這裡有什麼優勢:

  1. 我們在一個地方實現了日期字符串解析,現在它可以重用。
  2. 封裝在這里工作得很好(如果你認為你可以在別處實現字符串解析作為一個單獨的函數,這個解決方案更適合OOP範例)。
  3. cls是持有類本身的對象,而不是的實例。 這很酷,因為如果我們繼承我們的Date類,所有的孩子都會定義from_string

靜態方法

staticmethod呢? 它與classmethod非常相似,但不包含任何必需的參數(如類方法或實例方法)。

我們來看下一個用例。

我們有一個我們想要以某種方式驗證的日期字符串。 這個任務也邏輯上綁定到我們迄今為止使用的Date類,但仍然不需要實例化它。

這裡是staticmethod方法可能有用的地方。 我們來看下一段代碼:

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

    # usage:
    is_date = Date.is_date_valid('11-09-2012')

因此,從staticmethod使用中我們可以看到,我們沒有任何訪問類的東西 - 它基本上只是一個函數,在語法上稱為方法,但不能訪問對象,它的內部(字段和另一個方法),而classmethod呢。


我是這個網站的初學者,我已閱讀所有以上答案,並獲得我想要的信息。 但是,我沒有權利上傳。 所以我想用我的理解來回答的問題。

  • @staticmethod不需要self或cls作為方法的第一個參數
  • @staticmethod@classmethod包裝函數可以通過實例或類變量調用
  • @staticmethod裝飾函數會影響子類繼承的某種“不可變屬性”,它無法覆蓋由@staticmethod裝飾器包裝的基類函數。
  • @classmethod需要cls(類名稱,如果需要,可以更改變量名稱,但不建議)作為函數的第一個參數
  • @classmethod總是以子類方式使用,子類繼承可能會改變基類函數的效果,即@classmethod包裝後的基類函數可能被不同的子類覆蓋。

當他/她想要根據哪個子類調用方法來改變方法的行為時,可以使用@classmethod 。 請記住,我們在類方法中引用了調用類。

在使用靜態時,您希望行為在子類中保持不變

例:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."

簡而言之,@classmehtod將常規方法轉換為工廠方法。

我們來看一個例子:

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

沒有@ class方法,你應該努力一個一個地創建實例,並且它們都是scartted。

book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey

以@classmethod為例

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'
    @classmethod
    def book1(cls):
        return cls('Learning Python', 'Mark Lutz')
    @classmethod
    def book2(cls):
        return cls('Python Think', 'Allen B Dowey')

測試它:

In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey

看到? 實例在類定義中成功創建,並且它們一起收集。

總之,@classmethod裝飾器將常規方法轉換為工廠方法,使用classmethods可以根據需要添加盡可能多的替代構造函數。





class-method