Jak uniknąć jawnego „ja” w Pythonie?

131

Uczyłem się Pythona, postępując zgodnie z kilkoma samouczkami dotyczącymi pygame .

Tam znalazłem szerokie zastosowanie słowa kluczowego self , a ponieważ wywodzę się głównie z języka Java, stwierdzam, że ciągle zapominam o wpisaniu self . Na przykład zamiast self.rect.centerxwpisywać rect.centerx, ponieważ dla mnie rect jest już zmienną składową klasy.

Równoległość Java, o której mogę pomyśleć w tej sytuacji, polega na poprzedzeniu wszystkich odwołań do zmiennych składowych tym .

Czy utknąłem z przedrostkiem wszystkich zmiennych składowych self , czy jest sposób ich zadeklarowania, który pozwoliłby mi uniknąć konieczności robienia tego?

Nawet jeśli to, co sugeruję, nie jest pythonowe , nadal chciałbym wiedzieć, czy jest to możliwe.

Rzuciłem okiem na te powiązane pytania SO, ale nie do końca odpowiadają one, o co mi chodzi:

bguiz
źródło
5
Pochodzę z języka Java i uważam to za naturalne, ale wyraźnie dodaję „this” do każdego wywołania, aby było jaśniejsze, że odnoszę się do zmiennej instancji.
Uri
4
Czy znasz konwencję m_prefiksu dla wszystkich nazw członków, którą przestrzegają niektórzy programiści C ++ / Java? W self.podobny sposób użycie ułatwia czytelność. Powinieneś także przeczytać dirtsimple.org/2004/12/python-is-not-java.html .
Beni Cherniavsky-Paskin
2
Chociaż zwykle m_jest używany tylko dla niepublicznych niestatycznych składowych danych (przynajmniej w C ++).
@Beni, świetny powiązany artykuł, i tak, faktycznie postępuję zgodnie z konwencją używania mVariableNamezmiennych składowych podczas kodowania w Javie. Myślę, że komentarz @ Anurag podsumowuje to całkiem dobrze, ponieważ programista Java powinien zrobić, ucząc się Pythona.
bguiz
11
Więc, dlaczego wszyscy mówią OP, dlaczego używanie self jest dobre / konieczne / itp. ale nikt nie mówi, czy można tego jakoś uniknąć? Nawet jeśli przez jakąś brudną sztuczkę?
einpoklum

Odpowiedzi:

99

Python wymaga określenia self. W rezultacie nigdy nie ma nieporozumień co do tego, co jest składnikiem, a co nie, nawet bez widocznej pełnej definicji klasy. Prowadzi to do przydatnych właściwości, takich jak: nie można dodawać elementów członkowskich, które przypadkowo przesłaniają elementy niebędące członkami i tym samym łamią kod.

Jeden skrajny przykład: możesz napisać klasę bez wiedzy o tym, jakie klasy bazowe może mieć, i zawsze wiedzieć, czy uzyskujesz dostęp do członka, czy nie:

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

To cały kod! (some_function zwraca typ używany jako podstawa).

Inny, w którym metody klasy są tworzone dynamicznie:

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

Pamiętaj, że oba te przykłady są ekstremalne i nie będziesz ich widzieć codziennie, ani nie sugeruję, abyś często pisał taki kod, ale jasno pokazują aspekty tego, jak ja sam jesteś wyraźnie wymagany.


źródło
4
Dziękuję Ci. Ta odpowiedź trafia w sedno, ponieważ wyjaśnia również korzyści płynące z używania self.
bguiz
2
@Roger Pate: Przestań edytować moje pytanie, aby usunąć z niego Pythona. Myślę, że należy do tego. (i dzięki za odpowiedź!)
bguiz
3
@bguiz: Konwencja SO nie powielać tagów w tytule. Jednak kiedy redagowałem 2 dni temu, nie widziałem, żebyś przywrócił tytuł 7 miesięcy temu.
1
Byłoby miło, gdyby jaźń mogła zostać zredukowana do jednej postaci.
dwjohnston
5
To właściwie raczej głupio brzmiący powód. Python może nie zaakceptować shadowingu i wymagać, abyś zadeklarował to konkretnie, tj. „Pobłogosław” twoje pole cieniowania jakimś rodzajem __shadow__ myFieldName. Zapobiegałoby to również przypadkowemu cieniowaniu, prawda?
einpoklum
39

Wszystkie poprzednie odpowiedzi są w zasadzie wariantami „nie możesz” lub „nie powinieneś”. Chociaż zgadzam się z tym drugim zdaniem, to pytanie technicznie nadal pozostaje bez odpowiedzi.

Ponadto istnieją uzasadnione powody, dla których ktoś mógłby chcieć zrobić coś podobnego do tego, o co chodzi w tym pytaniu. Czasami napotykam na długie równania matematyczne, w których używanie długich nazw sprawia, że ​​równanie jest nierozpoznawalne. Oto kilka sposobów, jak możesz to zrobić na przykładzie puszki:

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

Trzeci przykład - tj. Używanie for k in self.__dict__ : exec(k+'= self.'+k)jest w zasadzie tym, o co właściwie chodzi w pytaniu, ale pozwól mi wyjaśnić, że nie uważam, że jest to ogólnie dobry pomysł.

Aby uzyskać więcej informacji i sposobów iteracji po zmiennych klasowych, a nawet funkcjach, zobacz odpowiedzi i dyskusję na to pytanie . Omówienie innych sposobów dynamicznego nazywania zmiennych oraz powodów, dla których zwykle nie jest to dobry pomysł, można znaleźć w tym poście na blogu .

AKTUALIZACJA: Wydaje się, że nie ma możliwości dynamicznej aktualizacji lub zmiany ustawień lokalnych w funkcji w Pythonie3, więc calc3 i podobne warianty nie są już możliwe. Jedynym rozwiązaniem kompatybilnym z Python3, o którym mogę teraz pomyśleć, jest użycie 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 )

Co znowu byłoby straszną praktyką w ogóle.

argentum2f
źródło
4
Znakomity! To jest najbardziej (jedyna?) Poprawna odpowiedź. +1 Ty też w wyjątkowy sposób podajesz praktyczny powód, aby to zrobić. + 1i
David Lotts
Po utworzeniu klasy i przeniesieniu do niej kodu: teraz wszystkie metody i zmienne nie są już rozpoznawane (brak siebie ...) przypomina mi się jeszcze jeden powód, dla którego Python i nie dogaduję się. Dzięki za to jako pomysł. Nie rozwiązuje ogólnie problemu / bólu głowy / nieczytelności, ale zapewnia skromne obejście.
javadba
Dlaczego po prostu nie zaktualizować localszamiast używać exec?
Nathan
Próbowałem locals().update(self.__dict__)w Pythonie 2 i 3, ale to nie zadziałało. W pythonie3 nawet sztuczka „exec” nie wchodzi w grę. Z drugiej strony globals().update(self.__dict__)działa, ale ogólnie byłaby to straszna praktyka.
argentum2f
26

Właściwie selfnie jest słowem kluczowym, to tylko nazwa tradycyjnie nadana pierwszemu parametrowi metod instancji w Pythonie. I tego pierwszego parametru nie można pominąć, ponieważ jest to jedyny mechanizm, jaki posiada metoda, aby wiedzieć, która instancja Twojej klasy jest wywoływana.

Michał Marczyk
źródło
1
Ta odpowiedź, zwłaszcza drugie zdanie, jest znacznie bardziej przydatna niż zaakceptowana odpowiedź dla mnie, ponieważ mogę wiedzieć, że `` wyraźne ja '' jest tylko jednym z ograniczeń w Pythonie i nie można go uniknąć
QuestionDriven
21

Możesz na przykład użyć dowolnej nazwy

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

lub nawet

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

ale utkniesz z użyciem nazwy zakresu.

Nie polecam używania czegoś innego dla siebie, chyba że masz przekonujący powód, ponieważ byłoby to obce dla doświadczonych pytonistów.

Esteban Küber
źródło
26
Możesz to zrobić, ale nie rób tego ! Nie ma powodu, aby Twój kod był dziwniejszy niż powinien. Tak, możesz to nazwać dowolnie, ale konwencja jest taka, aby to nazywać selfi powinieneś postępować zgodnie z konwencją. Dzięki temu Twój kod będzie łatwiejszy do zrozumienia dla każdego doświadczonego programisty Pythona, który go obejrzy. (Obejmuje to ciebie, za sześć miesięcy, próbujesz dowiedzieć się, co robi twój stary program!)
Steveha
3
Jeszcze bardziej obcy:def function(_, variable): _.variable = variable
Bob Stein
1
@ BobStein-VisiBone jeszcze bardziej obcy:def funcion(*args): args[0].variable = args[1]
Aemyl
4
@steveha nie poleca tego, ta informacja jest bardzo pomocna dla ludzi takich jak ja, którzy nie wiedzieli, że można używać innych słów kluczowych niż ja i zastanawiają się, dlaczego obiekt własnej klasy jest przekazywany.
Sven van den Boogaart
> „dziwniejszy”. Python jest dziwny - szczególnie jego struktury klasowe i to użycie self jest kanarkiem dla wspierania antyczytelności. Wiem, że po tym przyjdzie wielu obrońców, ale to nie zmienia prawdziwości.
javadba
9

tak, musisz zawsze określać self, ponieważ wyraźne jest lepsze niż niejawne, zgodnie z filozofią Pythona.

Dowiesz się również, że sposób, w jaki programujesz w Pythonie, bardzo różni się od sposobu, w jaki programujesz w Javie, stąd użycie selfma tendencję do zmniejszania się, ponieważ nie wyświetlasz wszystkiego wewnątrz obiektu. Zamiast tego w większym stopniu wykorzystujesz funkcję na poziomie modułu, którą można lepiej przetestować.

tak poza tym. Na początku tego nienawidziłem, teraz nienawidzę czegoś odwrotnego. to samo dla sterowania przepływem sterowanym wcięciem.

Stefano Borini
źródło
2
„W większym stopniu korzystasz z funkcji na poziomie modułu, którą można lepiej przetestować” jest wątpliwe i zdecydowanie się z tym nie zgadzam. Prawdą jest, że nie jesteś zmuszony uczynić wszystkiego metodą (statyczną lub nie) jakiejś klasy, niezależnie od tego, czy jest to "logicznie poziom modułu", ale to nie ma nic wspólnego z sobą i nie ma żadnego znaczącego wpływu na testowanie w jedną lub drugą stronę (dla mnie).
Wynika to z mojego doświadczenia. Nie podążam za tym za mantrę za każdym razem, ale ułatwia to testowanie, jeśli umieścisz cokolwiek, co nie wymaga dostępu do zmiennych składowych, jako oddzielną, niezależną metodę. tak, oddzielasz logikę od danych, co tak, jest przeciwko OOP, ale są razem, tylko na poziomie modułu. Nie daję ani jednej, ani drugiej oceny „najlepszy”, to tylko kwestia gustu. Czasami stwierdzam, że określam metody klas, które nie mają nic wspólnego z samą klasą, ponieważ selfw żaden sposób nie dotykają . Więc jaki jest sens posiadania ich na zajęciach?
Stefano Borini
Nie zgadzam się z obydwoma częściami (wydaje się, że używam „niemetodowych” nie częściej niż w innych językach), ale „co można lepiej przetestować” oznacza, że ​​jeden sposób jest lepszy od drugiego (czy tak właśnie ja czytać? nie wydaje się prawdopodobne), podczas gdy w moim doświadczeniu nie znajduję uzasadnienia. Zauważ, że nie mówię, że powinieneś zawsze używać jednej lub drugiej, mówię tylko, że metody i metody nie są w równym stopniu testowane.
5
tak, oczywiście, że możesz, ale podejście jest inne. Obiekt ma stan, którego nie ma metoda na poziomie modułu. Jeśli okaże się, że test nie powiedzie się podczas testowania metody na poziomie klasy, dwie rzeczy mogły być nie tak: 1) stan obiektu w momencie wywołania 2) sama metoda. Jeśli masz metodę bezstanową na poziomie modułu, może wystąpić tylko przypadek 2. Przeniosłeś konfigurację z obiektu (gdzie jest to czarna skrzynka, ponieważ test dotyczy, ponieważ jest zarządzany przez ostatecznie złożoną logikę wewnątrz obiektu) do zestawu testowego. Zmniejszasz złożoność i zachowujesz ściślejszą kontrolę nad konfiguracją.
Stefano Borini
1
„Jeśli masz metodę bezstanową na poziomie modułu”, a co z metodą stanową na poziomie modułu? Mówisz mi tylko o tym, że funkcje bezstanowe są łatwiejsze do przetestowania niż funkcje stanowe i zgadzam się z tym, ale nie ma to nic wspólnego z metodami i niemetodami. Zobacz parametr self jako dokładnie taki: po prostu kolejny parametr funkcji.
4

„Self” to konwencjonalny symbol zastępczy dla bieżącej instancji obiektu klasy. Jest używany, gdy chcesz odwołać się do właściwości, pola lub metody obiektu wewnątrz klasy, tak jakbyś odnosił się do „samego siebie”. Ale żeby to skrócić, ktoś w dziedzinie programowania w Pythonie zaczął używać "self", inne dziedziny używają "this", ale robią to jako słowo kluczowe, którego nie można zastąpić. Raczej użyłem „jego”, aby zwiększyć czytelność kodu. To jedna z dobrych rzeczy w Pythonie - masz swobodę wyboru własnego symbolu zastępczego dla instancji obiektu innego niż „self”. Przykład dla siebie:

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)

Teraz zamieniamy „siebie” na „jego”:

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)

który jest teraz bardziej czytelny?

LEMUEL ADANE
źródło
dlaczego nie po prostu s(lub jakaś inna pojedyncza litera) zamiastits
javadba
„jego” ma znaczenie, a „s” nie.
LEMUEL ADANE
sma to samo znaczenie: alias do instancji klasy. musiałbym sprawdzić, co itsoznacza w kontekście tak samo
javadba
Obie nie są dla mnie czytelne. Nadal nielogiczne jest przyjmowanie „siebie” jako parametru zewnętrznego, nie ma to żadnego sensu.
Cesar
3

self jest częścią składni Pythona, która umożliwia dostęp do elementów obiektów, więc obawiam się, że utknąłeś z tym

Charles Ma
źródło
2
self to sposób na poinformowanie modyfikatora dostępu bez jego rzeczywistego używania. +1
Perpetualcoder
1

Właściwie możesz skorzystać z przepisu „Ukryte ja” z prezentacji Armina Ronachera „5 lat złych pomysłów” (wygoogluj to).

To bardzo sprytny przepis, jak prawie wszystko od Armina Ronachera, ale nie sądzę, żeby ten pomysł był zbyt atrakcyjny. Myślę, że wolałbym jawne to w C # / Java.

Aktualizacja. Link do „przepisu na zły pomysł”: https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58

Alex Yu
źródło
Czy to jest link, do którego się odnosisz? Jeśli tak,
proszę
Nie, "zły pomysł" Armina wygląda bardziej śmiesznie jak na mój gust. Dołączyłem link.
Alex Yu,
Zanim klikniesz odsyłacz do przepisu, zwróć uwagę, że powoduje to ukrywanie się na liście parametrów def method(<del> self </del> ), ale self.variablenadal jest wymagane w przypadku tego sprytnego hacka.
David Lotts
0

Tak, jaźń jest nudna. Ale czy jest lepiej?

class Test:

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

    def run(_):
        print _.test

źródło
10
_ma specjalne znaczenie w powłoce Pythona, gdzie przechowuje ostatnią zwróconą wartość. Używanie go w ten sposób jest bezpieczne, ale może być mylące; Unikałbym tego.
Cairnarvon
Nie, to nie jest lepsze, ale dlaczego nie zamiast jednej litery, np. sLub m(aby naśladować C ++)
javadba
0

Od: Self Hell - Więcej funkcji stanowych.

... podejście hybrydowe działa najlepiej. Wszystkie metody klas, które faktycznie wykonują obliczenia, powinny zostać przeniesione do domknięć, a rozszerzenia czyszczące składnię powinny zostać zachowane w klasach. Upychaj zamknięcia na klasy, traktując klasę podobnie jak przestrzeń nazw. Zamknięcia są zasadniczo funkcjami statycznymi, a więc nie wymagają self *, nawet w klasie ...

user5554473
źródło
Zamknięcia są dobre dla małych przypadków użycia, ale ich rozszerzone użycie znacznie zwiększy narzut pamięci programu (ponieważ używasz obiektu poza obiektowego opartego na prototypach, a nie na klasach - więc każdy obiekt wymaga własnego zestawu funkcji raczej utrzymywanie wspólnego zestawu funkcji w klasie). Co więcej, uniemożliwi ci to magię / dunder metod (np. __str__I tym podobne), ponieważ są one wywoływane w inny sposób niż zwykłe metody.
Wydmy
0

Myślę, że byłoby łatwiej i bardziej czytelnie, gdyby istniało stwierdzenie „member”, tak jak jest „global”, dzięki czemu można powiedzieć tłumaczowi, które obiekty należą do klasy.

Pablo Villar
źródło