Làm cách nào để tránh 'tự' rõ ràng trong Python?


131

Tôi đã học Python bằng cách làm theo một số hướng dẫn về pygame .

Trong đó tôi thấy sử dụng rộng rãi trong những từ khóa tự , và đến từ một nền chủ yếu Java, tôi thấy rằng tôi giữ quên gõ tự . Ví dụ, thay vì self.rect.centerxtôi sẽ gõ rect.centerx, bởi vì, với tôi, orth đã là một biến thành viên của lớp.

Song song Java tôi có thể nghĩ cho tình huống này là phải thêm tiền tố vào tất cả các tham chiếu đến các biến thành viên với điều này .

Tôi có bị mắc kẹt tiền tố với tất cả các biến thành viên với chính mình không , hay có cách nào để khai báo chúng cho phép tôi tránh phải làm như vậy không?

Ngay cả khi những gì tôi đề nghị không phải là pythonic , tôi vẫn muốn biết liệu điều đó có khả thi hay không.

Tôi đã xem xét những câu hỏi SO liên quan này, nhưng họ không trả lời tôi là gì sau:


5
Tôi đến từ một nền tảng Java và thấy nó tự nhiên, nhưng tôi rõ ràng thêm "cái này" vào mỗi cuộc gọi để làm cho nó rõ ràng hơn rằng tôi đang đề cập đến một biến thể hiện.
Uri

4
Bạn có quen thuộc với quy ước m_tiền tố cho tất cả các tên thành viên được quan sát bởi một số lập trình viên C ++ / Java không? Việc sử dụng self.giúp dễ đọc theo cách tương tự. Ngoài ra, bạn nên đọc dirtsimple.org/2004/12/python-is-not-java.html .
Beni Cherniavsky-Paskin

2
Mặc dù thường chỉ m_được sử dụng cho các thành viên dữ liệu không tĩnh không công khai (ít nhất là trong C ++).

@Beni bài viết được liên kết tuyệt vời, và thực sự, tôi thực sự tuân theo quy ước sử dụng mVariableName, cho các biến thành viên, khi mã hóa trong Java. Tôi nghĩ rằng bình luận của @ Anurag tổng hợp khá tốt, vì những gì một nhà phát triển java nên làm khi học python.
bguiz

11
Vì vậy, tại sao mọi người lại nói với OP tại sao sử dụng bản thân là tốt / cần thiết / v.v. nhưng không ai nói liệu có thể tránh được bằng cách nào đó? Ngay cả khi bằng một loại lừa bẩn?
einpoklum

Câu trả lời:


99

Python yêu cầu tự xác định. Kết quả là không bao giờ có bất kỳ sự nhầm lẫn nào về những gì thành viên và những gì không, ngay cả khi không có định nghĩa đầy đủ về lớp. Điều này dẫn đến các thuộc tính hữu ích, chẳng hạn như: bạn không thể thêm các thành viên vô tình che giấu những người không phải thành viên và do đó phá vỡ mã.

Một ví dụ cực đoan: bạn có thể viết một lớp mà không có bất kỳ kiến ​​thức nào về các lớp cơ sở mà nó có thể có và luôn biết liệu bạn có truy cập thành viên hay không:

class A(some_function()):
  def f(self):
    self.member = 42
    self.method()

Đó là mã hoàn chỉnh ! (some_function trả về loại được sử dụng làm cơ sở.)

Khác, trong đó các phương thức của một lớp được cấu tạo động:

class B(object):
  pass

print B()
# <__main__.B object at 0xb7e4082c>

def B_init(self):
  self.answer = 42
def B_str(self):
  return "<The answer is %s.>" % self.answer
# notice these functions require no knowledge of the actual class
# how hard are they to read and realize that "members" are used?

B.__init__ = B_init
B.__str__ = B_str

print B()
# <The answer is 42.>

Hãy nhớ rằng, cả hai ví dụ này đều cực đoan và bạn sẽ không nhìn thấy chúng mỗi ngày, tôi cũng không khuyên bạn nên thường xuyên viết mã như thế này, nhưng chúng cho thấy rõ các khía cạnh của bản thân được yêu cầu rõ ràng.


4
Cảm ơn bạn. Câu trả lời này đạt đến điểm cos nó cũng giải thích những lợi ích của việc sử dụng self.
bguiz

2
@Roger Pate: Vui lòng dừng chỉnh sửa câu hỏi của tôi để xóa python khỏi nó. Tôi nghĩ rằng nó thuộc về nơi đó. (và cảm ơn vì câu trả lời!)
bguiz

3
@bguiz: Đó là quy ước SO không sao chép các thẻ trong tiêu đề. Tuy nhiên, khi tôi chỉnh sửa 2 ngày trước, tôi không thấy bạn hoàn nguyên tiêu đề 7 tháng trước.

1
Sẽ thật tuyệt nếu bản thân có thể được giảm xuống thành một nhân vật.
dwjohnston

5
Đó thực sự là một lý do nghe có vẻ ngớ ngẩn. Python có thể không chấp nhận đổ bóng và yêu cầu bạn khai báo cụ thể, tức là 'ban phước' cho trường bóng của bạn bằng một số loại __shadow__ myFieldName. Điều đó cũng sẽ ngăn chặn bóng tối vô tình, phải không?
einpoklum

39

Các câu trả lời trước đây là tất cả các biến thể của "bạn không thể" hoặc "bạn không nên". Trong khi tôi đồng ý với tình cảm sau, câu hỏi về mặt kỹ thuật vẫn chưa được trả lời.

Hơn nữa, có những lý do chính đáng tại sao một người nào đó có thể muốn làm điều gì đó dọc theo những gì câu hỏi thực sự đang hỏi. Một điều tôi đôi khi gặp phải là các phương trình toán học dài, trong đó việc sử dụng tên dài làm cho phương trình không thể nhận ra. Dưới đây là một số cách bạn có thể làm điều này trong một ví dụ đóng hộp:

import numpy as np
class MyFunkyGaussian() :
    def __init__(self, A, x0, w, s, y0) :
        self.A = float(A)
        self.x0 = x0
        self.w = w
        self.y0 = y0
        self.s = s

    # The correct way, but subjectively less readable to some (like me) 
    def calc1(self, x) :
        return (self.A/(self.w*np.sqrt(np.pi))/(1+self.s*self.w**2/2)
                * np.exp( -(x-self.x0)**2/self.w**2)
                * (1+self.s*(x-self.x0)**2) + self.y0 )

    # The correct way if you really don't want to use 'self' in the calculations
    def calc2(self, x) :
        # Explicity copy variables
        A, x0, w, y0, s = self.A, self.x0, self.w, self.y0, self.s
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

    # Probably a bad idea...
    def calc3(self, x) :
        # Automatically copy every class vairable
        for k in self.__dict__ : exec(k+'= self.'+k)
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

g = MyFunkyGaussian(2.0, 1.5, 3.0, 5.0, 0.0)
print(g.calc1(0.5))
print(g.calc2(0.5))
print(g.calc3(0.5))

Ví dụ thứ ba - tức là sử dụng for k in self.__dict__ : exec(k+'= self.'+k)về cơ bản là những gì câu hỏi thực sự yêu cầu, nhưng hãy để tôi nói rõ rằng tôi không nghĩ rằng đó thường là một ý tưởng tốt.

Để biết thêm thông tin và các cách để lặp qua các biến lớp hoặc thậm chí các hàm, hãy xem câu trả lời và thảo luận cho câu hỏi này . Để thảo luận về các cách khác để tự động đặt tên biến và tại sao điều này thường không phải là một ý tưởng tốt, hãy xem bài đăng trên blog này .

CẬP NHẬT: Dường như không có cách nào để tự động cập nhật hoặc thay đổi cục bộ trong một hàm trong Python3, vì vậy calc3 và các biến thể tương tự không còn có thể. Giải pháp tương thích python3 duy nhất tôi có thể nghĩ đến bây giờ là sử dụng globals:

def calc4(self, x) :
        # Automatically copy every class variable in globals
        globals().update(self.__dict__)
        sqrt, exp, pi = np.sqrt, np.exp, np.pi
        return ( A/( w*sqrt(pi) )/(1+s*w**2/2)
                * exp( -(x-x0)**2/w**2 )
                * (1+s*(x-x0)**2) + y0 )

Mà, một lần nữa, sẽ là một thực tế khủng khiếp nói chung.


4
Xuất sắc! Đây là câu trả lời đúng nhất (duy nhất?). +1 Bạn cũng đưa ra một lý do thực tế để thực hiện nó. + 1i
David Lotts

Sau khi tạo một lớp và di chuyển mã bên trong nó: bây giờ tất cả các phương thức và biến không còn được nhận ra (không có bản thân ..) Tôi nhắc nhở về một lý do khác khiến python và tôi không hợp nhau. Cảm ơn vì điều này như một ý tưởng. Nó không khắc phục vấn đề / đau đầu / không thể đọc tổng thể nhưng cung cấp một cách giải quyết khiêm tốn.
javadba

Tại sao không chỉ cập nhật localsthay vì sử dụng exec?
Nathan

Tôi đã thử locals().update(self.__dict__)trong python 2 và 3, nhưng nó không hoạt động. Trong python3, ngay cả thủ thuật 'exec' cũng không còn là một lựa chọn nữa. Mặt khác, globals().update(self.__dict__)làm việc, nhưng nói chung sẽ là một thực tế khủng khiếp.
argentum2f

26

Trên thực tế selfkhông phải là một từ khóa, nó chỉ là tên được quy ước cho tham số đầu tiên của các phương thức cá thể trong Python. Và tham số đầu tiên đó không thể bị bỏ qua, vì đó là cơ chế duy nhất mà một phương thức có thể biết về trường hợp nào của lớp bạn được gọi.


1
Câu trả lời này, đặc biệt là câu thứ 2, hữu ích hơn nhiều so với câu trả lời được chấp nhận cho tôi vì tôi có thể biết "bản thân rõ ràng" chỉ là một trong những hạn chế trong Python và không thể tránh khỏi
Câu hỏi

21

Bạn có thể sử dụng bất cứ tên nào bạn muốn, ví dụ

class test(object):
    def function(this, variable):
        this.variable = variable

hoặc thậm chí

class test(object):
    def function(s, variable):
        s.variable = variable

nhưng bạn bị mắc kẹt với việc sử dụng tên cho phạm vi.

Tôi không khuyên bạn nên sử dụng một cái gì đó khác biệt cho bản thân trừ khi bạn có một lý do thuyết phục, vì nó sẽ khiến nó trở nên xa lạ đối với những con trăn có kinh nghiệm.


26
Bạn có thể làm điều này, nhưng đừng ! Không có lý do để làm cho mã của bạn lạ hơn nó cần phải có. Vâng, bạn có thể gọi nó là bất cứ điều gì, nhưng quy ước là gọi nó self, và bạn nên tuân theo quy ước. Nó sẽ làm cho mã của bạn dễ hiểu hơn đối với bất kỳ lập trình viên Python có kinh nghiệm nào nhìn vào nó. (Điều này bao gồm bạn, sáu tháng kể từ bây giờ, cố gắng tìm hiểu chương trình cũ của bạn làm gì!)
steveha

3
Thậm chí nhiều người ngoài hành tinh:def function(_, variable): _.variable = variable
Bob Stein

1
@ BobStein-VisiBone thậm chí còn xa lạ hơn:def funcion(*args): args[0].variable = args[1]
Aemyl

4
@steveha anh ấy không khuyến nghị, thông tin này rất hữu ích cho những người như tôi, những người không biết bạn có thể sử dụng các từ khóa khác với bản thân và tự hỏi tại sao một đối tượng của lớp riêng lại được thông qua.
Sven van den Boogaart

> "kì lạ". Python lạ - đặc biệt là các cấu trúc lớp của nó và việc sử dụng bản thân này là một công cụ hỗ trợ chống đọc. Tôi biết sẽ có nhiều người bảo vệ đến sau điều này nhưng điều đó không thay đổi tính chính xác.
javadba

9

vâng, bạn phải luôn luôn chỉ định self, bởi vì rõ ràng là tốt hơn ngầm định, theo triết lý python.

Bạn cũng sẽ thấy rằng cách bạn lập trình trong python rất khác so với cách bạn lập trình trong java, do đó việc sử dụng selfcó xu hướng giảm vì bạn không chiếu mọi thứ vào bên trong đối tượng. Thay vào đó, bạn sử dụng chức năng cấp mô-đun lớn hơn, có thể được kiểm tra tốt hơn.

nhân tiện. Tôi ghét nó lúc đầu, bây giờ tôi ghét điều ngược lại. tương tự cho điều khiển dòng chảy thụt lề.


2
"Bạn sử dụng chức năng cấp mô-đun lớn hơn, có thể được kiểm tra tốt hơn" là không rõ ràng và tôi phải hoàn toàn không đồng ý. Đúng là bạn không bị buộc phải biến mọi thứ thành phương thức (tĩnh hay không) của một số lớp, bất kể đó là "cấp độ mô-đun logic", nhưng điều đó không liên quan gì đến bản thân và không có tác động đáng kể đến thử nghiệm bằng cách này hay cách khác (đối với tôi).

Nó phát sinh từ kinh nghiệm của tôi. Tôi không tuân theo nó như một câu thần chú mỗi lần, nhưng nó giúp mọi thứ dễ kiểm tra hơn nếu bạn đặt bất cứ thứ gì không yêu cầu quyền truy cập biến thành viên như một phương pháp độc lập, riêng biệt. vâng, bạn tách logic khỏi dữ liệu, có, chống lại OOP, nhưng chúng cùng nhau, chỉ ở cấp độ mô-đun. Tôi không cho cái này hay cái kia "tốt nhất", đó chỉ là vấn đề của hương vị. Đôi khi tôi thấy mình chỉ định các phương thức lớp không liên quan gì đến chính lớp đó, vì chúng không chạm selfvào bất kỳ cách nào. Vì vậy, điểm quan trọng trong việc có chúng trên lớp là gì?
Stefano Borini

Tôi không đồng ý với cả hai phần của nó (có vẻ như tôi sử dụng "phi phương pháp" không thường xuyên hơn các ngôn ngữ khác), nhưng "có thể được kiểm tra tốt hơn" có nghĩa là một cách tốt hơn so với cách khác (đó là cách tôi đọc nó? dường như không có khả năng), trong khi tôi không tìm thấy sự hỗ trợ cho điều đó theo kinh nghiệm của mình. Lưu ý rằng tôi không nói rằng bạn nên luôn luôn sử dụng cái này hay cái kia, tôi chỉ nói các phương thức và phi phương pháp đều có thể được kiểm tra như nhau.

5
vâng, tất nhiên bạn có thể, nhưng cách tiếp cận là khác nhau. Một đối tượng có một trạng thái, một phương thức cấp mô-đun không có. Nếu bạn phát hiện ra một bài kiểm tra thất bại trong khi kiểm tra một phương thức cấp lớp, có hai điều có thể đã sai: 1) trạng thái đối tượng tại thời điểm của cuộc gọi 2) chính phương thức đó. Nếu bạn có một phương thức cấp mô-đun không trạng thái, chỉ có trường hợp 2 có thể xảy ra. Bạn đã di chuyển thiết lập từ đối tượng (trong đó là hộp đen khi liên quan đến thử nghiệm, vì nó bị chi phối bởi logic phức tạp cuối cùng bên trong đối tượng) sang testsuite. Bạn đang giảm độ phức tạp và kiểm soát thiết lập chặt chẽ hơn.
Stefano Borini

1
"Nếu bạn có một phương thức cấp mô-đun không trạng thái", vậy còn phương pháp cấp mô-đun trạng thái thì sao? Tất cả những gì bạn nói với tôi là các hàm không trạng thái dễ kiểm tra hơn các hàm trạng thái và tôi sẽ đồng ý với điều đó, nhưng nó không liên quan gì đến các phương thức so với các phương thức không. Xem tham số tự chính xác như vậy: chỉ một tham số khác cho hàm.

4

"Bản thân" là trình giữ chỗ thông thường của thể hiện đối tượng hiện tại của một lớp. Nó được sử dụng khi bạn muốn tham chiếu đến thuộc tính hoặc trường hoặc phương thức của đối tượng trong một lớp như thể bạn đang đề cập đến "chính nó". Nhưng để làm cho nó ngắn hơn một người nào đó trong vương quốc lập trình Python bắt đầu sử dụng "tự", các vương quốc khác sử dụng "này" nhưng họ làm cho nó như một từ khóa không thể thay thế. Tôi thay vì sử dụng "của nó" để tăng khả năng đọc mã. Đó là một trong những điều tốt trong Python - bạn có quyền tự do chọn trình giữ chỗ của riêng bạn cho ví dụ của đối tượng chứ không phải là "bản thân". Ví dụ cho bản thân:

class UserAccount():    
    def __init__(self, user_type, username, password):
        self.user_type = user_type
        self.username = username            
        self.password = encrypt(password)        

    def get_password(self):
        return decrypt(self.password)

    def set_password(self, password):
        self.password = encrypt(password)

Bây giờ chúng tôi thay thế 'tự' bằng 'của nó':

class UserAccount():    
    def __init__(its, user_type, username, password):
        its.user_type = user_type
        its.username = username            
        its.password = encrypt(password)        

    def get_password(its):
        return decrypt(its.password)

    def set_password(its, password):
        its.password = encrypt(password)

cái nào dễ đọc hơn bây giờ?


tại sao không chỉ s(hoặc một số chữ cái khác) thay vìits
javadba

'Nó' có nghĩa và 's' thì không.
LEMUEL ADane

scó cùng ý nghĩa: một bí danh cho thể hiện của lớp. Tôi sẽ phải tìm kiếm những gì itscó nghĩa là trong bối cảnh giống nhau
javadba

Cả hai đều không thể đọc được với tôi. Vẫn là phi logic khi lấy "chính mình" làm tham số bên ngoài, nó không có ý nghĩa gì.
Cesar

3

self là một phần của cú pháp python để truy cập các thành viên của các đối tượng, vì vậy tôi sợ bạn bị mắc kẹt với nó


2
tự là một cách để nói với người sửa đổi truy cập mà không thực sự sử dụng một. +1
Perpetualcoder

1

Trên thực tế, bạn có thể sử dụng công thức "Tự ẩn" từ bài thuyết trình của Armin Ronacher "5 năm ý tưởng tồi" (google nó).

Đó là một công thức rất thông minh, vì hầu hết mọi thứ từ Armin Ronacher, nhưng tôi không nghĩ ý tưởng này rất hấp dẫn. Tôi nghĩ rằng tôi thích rõ ràng điều này trong C # / Java.

Cập nhật. Liên kết đến "công thức ý tưởng tồi": https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58


này liên kết mà bạn đang đề cập đến những gì bạn đã đề cập đến? Nếu vậy xin vui lòng bao gồm trong câu trả lời của bạn
reubenjohn

Không, Armin "ý tưởng tồi" trông buồn cười hơn đối với sở thích của tôi. Tôi bao gồm liên kết.
Alex Yu

Trước khi bạn nhấp vào liên kết công thức, hãy lưu ý rằng điều này làm cho nó ẩn trong danh sách tham số def method(<del> self </ del> ), nhưng self.variablevẫn được yêu cầu với cách hack thông minh này.
David Lotts

0

Vâng, bản thân là tẻ nhạt. Nhưng, nó có tốt hơn không?

class Test:

    def __init__(_):
        _.test = 'test'

    def run(_):
        print _.test

10
_có ý nghĩa đặc biệt trong shell Python, nơi nó giữ giá trị trả về cuối cùng. Sử dụng nó như thế này là an toàn, nhưng có thể gây nhầm lẫn; Tôi sẽ tránh nó.
Cairnarvon

Không, điều đó không tốt hơn nhưng tại sao không phải là một chữ cái thay vào đó, ví dụ shoặc m(bắt chước C ++)
javadba

0

Từ: Tự địa ngục - Chức năng nhà nước hơn.

... một phương pháp lai hoạt động tốt nhất. Tất cả các phương thức lớp của bạn thực sự tính toán nên được chuyển thành các bao đóng và các phần mở rộng để dọn sạch cú pháp nên được giữ trong các lớp. Nhét các bao đóng vào các lớp, coi lớp giống như một không gian tên. Các bao đóng về cơ bản là các hàm tĩnh và do đó không yêu cầu tự *, ngay cả trong lớp ...


Việc đóng cửa rất tốt cho các trường hợp sử dụng nhỏ, nhưng việc sử dụng chúng kéo dài sẽ làm tăng chi phí bộ nhớ của chương trình khá nhiều (vì bạn đang sử dụng OO dựa trên nguyên mẫu thay vì OO dựa trên lớp - vì vậy mỗi đối tượng yêu cầu bộ chức năng riêng thay vì giữ một tập hợp các hàm chung trong một lớp). Hơn nữa, nó sẽ ngăn bạn có thể sử dụng các phương thức ma thuật / dunder (ví dụ __str__và tương tự) vì chúng được gọi theo một cách khác với các phương thức thông thường.
Cồn

0

Tôi nghĩ rằng nó sẽ dễ dàng và dễ đọc hơn nếu có một câu lệnh thành viên của Cameron, giống như có toàn cầu, vì vậy bạn có thể nói với người phiên dịch là thành viên của đối tượng của lớp.

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.