Sự khác biệt của phương thức lớp trong Python: bị ràng buộc, không bị ràng buộc và tĩnh


242

Sự khác biệt giữa các phương thức lớp sau là gì?

Có phải cái này là tĩnh và cái kia không?

class Test(object):
  def method_one(self):
    print "Called method_one"

  def method_two():
    print "Called method_two"

a_test = Test()
a_test.method_one()
a_test.method_two()

18
Không có sự khác biệt nào ngoài định nghĩa method_two () không hợp lệ và cuộc gọi của nó không thành công.
anatoly techtonik

14
@techtonik: Không có gì sai với định nghĩa của phương thức_two! Nó được gọi trong một thông số không chính xác / không hợp lệ, tức là có thêm một đối số.
0xc0de

1
Bạn là cả hai phương thức cá thể , không phải phương thức lớp. Bạn tạo một phương thức lớp bằng cách áp dụng @classmethodcho định nghĩa. Tham số đầu tiên phải được gọi clsthay vì selfvà sẽ nhận đối tượng lớp chứ không phải là một thể hiện của lớp của bạn: Test.method_three()a_test.method_three()tương đương.
Lutz Prechelt

Tại sao bạn muốn tạo một định nghĩa hàm mà không có selfđối số? Có một trường hợp sử dụng mạnh mẽ cho điều này?
alpha_989

Câu trả lời:


412

Trong Python, có một sự phân biệt giữa các phương thức ràng buộckhông ràng buộc .

Về cơ bản, một cuộc gọi đến một hàm thành viên (như method_one), một hàm bị ràng buộc

a_test.method_one()

được dịch sang

Test.method_one(a_test)

tức là một cuộc gọi đến một phương thức không liên kết. Do đó, một cuộc gọi đến phiên bản của method_twobạn sẽ thất bại với mộtTypeError

>>> a_test = Test() 
>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given) 

Bạn có thể thay đổi hành vi của một phương thức bằng cách sử dụng trang trí

class Test(object):
    def method_one(self):
        print "Called method_one"

    @staticmethod
    def method_two():
        print "Called method two"

Trình trang trí cho siêu dữ liệu mặc định tích hợp type(lớp của một lớp, xem câu hỏi này ) để không tạo các phương thức ràng buộc cho method_two.

Bây giờ, bạn có thể gọi phương thức tĩnh cả trên một cá thể hoặc trên lớp trực tiếp:

>>> a_test = Test()
>>> a_test.method_one()
Called method_one
>>> a_test.method_two()
Called method_two
>>> Test.method_two()
Called method_two

17
Tôi ủng hộ câu trả lời này, nó vượt trội hơn tôi. Làm tốt lắm Torsten :)
freespace

24
trong python 3 phương thức không liên kết được phản đối. thay vào đó chỉ là một chức năng.
boldnik

@boldnik, tại sao bạn nói các phương thức không liên kết bị phản đối? phương pháp tĩnh là vẫn còn hiện diện trong tài liệu: docs.python.org/3/library/functions.html#staticmethod
alpha_989

195

Các phương thức trong Python là một điều rất, rất đơn giản một khi bạn đã hiểu những điều cơ bản của hệ thống mô tả. Hãy tưởng tượng lớp sau:

class C(object):
    def foo(self):
        pass

Bây giờ chúng ta hãy xem lớp đó trong shell:

>>> C.foo
<unbound method C.foo>
>>> C.__dict__['foo']
<function foo at 0x17d05b0>

Như bạn có thể thấy nếu bạn truy cập foothuộc tính trên lớp, bạn sẽ nhận được một phương thức không liên kết, tuy nhiên bên trong bộ lưu trữ lớp (dict) có một hàm. Tại sao vậy Lý do cho điều này là lớp của lớp của bạn thực hiện một __getattribute__giải quyết các mô tả. Nghe có vẻ phức tạp, nhưng không phải. C.foogần tương đương với mã này trong trường hợp đặc biệt đó:

>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>

Đó là bởi vì các hàm có một __get__phương thức làm cho chúng mô tả. Nếu bạn có một thể hiện của một lớp thì nó gần giống nhau, chỉ đó Nonelà thể hiện của lớp:

>>> c = C()
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x17bd4d0>>

Bây giờ tại sao Python làm điều đó? Bởi vì đối tượng phương thức liên kết tham số đầu tiên của hàm với thể hiện của lớp. Đó là nơi bản thân đến từ. Bây giờ đôi khi bạn không muốn lớp của mình biến một hàm thành một phương thức, đó là nơi staticmethodphát huy tác dụng:

 class C(object):
  @staticmethod
  def foo():
   pass

Trình staticmethodtrang trí kết thúc lớp của bạn và thực hiện một hình nộm __get__trả về hàm được bọc là hàm chứ không phải là một phương thức:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Hy vọng rằng giải thích nó.


12
Trình staticmethodtrang trí kết thúc lớp của bạn (...) Cụm từ này hơi sai lệch vì lớp đang được bọc là lớp của phương thức foochứ không phải lớp foođược định nghĩa.
Piotr Dobrogost

12

Khi bạn gọi một thành viên lớp, Python sẽ tự động sử dụng một tham chiếu đến đối tượng làm tham số đầu tiên. Biến selfthực sự không có nghĩa gì, nó chỉ là một quy ước mã hóa. Bạn có thể gọi nó gargaloonếu bạn muốn. Điều đó nói rằng, lời kêu gọi method_twosẽ tăngTypeError , vì Python đang tự động chuyển một tham số (tham chiếu đến đối tượng cha của nó) cho một phương thức được xác định là không có tham số.

Để thực sự làm cho nó hoạt động, bạn có thể thêm nó vào định nghĩa lớp của bạn:

method_two = staticmethod(method_two)

hoặc bạn có thể sử dụng @staticmethod chức năng trang trí .


4
Bạn có nghĩa là "cú pháp trang trí chức năng @staticmethod".
tzot

11
>>> class Class(object):
...     def __init__(self):
...         self.i = 0
...     def instance_method(self):
...         self.i += 1
...         print self.i
...     c = 0
...     @classmethod
...     def class_method(cls):
...         cls.c += 1
...         print cls.c
...     @staticmethod
...     def static_method(s):
...         s += 1
...         print s
... 
>>> a = Class()
>>> a.class_method()
1
>>> Class.class_method()    # The class shares this value across instances
2
>>> a.instance_method()
1
>>> Class.instance_method() # The class cannot use an instance method
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead)
>>> Class.instance_method(a)
2
>>> b = 0
>>> a.static_method(b)
1
>>> a.static_method(a.c) # Static method does not have direct access to 
>>>                      # class or instance properties.
3
>>> Class.c        # a.c above was passed by value and not by reference.
2
>>> a.c
2
>>> a.c = 5        # The connection between the instance
>>> Class.c        # and its class is weak as seen here.
2
>>> Class.class_method()
3
>>> a.c
5

2
Class.instance_method() # The class cannot use an instance methodnó có thể sử dụng. Chỉ cần vượt qua ví dụ thủ công:Class.instance_method(a)
warvariuc

@warwaruk Nó đây, nhìn vào dòng bên dưới TyeErrordòng.
kzh

vâng tôi đã thấy nó sau. Tuy nhiên, imo, không đúng khi nói 'Lớp không thể sử dụng một phương thức cá thể', bởi vì bạn vừa thực hiện nó một dòng dưới đây.
warvariuc

@kzh, Cảm ơn lời giải thích của bạn. Khi bạn gọi a.class_method(), có vẻ như a.cđã được cập nhật 1, vì vậy hãy gọi Class.class_method(), cập nhật Class.cbiến 2. Tuy nhiên, khi bạn được chỉ định a.c=5, tại sao Class.ckhông được cập nhật 5?
alpha_989

@ alpha_989 python trước tiên tìm kiếm một thuộc tính trực tiếp trên một cá thể của ovjects và nếu nó không ở đó, nó sẽ tìm nó trên lớp của nó theo mặc định. Nếu bạn có bất kỳ câu hỏi nào khác về điều này, xin vui lòng mở một câu hỏi và liên kết nó ở đây và tôi sẽ vui lòng giúp thêm.
kzh

4

method_two sẽ không hoạt động vì bạn đang xác định hàm thành viên nhưng không cho nó biết hàm đó là thành viên của. Nếu bạn thực hiện dòng cuối cùng, bạn sẽ nhận được:

>>> a_test.method_two()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

Nếu bạn đang xác định các hàm thành viên cho một lớp, đối số đầu tiên phải luôn là 'tự'.


3

Giải thích chính xác từ Armin Ronacher ở trên, mở rộng câu trả lời của anh ấy để những người mới bắt đầu như tôi hiểu rõ về nó:

Sự khác biệt trong các phương thức được định nghĩa trong một lớp, cho dù là phương thức tĩnh hay phương thức (có một loại phương thức lớp khác - không được thảo luận ở đây nên bỏ qua nó), trong thực tế liệu chúng có bị ràng buộc với thể hiện của lớp hay không. Ví dụ, giả sử phương thức có nhận được tham chiếu đến thể hiện của lớp trong thời gian chạy không

class C:
    a = [] 
    def foo(self):
        pass

C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C() 
c # this is the class instance

Các __dict__bất động sản từ điển của đối tượng lớp giữ tham chiếu đến tất cả các thuộc tính và phương thức của một đối tượng lớp và do đó

>>> C.__dict__['foo']
<function foo at 0x17d05b0>

phương pháp foo có thể truy cập như trên. Một điểm quan trọng cần lưu ý ở đây là mọi thứ trong python đều là một đối tượng và vì vậy các tài liệu tham khảo trong từ điển ở trên đều tự trỏ đến các đối tượng khác. Hãy để tôi gọi chúng là Đối tượng thuộc tính lớp - hoặc là CPO trong phạm vi câu trả lời của tôi cho ngắn gọn.

Nếu CPO là một mô tả, thì người phiên dịch python gọi __get__() phương thức của CPO để truy cập giá trị mà nó chứa.

Để xác định xem CPO có phải là mô tả hay không, trình thông dịch python kiểm tra xem nó có thực hiện giao thức mô tả hay không. Để thực hiện giao thức mô tả là thực hiện 3 phương thức

def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)

ví dụ

>>> C.__dict__['foo'].__get__(c, C)

Ở đâu

  • self là CPO (nó có thể là một thể hiện của danh sách, str, hàm, v.v.) và được cung cấp bởi bộ thực thi
  • instance là thể hiện của lớp nơi CPO này được xác định (đối tượng 'c' ở trên) và cần phải được cung cấp bởi chúng tôi
  • ownerlà lớp nơi CPO này được xác định (đối tượng lớp 'C' ở trên) và cần được cung cấp bởi chúng tôi. Tuy nhiên điều này là do chúng tôi đang gọi nó trên CPO. khi chúng ta gọi nó trên cá thể, chúng ta không cần cung cấp cái này vì bộ thực thi có thể cung cấp thể hiện hoặc lớp của nó (đa hình)
  • value là giá trị dự định cho CPO và cần được cung cấp bởi chúng tôi

Không phải tất cả CPO là mô tả. Ví dụ

>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510> 
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'

Điều này là do lớp danh sách không thực hiện giao thức mô tả.

Do đó, đối số tự nhập c.foo(self)là bắt buộc vì chữ ký phương thức của nó thực sự C.__dict__['foo'].__get__(c, C)là như vậy (như đã giải thích ở trên, C là không cần thiết vì nó có thể được tìm ra hoặc đa hình) Và đây cũng là lý do tại sao bạn nhận được TypeError nếu bạn không vượt qua đối số yêu cầu đó.

Nếu bạn nhận thấy phương thức vẫn được tham chiếu qua lớp Object C và liên kết với thể hiện của lớp được thực hiện thông qua việc chuyển một bối cảnh dưới dạng đối tượng thể hiện vào hàm này.

Điều này khá tuyệt vời vì nếu bạn chọn không giữ ngữ cảnh hoặc không ràng buộc với thể hiện, tất cả những gì cần thiết là viết một lớp để bọc CPO mô tả và ghi đè __get__()phương thức của nó để không yêu cầu ngữ cảnh. Lớp mới này là cái mà chúng ta gọi là trang trí và được áp dụng thông qua từ khóa@staticmethod

class C(object):
  @staticmethod
  def foo():
   pass

Sự vắng mặt của bối cảnh trong CPO được bao bọc mới fookhông gây ra lỗi và có thể được xác minh như sau:

>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>

Ca sử dụng của một phương thức tĩnh là nhiều hơn một không gian tên và khả năng duy trì mã (lấy nó ra khỏi một lớp và làm cho nó có sẵn trong toàn bộ mô-đun, v.v.).

Có thể tốt hơn để viết các phương thức tĩnh thay vì các phương thức cá thể bất cứ khi nào có thể, trừ khi bạn cần phải tiếp cận các phương thức (như các biến đối tượng truy cập, biến lớp, v.v.). Một lý do là để giảm bớt việc thu gom rác bằng cách không giữ các tham chiếu không mong muốn đến các đối tượng.


1

đó là một lỗi

trước hết, dòng đầu tiên phải như thế này (hãy cẩn thận với thủ đô)

class Test(object):

Bất cứ khi nào bạn gọi một phương thức của một lớp, nó sẽ tự nhận là đối số đầu tiên (do đó là tên tự) và method_two đưa ra lỗi này

>>> a.method_two()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: method_two() takes no arguments (1 given)

1

Đối tượng thứ hai sẽ không hoạt động vì khi bạn gọi nó như con trăn đó, bên trong cố gắng gọi nó với đối tượng a_test là đối số đầu tiên, nhưng phương thức của bạn không chấp nhận bất kỳ đối số nào, vì vậy nó sẽ không hoạt động, bạn sẽ có thời gian chạy lỗi. Nếu bạn muốn tương đương với một phương thức tĩnh, bạn có thể sử dụng một phương thức lớp. Có rất ít nhu cầu về các phương thức lớp trong Python so với các phương thức tĩnh trong các ngôn ngữ như Java hoặc C #. Thông thường, giải pháp tốt nhất là sử dụng một phương thức trong mô-đun, bên ngoài định nghĩa lớp, những phương thức này hoạt động hiệu quả hơn phương thức lớp.


Nếu tôi xác định hàm Class Test(object): @staticmethod def method_two(): print(“called method_two”) Một trường hợp sử dụng, tôi đã nghĩ đến khi bạn muốn hàm đó là một phần của lớp, nhưng không muốn người dùng truy cập trực tiếp vào hàm. Vì vậy, method_twocó thể được gọi bởi các chức năng khác trong Testthể hiện, nhưng không thể được gọi bằng cách sử dụng a_test.method_two(). Nếu tôi sử dụng def method_two(), điều này sẽ làm việc cho trường hợp sử dụng này? Hoặc có cách nào tốt hơn để sửa đổi định nghĩa hàm, để nó hoạt động như dự định cho trường hợp sử dụng ở trên?
alpha_989

1

Cuộc gọi đến method_two sẽ đưa ra một ngoại lệ vì không chấp nhận tham số tự, thời gian chạy Python sẽ tự động chuyển nó.

Nếu bạn muốn tạo một phương thức tĩnh trong lớp Python, hãy trang trí nó bằng staticmethod decorator.

Class Test(Object):
  @staticmethod
  def method_two():
    print "Called method_two"

Test.method_two()


0

Định nghĩa method_twolà không hợp lệ. Khi bạn gọi method_two, bạn sẽ nhận được TypeError: method_two() takes 0 positional arguments but 1 was giventừ thông dịch viên.

Một phương thức cá thể là một hàm giới hạn khi bạn gọi nó như thế nào a_test.method_two(). Nó tự động chấp nhận self, chỉ ra một thể hiện của Test, như là tham số đầu tiên của nó. Thông qua selftham số, một phương thức thể hiện có thể tự do truy cập các thuộc tính và sửa đổi chúng trên cùng một đối tượng.


0

Phương pháp không giới hạn

Các phương thức không liên kết là các phương thức chưa bị ràng buộc với bất kỳ thể hiện lớp cụ thể nào.

Phương pháp giới hạn

Các phương thức ràng buộc là các phương thức được ràng buộc với một thể hiện cụ thể của một lớp.

Như tài liệu của nó ở đây , tự có thể đề cập đến những thứ khác nhau tùy thuộc vào chức năng bị ràng buộc, không ràng buộc hoặc tĩnh.

Hãy xem ví dụ sau:

class MyClass:    
    def some_method(self):
        return self  # For the sake of the example

>>> MyClass().some_method()
<__main__.MyClass object at 0x10e8e43a0># This can also be written as:>>> obj = MyClass()

>>> obj.some_method()
<__main__.MyClass object at 0x10ea12bb0>

# Bound method call:
>>> obj.some_method(10)
TypeError: some_method() takes 1 positional argument but 2 were given

# WHY IT DIDN'T WORK?
# obj.some_method(10) bound call translated as
# MyClass.some_method(obj, 10) unbound method and it takes 2 
# arguments now instead of 1 

# ----- USING THE UNBOUND METHOD ------
>>> MyClass.some_method(10)
10

Vì chúng tôi không sử dụng thể hiện của lớp - obj - trong cuộc gọi cuối cùng, chúng ta có thể nói rằng nó trông giống như một phương thức tĩnh.

Nếu vậy, sự khác biệt giữa MyClass.some_method(10)cuộc gọi và cuộc gọi đến một chức năng tĩnh được trang trí với một @staticmethodtrang trí là gì?

Bằng cách sử dụng trình trang trí, chúng tôi làm rõ một cách rõ ràng rằng phương thức sẽ được sử dụng mà không cần tạo một thể hiện cho nó trước. Thông thường, người ta sẽ không mong đợi các phương thức thành viên lớp được sử dụng mà không có thể hiện và việc tích hợp chúng có thể gây ra các lỗi có thể tùy thuộc vào cấu trúc của phương thức.

Ngoài ra, bằng cách thêm trình @staticmethodtrang trí, chúng tôi cũng có thể tiếp cận thông qua một đối tượng.

class MyClass:    
    def some_method(self):
        return self    

    @staticmethod
    def some_static_method(number):
        return number

>>> MyClass.some_static_method(10)   # without an instance
10
>>> MyClass().some_static_method(10)   # Calling through an instance
10

Bạn không thể làm ví dụ trên với các phương thức ví dụ. Bạn có thể sống sót trong cuộc gọi đầu tiên (như chúng tôi đã làm trước đây) nhưng cuộc gọi thứ hai sẽ được dịch thành một cuộc gọi không liên kết MyClass.some_method(obj, 10)sẽ tạo ra một cuộc gọiTypeError phương thức cá thể có một đối số và bạn đã vô tình cố gắng vượt qua hai đối số.

Sau đó, bạn có thể nói, nếu tôi có thể gọi các phương thức tĩnh thông qua cả một thể hiện và một lớp MyClass.some_static_methodMyClass().some_static_methodnên là các phương thức giống nhau. Đúng!


0

Phương thức giới hạn = phương thức thể hiện

Phương thức không liên kết = phương thức tĩnh.


Vui lòng thêm một số giải thích thêm cho câu trả lời của bạn để những người khác có thể học hỏi từ nó. Ví dụ, hãy xem các câu trả lời khác cho câu hỏi này
Nico Haase
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.