Как избежать явного «я» в Python?

131

Я изучал Python, следуя некоторым руководствам по pygame .

В нем я нашел широкое использование ключевого слова self и, исходя в основном из Java, обнаружил, что все время забываю вводить self . Например, вместо self.rect.centerxя бы напечатал rect.centerx, потому что для меня rect уже является переменной-членом класса.

Параллель Java, о которой я могу думать в этой ситуации, заключается в том, что все ссылки на переменные-члены должны быть префиксом этого .

Я застрял в префиксе всех переменных-членов с помощью self или есть способ объявить их, что позволило бы мне избежать этого?

Даже если то, что я предлагаю, не питоническое , я все равно хотел бы знать, возможно ли это.

Я взглянул на эти связанные вопросы SO, но они не совсем отвечают на то, что мне нужно:

bguiz
источник
5
Я пришел из среды Java и считаю это естественным, но я явно добавляю "this" к каждому вызову, чтобы было понятнее, что я имею в виду переменную экземпляра.
Ури,
4
Вы знакомы с соглашением о m_префиксе для всех имен членов, которое наблюдается некоторыми программистами на C ++ / Java? self.Подобным образом использование улучшает читаемость. Также вам следует прочитать dirtsimple.org/2004/12/python-is-not-java.html .
Бени Чернявский-Паскин
2
Хотя обычно m_используется только для непубличных нестатических элементов данных (по крайней мере, в C ++).
@Beni отличная связанная статья, и да, действительно, я действительно следую соглашению об использовании mVariableNameпеременных-членов при кодировании на Java. Я думаю, что комментарий @ Anurag довольно хорошо подводит итог тому, что должен делать Java-разработчик при изучении python.
bguiz
11
Итак, почему все говорят OP, почему использование себя хорошо / необходимо / и т. Д. но никто не говорит, можно ли этого как-то избежать? Даже если по какой-то пакости?
einpoklum

Ответы:

99

Python требует указания self. В результате никогда не возникает путаницы в том, что является членом, а что нет, даже без видимого полного определения класса. Это приводит к полезным свойствам, таким как: вы не можете добавлять элементы, которые случайно затеняют нечленов и тем самым нарушают код.

Один крайний пример: вы можете написать класс, не зная, какие базовые классы он может иметь, и всегда знать, обращаетесь ли вы к члену или нет:

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

Это полный код! (some_function возвращает тип, используемый в качестве основы.)

Другой, где методы класса динамически составляются:

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

Помните, что оба этих примера являются экстремальными, и вы не будете видеть их каждый день, и я не предлагаю вам часто писать такой код, но они ясно показывают, что аспекты себя явно требуются.


источник
4
Спасибо. Этот ответ попадает в точку, потому что он также объясняет преимущества использования self.
bguiz 01
2
@Roger Pate: Пожалуйста, прекратите редактировать мой вопрос, чтобы удалить из него python. Я думаю, что ему здесь место. (и спасибо за ответ!)
bguiz 01
3
@bguiz: Это соглашение SO не дублировать теги в заголовке. Однако, когда я редактировал 2 дня назад, я не видел, чтобы вы вернули заголовок 7 месяцев назад.
1
Было бы неплохо, если бы self можно было свести к одному персонажу.
dwjohnston
5
На самом деле это довольно глупо звучащая причина. Python может не принять затенение и потребовать от вас объявить его специально, то есть «благословить» ваше поле затенения каким-либо __shadow__ myFieldName. Это также предотвратит случайное затенение, не так ли?
einpoklum
39

Предыдущие ответы в основном представляют собой варианты «нельзя» или «не следует». Хотя я согласен с последним мнением, технически вопрос все еще остается без ответа.

Более того, есть законные причины, по которым кто-то может захотеть сделать что-то в соответствии с тем, что задает фактический вопрос. Иногда я сталкиваюсь с длинными математическими уравнениями, в которых использование длинных имен делает уравнение неузнаваемым. Вот несколько способов сделать это в готовом примере:

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

Третий пример - т.е. использование for k in self.__dict__ : exec(k+'= self.'+k)- это в основном то, о чем на самом деле спрашивается вопрос, но позвольте мне прояснить, что я не думаю, что в целом это хорошая идея.

Для получения дополнительной информации и способов перебора переменных класса или даже функций см. Ответы и обсуждение этого вопроса . Для обсуждения других способов динамического именования переменных и того, почему это обычно не очень хорошая идея, см. Это сообщение в блоге .

ОБНОВЛЕНИЕ: похоже, нет способа динамически обновлять или изменять локальные переменные в функции в Python3, поэтому calc3 и подобные варианты больше невозможны. Единственное решение, совместимое с python3, которое я могу придумать, - это использовать 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 )

Что, опять же, было бы ужасной практикой в ​​целом.

argentum2f
источник
4
Brilliant! Это наиболее (единственный?) Правильный ответ. +1 Вы также однозначно даете практическую причину для этого. +1i
Дэвид Лоттс
После создания класса и перемещения кода внутри него: теперь все методы и переменные больше не распознаются (нет себя ...) Мне напомнили еще об одной причине, по которой я не ладю с python. Спасибо за эту идею. Это не решает проблему / головную боль / нечитаемость в целом, но предоставляет скромное решение.
javadba
Почему бы просто не обновить localsвместо использования exec?
Натан,
Я пробовал использовать locals().update(self.__dict__)Python 2 и 3, но это не сработало. В python3 больше не подходит даже трюк с «exec». С другой стороны, globals().update(self.__dict__)работает, но в целом было бы ужасной практикой.
argentum2f
26

На самом деле selfэто не ключевое слово, это просто имя, условно присвоенное первому параметру методов экземпляра в Python. И этот первый параметр нельзя пропустить, так как это единственный механизм, который имеет метод, чтобы узнать, какой экземпляр вашего класса он вызывается.

Михал Марчик
источник
1
Этот ответ, особенно второе предложение, гораздо более полезен для меня, чем принятый ответ, потому что я знаю, что «явное Я» - это лишь одно из ограничений в Python, которого нельзя избежать
QuestionDriven
21

Вы можете использовать любое имя, которое хотите, например

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

или даже

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

но вы застряли в использовании имени для области видимости.

Я не рекомендую вам использовать что-то отличное от себя, если у вас нет убедительной причины, поскольку это сделает это чуждым для опытных питонистов.

Эстебан Кюбер
источник
26
Вы можете это сделать, но не делайте этого ! Нет причин делать ваш код более странным, чем он должен быть. Да, вы можете называть это как угодно, но принято называть это self, и вы должны следовать этому соглашению. Это упростит понимание вашего кода для любого опытного программиста на Python, который взглянет на него. (В том числе и вы, через шесть месяцев, пытаетесь выяснить, что делает ваша старая программа!)
стивеха
3
Еще более инопланетный:def function(_, variable): _.variable = variable
Боб Штайн
1
@ BobStein-VisiBone еще более инопланетный:def funcion(*args): args[0].variable = args[1]
Эмиль
4
@steveha, он не рекомендует это, эта информация очень полезна для людей вроде меня, которые не знали, что вы можете использовать ключевые слова, отличные от self, и задавались вопросом, почему передается объект собственного класса.
Sven van den Boogaart
> "страннее". Python является странно - особенно его классы структур , и это использование самости является канарейкой для поддержки анти читаемости. Я знаю, что после этого придет много защитников, но это не меняет правды.
javadba
9

да, вы всегда должны указывать self, потому что явное лучше, чем неявное, согласно философии Python.

Вы также обнаружите, что способ программирования на Python очень отличается от того, как вы программируете на Java, поэтому использование self имеет тенденцию к уменьшению, поскольку вы не проецируете все внутри объекта. Скорее, вы шире используете функции уровня модуля, которые можно лучше протестировать.

Кстати. Сначала я ненавидел это, теперь ненавижу противоположное. то же самое для управления потоком с отступом.

Стефано Борини
источник
2
«Вы шире используете функции уровня модуля, которые можно лучше протестировать» - сомнительно, и я категорически не согласен. Это правда, что вас не заставляют делать все методом (статическим или нет) какого-либо класса, независимо от того, является ли он «логически модульным», но это не имеет ничего общего с собой и не оказывает существенного влияния на тестирование так или иначе (для меня).
Это вытекает из моего опыта. Я не следую этому каждый раз как мантру, но это упрощает тестирование, если вы помещаете что-либо, не требующее доступа к переменным-членам, как отдельный независимый метод. да, вы отделяете логику от данных, что да, против ООП, но они вместе, только на уровне модуля. Я не ставлю ни тому, ни другому «лучшую» оценку, это дело вкуса. Иногда я обнаруживаю, что определяю методы класса, которые не имеют ничего общего с самим классом, поскольку они selfникоим образом не касаются . Так какой смысл иметь их в классе?
Стефано Борини
Я не согласен с обеими его частями (кажется, я использую «неметоды» не чаще, чем на других языках), но «которые можно лучше проверить» действительно означает, что один способ лучше другого (именно так я читать? кажется маловероятным), хотя я не нахожу подтверждения этому на своем опыте. Обратите внимание, что я не говорю, что вы всегда должны использовать один или другой, я только говорю, что методы и неметоды в равной степени могут быть протестированы.
5
да, конечно можно, но подход другой. У объекта есть состояние, а у метода уровня модуля - нет. Если вы обнаружите, что тест не прошел во время тестирования метода уровня класса, две вещи могли быть неправильными: 1) состояние объекта во время вызова 2) сам метод. Если у вас есть метод уровня модуля без сохранения состояния, возможен только случай 2. Вы переместили настройку из объекта (где тест является черным ящиком, поскольку он управляется сложной логикой внутри объекта) в набор тестов. Вы уменьшаете сложность и сохраняете более жесткий контроль над настройкой.
Стефано Борини
1
«Если у вас есть метод уровня модуля без сохранения состояния», как насчет метода уровня модуля с сохранением состояния? Все, что вы мне говорите, это то, что функции без состояния легче тестировать, чем с сохранением состояния, и я согласен с этим, но это не имеет ничего общего с методами и без методов. Рассматривайте параметр self именно так: просто еще один параметр функции.
4

«Я» - это обычный заполнитель текущего экземпляра объекта класса. Он используется, когда вы хотите обратиться к свойству объекта, полю или методу внутри класса, как если бы вы ссылались на «себя». Но чтобы сделать его короче, кто-то из области программирования Python начал использовать «self», в других областях используется «this», но они делают его как ключевое слово, которое нельзя заменить. Я скорее использовал «его» для повышения читабельности кода. Это одна из хороших вещей в Python - у вас есть свобода выбора собственного заполнителя для экземпляра объекта, кроме «self». Пример для себя:

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)

Теперь заменим self на its:

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)

что теперь более читабельно?

ЛЕМУЭЛЬ АДАН
источник
почему бы не просто s(или какая-нибудь другая буква) вместоits
javadba
«его» имеет значение, а «s» - нет.
LEMUEL ADANE
sимеет то же значение: псевдоним экземпляра класса. its
Мне бы все
Мне оба не читаются. Получать «себя» в качестве внешнего параметра все еще нелогично, в этом нет никакого смысла.
Cesar
3

self является частью синтаксиса python для доступа к членам объектов, поэтому я боюсь, что вы застряли на нем

Чарльз Ма
источник
2
self - это способ указать модификатор доступа, не используя его. +1
Perpetualcoder
1

На самом деле вы можете воспользоваться рецептом «Неявное Я» из презентации Армина Ронахера «5 лет плохих идей» (погуглите).

Это очень умный рецепт, как и почти все от Армина Ронахера, но я не думаю, что эта идея очень привлекательна. Я думаю, что предпочел бы это явно в C # / Java.

Обновить. Ссылка на «рецепт плохой идеи»: https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58

Алекс Ю
источник
Является ли это связь вы имеете в виду , что вы имели в виду? Если да, то
пожалуйста, включите
Нет, Армин «плохая идея» на мой вкус смотрится смешнее. Я включил ссылку.
Alex Yu
Прежде чем щелкнуть ссылку рецепта, обратите внимание, что это делает ее неявной в списке параметров def method(<del> self </del> ), но self.variableвсе же требуется для этого умного хака.
Дэвид Лоттс
0

Да, это утомительно. Но так ли лучше?

class Test:

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

    def run(_):
        print _.test

источник
10
_имеет особое значение в оболочке Python, где хранится последнее возвращенное значение. Это безопасно, но может сбивать с толку; Я бы этого избегал.
Cairnarvon
Нет, это не лучше, но почему бы вместо одной буквы, например, sили m(чтобы имитировать C ++)
javadba
0

От: Self Hell - Больше функций с отслеживанием состояния.

... гибридный подход работает лучше всего. Все методы вашего класса, которые фактически выполняют вычисления, должны быть перемещены в замыкания, а расширения для очистки синтаксиса должны храниться в классах. Поместите замыкания в классы, обращаясь с классом как с пространством имен. Замыкания по сути являются статическими функциями и поэтому не требуют selfs * даже в классе ...

user5554473
источник
Замыкания подходят для небольших случаев использования, но их расширенное использование значительно увеличит накладные расходы памяти программы (поскольку вы используете объектно-ориентированный объект на основе прототипов, а не объектно-ориентированный объект на основе классов, поэтому каждый объект требует своего собственного набора функций. скорее сохраняя общий набор функций в классе). Кроме того, это лишит вас возможности использовать методы magic / dunder (например, __str__и т.п.), поскольку они вызываются другим способом, чем обычные методы.
Dunes
0

Я думаю, что было бы проще и читабельнее, если бы был оператор «член» так же, как и «глобальный», чтобы вы могли сообщить интерпретатору, какие объекты являются членами класса.

Пабло Вильяр
источник