Tại sao bạn cần rõ ràng có đối số Tự khắc trong một phương thức Python?


196

Khi định nghĩa một phương thức trên một lớp trong Python, nó trông giống như thế này:

class MyClass(object):
    def __init__(self, x, y):
        self.x = x
        self.y = y

Nhưng trong một số ngôn ngữ khác, chẳng hạn như C #, bạn có một tham chiếu đến đối tượng mà phương thức được liên kết với từ khóa "này" mà không khai báo nó là một đối số trong nguyên mẫu phương thức.

Đây có phải là một quyết định thiết kế ngôn ngữ có chủ ý trong Python hay có một số chi tiết triển khai yêu cầu chuyển "bản thân" làm đối số không?


15
Tôi cá là bạn cũng sẽ muốn biết lý do tại sao bạn cần phải viết rõ ràng selfđể truy cập các thành viên - stackoverflow.com/questions/910020/ợi
Piotr Dobrogost

1
Nhưng có vẻ như một tấm nồi hơi mặc dù
Raghuveer

Một chút khó hiểu nhưng đáng để hiểu stackoverflow.com/a/31367197/1815624
CrandellWS

Câu trả lời:


91

Tôi muốn trích dẫn Zen của Python của Peters. "Rõ ràng là tốt hơn so với ngầm."

Trong Java và C ++, ' this.' có thể được suy ra, ngoại trừ khi bạn có tên biến khiến cho không thể suy ra. Vì vậy, đôi khi bạn cần nó và đôi khi không.

Python chọn để làm cho những thứ như thế này rõ ràng hơn là dựa trên một quy tắc.

Ngoài ra, vì không có gì được ngụ ý hay giả định, các phần của việc thực hiện được phơi bày. self.__class__, self.__dict__Và các cấu trúc "nội bộ" có sẵn trong một cách rõ ràng.


53
Mặc dù sẽ rất tốt nếu có một thông báo lỗi ít khó hiểu hơn khi bạn quên nó.
Martin Beckett

9
Tuy nhiên, khi bạn gọi một phương thức bạn không phải truyền biến đối tượng, nó không phá vỡ quy tắc của nhân chứng? Nếu để giữ zen này, nó phải là một cái gì đó như: object.method (object, param1, param2). Trông có vẻ không nhất quán ...
Vedmant

10
"Rõ ràng là tốt hơn ngầm định" - Không phải "phong cách" của Python được xây dựng xung quanh những thứ đang ẩn? ví dụ: các kiểu dữ liệu ẩn, các ranh giới hàm ẩn (không có {}), phạm vi biến ẩn ... nếu các biến toàn cục trong các mô-đun có sẵn trong các hàm ... tại sao không nên áp dụng logic / lý luận tương tự cho các lớp? Không phải quy tắc đơn giản hóa sẽ là "bất cứ điều gì được tuyên bố ở cấp cao hơn đều có sẵn ở cấp thấp hơn" như được xác định bằng cách thụt lề?
Simon

13
"Rõ ràng là tốt hơn ngầm" Phát hiện vô nghĩa
Vahid Amiri

10
Hãy đối mặt với nó, nó chỉ là xấu. Không có lý do cho việc này. Nó chỉ là một di tích xấu xí nhưng không sao.
Toskan

62

Đó là để giảm thiểu sự khác biệt giữa các phương thức và chức năng. Nó cho phép bạn dễ dàng tạo các phương thức trong siêu dữ liệu hoặc thêm các phương thức trong thời gian chạy vào các lớp có sẵn.

ví dụ

>>> class C(object):
...     def foo(self):
...         print "Hi!"
...
>>>
>>> def bar(self):
...     print "Bork bork bork!"
...
>>>
>>> c = C()
>>> C.bar = bar
>>> c.bar()
Bork bork bork!
>>> c.foo()
Hi!
>>>

Nó cũng (theo như tôi biết) làm cho việc thực hiện thời gian chạy python dễ dàng hơn.


10
+1 cho Nó để giảm thiểu sự khác biệt giữa các phương thức và chức năng. Câu trả lời này phải được chấp nhận
người dùng

Điều này cũng là gốc rễ của lời giải thích liên kết nhiều của guido.
Marcin

1
Điều này cũng cho thấy rằng trong Python, khi bạn thực hiện c.bar () trước tiên, nó sẽ kiểm tra thể hiện cho các thuộc tính, sau đó nó kiểm tra các thuộc tính lớp . Vì vậy, bạn có thể 'đính kèm' một dữ liệu hoặc hàm (đối tượng) vào Lớp bất cứ lúc nào và mong muốn truy cập trong thể hiện của nó (ví dụ: dir (thể hiện) sẽ như thế nào). Không chỉ khi bạn "tạo" c dụ. Nó rất năng động.
Nishant

10
Tôi không thực sự mua nó. Ngay cả trong trường hợp bạn cần lớp cha, bạn vẫn có thể suy ra nó khi thực thi. Và sự tương đương giữa các phương thức cá thể và các hàm lớp thông qua các thể hiện là ngớ ngẩn; Ruby làm tốt mà không có họ.
zachaysan

2
JavaScript cho phép bạn thêm các phương thức vào một đối tượng vào thời gian chạy và nó không yêu cầu selftrong khai báo hàm (nhớ bạn, có lẽ điều này đang ném đá từ một nhà kính, vì JavaScript có một số thisngữ nghĩa ràng buộc khá khó khăn )
Jonathan Benn

55

Tôi đề nghị một người nên đọc blog của Guido van Rossum về chủ đề này - Tại sao bản thân rõ ràng phải ở lại .

Khi một định nghĩa phương thức được trang trí, chúng ta không biết nên tự động cung cấp cho nó một tham số 'tự' hay không: trình trang trí có thể biến hàm thành một phương thức tĩnh (không có 'tự') hoặc một phương thức lớp (mà có một kiểu tự hài hước đề cập đến một lớp thay vì một thể hiện) hoặc nó có thể làm một cái gì đó hoàn toàn khác (thật đơn giản để viết một trình trang trí thực hiện '@ classmethod' hoặc '@staticmethod' trong Python thuần túy). Không có cách nào mà không biết người trang trí làm gì có nên ban cho phương thức được xác định bằng một đối số 'tự' ngầm hay không.

Tôi từ chối các hack như vỏ bọc đặc biệt '@ classmethod' và '@staticmethod'.


16

Python không ép buộc bạn sử dụng "tự". Bạn có thể đặt cho nó bất cứ tên nào bạn muốn. Bạn chỉ cần nhớ rằng đối số đầu tiên trong tiêu đề định nghĩa phương thức là tham chiếu đến đối tượng.


Theo quy ước, tuy nhiên, nó phải là 'bản thân' cho các trường hợp hoặc 'cls' trong đó các loại có liên quan (siêu dữ liệu mmmm)
pobk

1
Nó buộc phải đặt bản thân làm thông số đầu tiên trong mọi phương thức, chỉ là văn bản bổ sung không có ý nghĩa nhiều đối với tôi. Các ngôn ngữ khác chỉ hoạt động tốt với điều này.
Vedmant

tôi có đúng không luôn luôn tham số đầu tiên là một tham chiếu đến đối tượng.
Mohammad Mahdi KouchakYazdi

@MMKY Không, ví dụ với @staticmethodnó thì không.
Đánh dấu

1
"Bạn chỉ cần nhớ rằng đối số đầu tiên trong định nghĩa phương thức ..." Tôi đã thử nghiệm thay đổi từ "bản thân" thành "kwyjibo" và nó vẫn hoạt động. Vì vậy, như thường được giải thích, đó không phải là từ "bản thân" quan trọng mà là vị trí của bất cứ thứ gì chiếm không gian đó (?)
RBV

7

Cũng cho phép bạn làm điều này: (trong ngắn hạn, gọi Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)sẽ trả lại 12, nhưng sẽ làm như vậy theo cách điên rồ nhất.

class Outer(object):
    def __init__(self, outer_num):
        self.outer_num = outer_num

    def create_inner_class(outer_self, inner_arg):
        class Inner(object):
            inner_arg = inner_arg
            def weird_sum_with_closure_scope(inner_self, num)
                return num + outer_self.outer_num + inner_arg
        return Inner

Tất nhiên, điều này khó tưởng tượng hơn trong các ngôn ngữ như Java và C #. Bằng cách làm cho tham chiếu tự rõ ràng, bạn có thể tự do tham khảo bất kỳ đối tượng nào bằng cách tự tham chiếu đó. Ngoài ra, cách chơi với các lớp trong thời gian chạy như vậy khó thực hiện hơn trong các ngôn ngữ tĩnh hơn - không nhất thiết phải tốt hay xấu. Chỉ là bản thân rõ ràng cho phép tất cả sự điên rồ này tồn tại.

Hơn nữa, hãy tưởng tượng điều này: Chúng tôi muốn tùy chỉnh hành vi của các phương thức (để định hình hoặc một số phép thuật đen điên rồ). Điều này có thể khiến chúng ta suy nghĩ: nếu chúng ta có một lớp Methodmà hành vi chúng ta có thể ghi đè hoặc kiểm soát thì sao?

Vâng, đây là:

from functools import partial

class MagicMethod(object):
    """Does black magic when called"""
    def __get__(self, obj, obj_type):
        # This binds the <other> class instance to the <innocent_self> parameter
        # of the method MagicMethod.invoke
        return partial(self.invoke, obj)


    def invoke(magic_self, innocent_self, *args, **kwargs):
        # do black magic here
        ...
        print magic_self, innocent_self, args, kwargs

class InnocentClass(object):
    magic_method = MagicMethod()

Và bây giờ: InnocentClass().magic_method()sẽ hành động như mong đợi. Phương thức sẽ được liên kết với innocent_selftham số đến InnocentClassvà với magic_selfđối tượng MagicMethod. Lạ nhỉ? Giống như có 2 từ khóa this1this2trong các ngôn ngữ như Java và C #. Phép thuật như thế này cho phép các khung công tác thực hiện những thứ mà nếu không thì sẽ dài dòng hơn nhiều.

Một lần nữa, tôi không muốn bình luận về đạo đức của công cụ này. Tôi chỉ muốn thể hiện những điều khó thực hiện hơn nếu không có tài liệu tham khảo rõ ràng.


4
Khi tôi xem xét ví dụ đầu tiên của bạn, tôi có thể làm tương tự trong Java: lớp bên trong cần gọi OuterClass.thisđể lấy 'cái tôi' từ lớp bên ngoài, nhưng bạn vẫn có thể sử dụng thislàm tài liệu tham khảo cho chính nó; rất giống với những gì bạn làm ở đây trong Python. Đối với tôi không khó để tưởng tượng điều này. Có lẽ nó phụ thuộc vào sự thành thạo ngôn ngữ trong câu hỏi?
klaar

Nhưng bạn vẫn có thể tham khảo bất kỳ phạm vi nào khi bạn ở trong một phương thức của một lớp ẩn danh, được định nghĩa bên trong một lớp ẩn danh, được định nghĩa bên trong một triển khai ẩn danh của giao diện Something, lần lượt được xác định bên trong một triển khai ẩn danh khác Something? Trong python tất nhiên bạn có thể tham khảo bất kỳ phạm vi.
vlad-ardelean

Bạn đã đúng, trong Java bạn chỉ có thể tham chiếu đến lớp bên ngoài bằng cách gọi tên lớp rõ ràng của nó và sử dụng nó để làm tiền tố this. Tài liệu tham khảo ngầm là impossibru trong Java.
klaar

Tôi tự hỏi nếu điều này sẽ làm việc mặc dù: Trong mỗi phạm vi (mỗi phương thức) có một biến cục bộ, tham chiếu thiskết quả. Ví dụ Object self1 = this;(sử dụng Object hoặc một cái gì đó ít chung chung hơn). Sau đó, nếu bạn có quyền truy cập vào biến trong phạm vi cao hơn, bạn có thể có quyền truy cập vào self1, self2... selfn. Tôi nghĩ rằng những điều này nên được tuyên bố cuối cùng hoặc một cái gì đó, nhưng nó có thể hoạt động.
vlad-ardelean

3

Tôi nghĩ lý do thực sự bên cạnh "Zen của Python" là Hàm là công dân hạng nhất trong Python.

Mà về cơ bản làm cho họ trở thành một đối tượng. Bây giờ Vấn đề cơ bản là nếu các chức năng của bạn cũng là đối tượng thì trong mô hình hướng đối tượng, bạn sẽ gửi tin nhắn đến các đối tượng như thế nào khi chính các tin nhắn là đối tượng?

Trông giống như một vấn đề trứng gà, để giảm nghịch lý này, cách duy nhất có thể là chuyển một bối cảnh thực thi sang các phương thức hoặc phát hiện ra nó. Nhưng vì python có thể có các hàm lồng nhau nên sẽ không thể làm như vậy vì bối cảnh thực thi sẽ thay đổi cho các hàm bên trong.

Điều này có nghĩa là giải pháp khả thi duy nhất là vượt qua 'bản thân' một cách rõ ràng (Bối cảnh thực hiện).

Vì vậy, tôi tin rằng đây là một vấn đề triển khai mà Zen đã đến nhiều sau đó.


Xin chào Tôi mới biết về python (từ nền java) và tôi không hiểu lắm những gì bạn nói "làm thế nào bạn gửi tin nhắn đến các đối tượng khi chính các tin nhắn là đối tượng". Tại sao đó là một vấn đề, bạn có thể giải thích?
Qiulang

1
@Qiulang Aah, trong các phương thức gọi lập trình hướng đối tượng trên các đối tượng tương đương với việc gửi tin nhắn đến các Đối tượng có hoặc không có tải trọng (tham số cho hàm của bạn). Các phương thức bên trong sẽ được biểu diễn dưới dạng một khối mã được liên kết với một lớp / đối tượng và sử dụng môi trường ẩn có sẵn cho nó thông qua đối tượng mà nó được gọi để chống lại. Nhưng nếu các phương thức của bạn là các đối tượng thì chúng có thể tồn tại độc lập với việc được liên kết với một lớp / đối tượng đặt ra câu hỏi nếu bạn gọi phương thức này mà môi trường sẽ chạy với môi trường nào?
pankajdoharey

Do đó, phải có một cơ chế để cung cấp một môi trường, bản thân nó có nghĩa là môi trường hiện tại tại điểm thực thi nhưng bạn cũng có thể cung cấp một môi trường khác.
pankajdoharey

2

Tôi nghĩ rằng nó phải làm với PEP 227:

Tên trong phạm vi lớp học không thể truy cập. Tên được giải quyết trong phạm vi chức năng kèm theo trong cùng. Nếu một định nghĩa lớp xảy ra trong một chuỗi các phạm vi lồng nhau, quá trình phân giải sẽ bỏ qua các định nghĩa lớp. Quy tắc này ngăn chặn các tương tác kỳ lạ giữa các thuộc tính lớp và truy cập biến cục bộ. Nếu một hoạt động liên kết tên xảy ra trong một định nghĩa lớp, nó sẽ tạo một thuộc tính trên đối tượng lớp kết quả. Để truy cập biến này trong một phương thức hoặc trong một hàm được lồng trong một phương thức, một tham chiếu thuộc tính phải được sử dụng, thông qua chính nó hoặc thông qua tên lớp.


1

Như đã giải thích về bản thân trong Python, Demystified

bất cứ điều gì như obj.meth (args) trở thành Class.meth (obj, args). Quá trình gọi là tự động trong khi quá trình nhận không (rõ ràng). Đây là lý do tham số đầu tiên của hàm trong lớp phải là chính đối tượng.

class Point(object):
    def __init__(self,x = 0,y = 0):
        self.x = x
        self.y = y

    def distance(self):
        """Find distance from origin"""
        return (self.x**2 + self.y**2) ** 0.5

Lời mời:

>>> p1 = Point(6,8)
>>> p1.distance()
10.0

init () định nghĩa ba tham số nhưng chúng ta chỉ truyền hai (6 và 8). Tương tự khoảng cách () yêu cầu một nhưng không có đối số nào được thông qua.

Tại sao Python không phàn nàn về số đối số này không khớp ?

Nói chung, khi chúng ta gọi một phương thức với một số đối số, hàm lớp tương ứng được gọi bằng cách đặt đối tượng của phương thức trước đối số đầu tiên. Vì vậy, bất cứ điều gì như obj.meth (args) sẽ trở thành Class.meth (obj, args). Quá trình gọi là tự động trong khi quá trình nhận không (rõ ràng).

Đây là lý do tham số đầu tiên của hàm trong lớp phải là chính đối tượng. Viết tham số này là tự nó chỉ là một quy ước . Nó không phải là một từ khóa và không có ý nghĩa đặc biệt trong Python. Chúng tôi có thể sử dụng tên khác (như thế này) nhưng tôi thực sự khuyên bạn không nên. Việc sử dụng các tên khác ngoài bản thân được hầu hết các nhà phát triển tán thành và làm giảm khả năng đọc của mã ("Số lượng dễ đọc").
...
Trong, ví dụ đầu tiên self.x là một thuộc tính thể hiện trong khi x là biến cục bộ. Chúng không giống nhau và nằm trong các không gian tên khác nhau.

Tự ở đây để ở lại

Nhiều người đã đề xuất tự biến mình thành một từ khóa trong Python, như thế này trong C ++ và Java. Điều này sẽ loại bỏ việc sử dụng bản thân rõ ràng khỏi danh sách tham số chính thức trong các phương thức. Trong khi ý tưởng này có vẻ hứa hẹn, nó sẽ không xảy ra. Ít nhất là không trong tương lai gần. Lý do chính là khả năng tương thích ngược . Dưới đây là một blog từ chính người tạo ra Python giải thích lý do tại sao bản thân rõ ràng phải ở lại.


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.