Câu hỏi Ý nghĩa của @classmethod và @staticmethod cho người mới bắt đầu?


Ai đó có thể giải thích cho tôi ý nghĩa của @classmethod và @staticmethod trong python? Tôi cần phải biết sự khác biệt và ý nghĩa.

Trong khả năng hiểu biết của tôi, @classmethod nói với một lớp rằng đó là một phương thức cần được kế thừa vào các lớp con, hoặc ... một cái gì đó. Tuy nhiên, điểm của điều đó là gì? Tại sao không chỉ xác định phương thức lớp mà không thêm @classmethod hoặc là @staticmethod hoặc bất kỳ @ các định nghĩa?

tl; dr:  khi nào tôi có nên sử dụng chúng không, tại sao tôi có nên sử dụng chúng và làm sao Tôi có nên sử dụng chúng không?

Tôi khá tiên tiến với C ++, vì vậy việc sử dụng các khái niệm lập trình nâng cao hơn không phải là vấn đề. Hãy cho tôi một ví dụ C ++ tương ứng nếu có thể.


1258
2017-08-29 13:37


gốc




Các câu trả lời:


Tuy nhiên classmethod và staticmethod khá giống nhau, có sự khác biệt nhỏ về mức sử dụng cho cả hai thực thể: classmethod phải có tham chiếu đến đối tượng lớp làm tham số đầu tiên, trong khi staticmethod có thể không có tham số nào cả.

Thí dụ

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')

Giải trình

Hãy giả sử một ví dụ về một lớp, xử lý thông tin ngày tháng (đây là những gì sẽ là bản mẫu của chúng tôi để nấu ăn):

class Date(object):

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

Lớp này rõ ràng có thể được sử dụng để lưu trữ thông tin về các ngày nhất định (không có thông tin múi giờ; hãy giả sử tất cả các ngày được trình bày trong UTC).

Ở đây chúng tôi có __init__, một khởi tạo điển hình của các cá thể lớp Python, nhận các đối số như là một đối tượng điển hình instancemethod, có đối số không bắt buộc đầu tiên (self) giữ tham chiếu đến một cá thể mới được tạo.

Phương pháp lớp

Chúng tôi có một số nhiệm vụ có thể được thực hiện tốt bằng cách sử dụng classmethodS.

Giả sử rằng chúng tôi muốn tạo ra rất nhiều Date các cá thể lớp có thông tin ngày đến từ nguồn bên ngoài được mã hóa dưới dạng chuỗi định dạng tiếp theo ('dd-mm-yyyy'). Chúng ta phải làm điều đó ở những nơi khác nhau trong mã nguồn của chúng ta trong dự án.

Vì vậy, những gì chúng ta phải làm ở đây là:

  1. Phân tích chuỗi để nhận ngày, tháng và năm dưới dạng ba biến số nguyên hoặc một bộ 3 mục bao gồm biến đó.
  2. Khởi tạo Date bằng cách chuyển các giá trị đó tới cuộc gọi khởi tạo.

Điều này sẽ giống như:

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

Với mục đích này, C ++ có tính năng như quá tải, nhưng Python thiếu tính năng đó, vì vậy đây là khi classmethod áp dụng. Cho phép tạo khác "constructor".

    @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')

Hãy xem xét cẩn thận hơn về triển khai ở trên và xem xét những lợi thế mà chúng tôi có ở đây:

  1. Chúng tôi đã triển khai phân tích cú pháp chuỗi ngày ở một nơi và bây giờ có thể sử dụng lại.
  2. Đóng gói hoạt động tốt ở đây (nếu bạn nghĩ rằng bạn có thể thực hiện phân tích cú pháp chuỗi như một hàm duy nhất ở nơi khác, giải pháp này phù hợp với mô hình OOP tốt hơn nhiều).
  3. cls là một đối tượng chứa bản thân lớp, không phải là một thể hiện của lớp. Nó khá tuyệt vì nếu chúng ta kế thừa Date lớp học, tất cả trẻ em sẽ có from_string cũng được định nghĩa.

Phương pháp tĩnh

Thế còn staticmethod? Nó khá giống với classmethod nhưng không có bất kỳ tham số bắt buộc nào (như phương thức lớp hoặc phương thức thể hiện).

Hãy xem xét trường hợp sử dụng tiếp theo.

Chúng tôi có một chuỗi ngày mà chúng tôi muốn xác nhận bằng cách nào đó. Nhiệm vụ này cũng bị ràng buộc về mặt logic Date lớp chúng tôi đã sử dụng cho đến nay, nhưng vẫn không yêu cầu sự khởi tạo của nó.

Đây là nơi staticmethod có thể hữu ích. Hãy xem đoạn mã tiếp theo:

    @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')

Vì vậy, như chúng ta có thể thấy từ việc sử dụng staticmethod, chúng ta không có quyền truy cập vào lớp đó - về cơ bản nó chỉ là một hàm, gọi là cú pháp giống như một phương thức, nhưng không truy cập vào đối tượng và nó là nội bộ (các trường và các phương thức khác), trong khi classmethod làm.


2163
2017-08-29 14:03



@hayden, về bản thân. instancemethod (không phải là lớp của các phương thức tĩnh) phải có một đối số bắt buộc (được thay thế bằng ví dụ). Mặc dù "self" là tên đối số theo quy ước, bạn được tự do sử dụng bất kỳ mã nhận dạng Python hợp lệ nào mà bạn thích (nhưng không khuyên bạn nên làm điều đó vì nó có thể gây nhầm lẫn cho các nhà phát triển khác đọc mã của bạn). - Rostyslav Dzinko
Biến ban đầu nên được đặt tên cls hoặc là Cls? Tôi cảm thấy bị phân chia - một mặt, đó là một lý lẽ. Mặt khác, bạn đang sử dụng nó để xây dựng một đối tượng. Tôi cảm thấy như Cls(day, month, year) rõ ràng hơn chỉ ra rằng một đối tượng thuộc loại được chỉ định bởi Cls đang được tạo và trả lại, nhưng cls(day, month, year), dường như chỉ là một cuộc gọi hàm bình thường. - ArtOfWarfare
@RostyslavDzinko điều gì sẽ xảy ra nếu bỏ qua @classmethod từ khóa. Ví dụ __init__ mất luôn self như đối số đầu tiên và nó không cần đường python @classmethod - Alexander Cska
@AlexanderCska Điều gì xảy ra là thay vì gọi date2 = Date.from_string (string_date), bạn cần phải nói Date.from_string (Date, string_date). Bởi vì bạn cần phải cung cấp cho các đối tượng lớp học, đó là hơi bất tiện. - zwep
Nếu không có gì khác, bây giờ tôi biết lý do tại sao nó được gọi là tấm nồi hơi! - chris dorn


Câu trả lời của Rostyslav Dzinko rất phù hợp. Tôi nghĩ tôi có thể làm nổi bật một lý do khác mà bạn nên chọn @classmethod kết thúc @staticmethod khi bạn tạo thêm hàm tạo.

Trong ví dụ trên, Rostyslav đã sử dụng @classmethod  from_string như một Nhà máy để tạo Date các đối tượng từ các tham số khác không được chấp nhận. Điều tương tự cũng có thể được thực hiện với @staticmethod như được hiển thị trong mã bên dưới:

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

Như vậy cả hai new_year và millenium_new_year là trường hợp của Date lớp học.

Nhưng, nếu bạn quan sát kỹ, quy trình Nhà máy được mã hóa cứng để tạo Date đối tượng không có vấn đề gì. Điều này có nghĩa là ngay cả khi Date lớp được phân lớp, các lớp con sẽ vẫn tạo đồng bằng Date đối tượng (không có bất kỳ thuộc tính nào của lớp con). Xem điều đó trong ví dụ dưới đây:

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 không phải là một thể hiện của DateTime? WTF? Vâng đó là vì @staticmethod trang trí được sử dụng.

Trong hầu hết các trường hợp, điều này là không mong muốn. Nếu những gì bạn muốn là một phương thức Factory nhận thức được lớp đã gọi nó, thì @classmethod là những gì bạn cần.

Viết lại Date.millenium vì (đó là phần duy nhất của mã trên thay đổi)

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

đảm bảo rằng class không phải là mã hóa cứng mà là học được. cls có thể là bất kỳ lớp con nào. Kết quả object sẽ đúng là một thể hiện của cls. Hãy kiểm tra điều đó.

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"

Lý do là, như bạn biết bây giờ, @classmethod đã được sử dụng thay vì @staticmethod


718
2018-01-30 13:35



1 cho ví dụ gọn gàng: isinstance(datetime2, DateTime) # False. Đây là một vận động mạnh mẽ để sử dụng @classmethod khi vắt các phương pháp nhà máy. - Viet


@classmethod có nghĩa là: khi phương thức này được gọi, chúng ta chuyển lớp như là đối số đầu tiên thay vì cá thể của lớp đó (như chúng ta thường làm với các phương thức). Điều này có nghĩa là bạn có thể sử dụng lớp và các thuộc tính của nó bên trong phương thức đó thay vì một cá thể cụ thể.

@staticmethod có nghĩa là: khi phương thức này được gọi, chúng ta không truyền một thể hiện của lớp cho nó (như chúng ta thường làm với các phương thức). Điều này có nghĩa là bạn có thể đặt một hàm bên trong một lớp nhưng bạn không thể truy cập cá thể của lớp đó (điều này rất hữu ích khi phương thức của bạn không sử dụng cá thể).


196
2017-08-29 13:40





@staticmethod hàm không là gì hơn hàm được định nghĩa bên trong một lớp. Nó có thể gọi được mà không cần khởi tạo lớp trước. Đó là định nghĩa là bất biến thông qua kế thừa.

  • Python không phải khởi tạo phương pháp liên kết cho đối tượng.
  • Nó giúp giảm bớt khả năng đọc mã: xem @staticmethod, chúng ta biết rằng phương thức này không phụ thuộc vào trạng thái của đối tượng;

@classmethod chức năng cũng có thể gọi mà không instantiating lớp, nhưng định nghĩa của nó sau lớp Sub, không phải lớp cha mẹ, thông qua thừa kế, có thể được ghi đè bởi lớp con. Đó là bởi vì đối số đầu tiên cho @classmethodchức năng luôn phải là cls (class).

  • Phương pháp nhà máy, được sử dụng để tạo một cá thể cho một lớp bằng cách sử dụng ví dụ một số loại tiền xử lý.
  • Các phương thức tĩnh gọi các phương thức tĩnh: nếu bạn chia một phương thức tĩnh trong một số phương thức tĩnh, bạn không nên mã hóa tên lớp nhưng sử dụng các phương thức lớp

đây là liên kết tốt với chủ đề này.


53
2018-04-14 07:12





Người ta sẽ sử dụng @classmethod khi anh ta / cô ấy muốn thay đổi hành vi của phương thức dựa trên lớp con nào đang gọi phương thức. hãy nhớ chúng ta có một tham chiếu đến lớp gọi trong một phương thức lớp.

Trong khi sử dụng tĩnh, bạn sẽ muốn hành vi vẫn không thay đổi giữa các lớp con

Thí dụ:

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..."

27
2017-08-21 07:18





Một chút biên soạn

@staticmethod Một cách để viết một phương thức bên trong một lớp mà không cần tham chiếu đến đối tượng mà nó đang được gọi. Vì vậy, không cần phải vượt qua đối số tiềm ẩn như tự hoặc cls. Nó được viết chính xác như cách viết bên ngoài lớp, nhưng nó không phải là không sử dụng trong python bởi vì nếu bạn cần đóng gói một phương thức bên trong một lớp vì phương thức này cần phải là một phần của lớp đó @staticmethod có ích trong đó trường hợp.

@classmethod Điều quan trọng là khi bạn muốn viết một phương thức nhà máy và bởi (các) thuộc tính tùy chỉnh này có thể được đính kèm trong một lớp. Thuộc tính này có thể được ghi đè trong lớp kế thừa.

So sánh giữa hai phương pháp này có thể như sau

Table


26
2017-07-19 16:43





Ý nghĩa của @classmethod và @staticmethod?

  • Phương thức là một hàm trong không gian tên của đối tượng, có thể truy cập dưới dạng một thuộc tính.
  • Phương thức thông thường (ví dụ: instance) nhận được cá thể (chúng ta thường gọi nó là self) làm đối số đầu tiên tiềm ẩn.
  • A lớp học phương pháp được lớp (chúng ta thường gọi nó là cls) làm đối số đầu tiên tiềm ẩn.
  • A tĩnh phương thức không có đối số đầu tiên tiềm ẩn (như một hàm bình thường).

khi nào tôi nên sử dụng chúng, tại sao tôi nên sử dụng chúng và tôi nên sử dụng chúng như thế nào?

Bạn không nhu cầu hoặc trang trí. Nhưng trên nguyên tắc bạn nên giảm thiểu số lượng đối số cho các hàm (xem Clean Coder), chúng rất hữu ích khi thực hiện điều đó.

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()

Đối với cả hai phương thức thể hiện và phương thức lớp, không chấp nhận ít nhất một đối số là một TypeError, nhưng không hiểu ngữ nghĩa của đối số đó là lỗi người dùng.

(Định nghĩa some_function's, ví dụ:

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

và điều này sẽ hoạt động.)

tìm kiếm rải rác trên các cá thể và các lớp:

Một tra cứu rải rác trên một cá thể được thực hiện theo thứ tự này - chúng ta tìm kiếm:

  1. một bộ mô tả dữ liệu trong không gian tên lớp (như một thuộc tính)
  2. dữ liệu trong cá thể __dict__
  3. một bộ mô tả phi dữ liệu trong không gian tên lớp (các phương thức).

Lưu ý, một tra cứu rải rác trên một cá thể được gọi như sau:

instance = Example()
instance.regular_instance_method 

và các phương thức là các thuộc tính có thể gọi được:

instance.regular_instance_method()

các phương thức mẫu

Lập luận, self, được ngầm đưa ra thông qua tra cứu chấm chấm.

Bạn phải truy cập các phương thức cá thể từ các cá thể của lớp.

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

phương pháp lớp

Lập luận, cls, được ngầm đưa ra thông qua tra cứu chấm chấm.

Bạn có thể truy cập phương thức này thông qua một cá thể hoặc lớp (hoặc các lớp con).

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

phương pháp tĩnh

Không có đối số nào được đưa ra ngầm định. Phương thức này hoạt động giống như bất kỳ hàm nào được định nghĩa (ví dụ) trên một không gian tên của mô-đun, ngoại trừ nó có thể được tra cứu

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

Một lần nữa, khi nào tôi nên sử dụng chúng, tại sao tôi nên sử dụng chúng?

Mỗi cái trong số này dần hạn chế hơn trong thông tin mà chúng truyền qua phương thức so với các phương thức mẫu.

Sử dụng chúng khi bạn không cần thông tin.

Điều này làm cho các hàm và phương thức của bạn dễ dàng hơn để giải thích và hủy bỏ.

Đó là dễ dàng hơn để lý do về?

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

hoặc là

def function(y, z): ...

hoặc là

def function(z): ...

Các hàm có ít đối số hơn sẽ dễ hiểu hơn. Họ cũng dễ dàng hơn để unittest.

Đây là giống như dụ, lớp và phương pháp tĩnh. Hãy nhớ rằng khi chúng ta có một thể hiện, chúng ta cũng có lớp học của nó, một lần nữa, hãy tự hỏi mình, đó là lý do dễ dàng hơn ?:

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):
    ...

Ví dụ được xây dựng

Dưới đây là một vài ví dụ được xây dựng yêu thích của tôi:

Các str.maketrans phương thức tĩnh là một hàm trong string mô-đun, nhưng nó thuận tiện hơn để nó có thể truy cập từ str không gian tên.

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

Các dict.fromkeys phương thức lớp trả về một từ điển mới được khởi tạo từ một khóa lặp:

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

Khi được phân lớp, chúng ta thấy rằng nó nhận thông tin lớp như một phương thức lớp, điều này rất hữu ích:

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

Lời khuyên của tôi - Kết luận

Sử dụng các phương thức tĩnh khi bạn không cần các đối số lớp hoặc đối tượng, nhưng hàm này có liên quan đến việc sử dụng đối tượng và nó thuận tiện cho hàm trong không gian tên của đối tượng.

Sử dụng các phương thức lớp khi bạn không cần thông tin cá thể, nhưng cần thông tin lớp có lẽ cho các lớp khác hoặc các phương thức tĩnh của nó, hoặc có lẽ chính nó như là một hàm tạo. (Bạn sẽ không mã hóa lớp để các lớp con có thể được sử dụng ở đây.)


18
2017-10-24 16:09



Điều đó thật tuyệt vời với sự cân bằng hợp lý về giải thích ngắn gọn và các ví dụ vi mô. - abalter


Tôi là người mới bắt đầu trên trang web này, tôi đã đọc tất cả các câu trả lời ở trên và nhận được thông tin mà tôi muốn. Tuy nhiên, tôi không có quyền để upvote. Vì vậy, tôi muốn bắt đầu trên StackOverflow với câu trả lời như tôi đã hiểu.

  • @staticmethod không cần tự hoặc cls như tham số đầu tiên của phương thức
  • @staticmethod và @classmethod chức năng bọc có thể được gọi bằng ví dụ hoặc biến lớp
  • @staticmethod chức năng trang trí ảnh hưởng đến một số loại 'bất động sản' mà thừa kế lớp con không thể ghi đè lên chức năng lớp cơ sở của nó được bao bọc bởi một @staticmethod trang trí.
  • @classmethod cần cls (tên lớp, bạn có thể thay đổi tên biến nếu bạn muốn, nhưng nó không được khuyến cáo) như tham số đầu tiên của hàm
  • @classmethod luôn được sử dụng theo cách phân lớp, thừa kế lớp con có thể thay đổi hiệu ứng của hàm lớp cơ sở, tức là @classmethod hàm lớp cơ sở được bao bọc có thể được ghi đè bởi các lớp con khác nhau.

1
2017-07-23 11:38



Tôi không chắc chắn những gì bạn có ý nghĩa bởi bất biến trong bối cảnh này? - Philip Adler