Як уникнути явного «я» в Python?


131

Я вивчав Python, дотримуючись деяких навчальних посібників із пігмаму .

У цьому я знайшов широке використання ключового слова self , і виходячи з передусім фону Java, я вважаю, що я все ще забуваю вводити self . Наприклад, замість того , щоб self.rect.centerxя надрукував би rect.centerx, тому що, мені, Прямокутник вже змінна член класу.

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

Чи я приклеював префіксацію всіх змінних членів самостійно , чи є спосіб оголосити їх, що дозволить мені уникнути необхідності цього робити?

Навіть якщо те, що я пропоную, не є пітонічним , я все одно хотів би знати, чи це можливо.

Я переглянув ці пов'язані питання ЗО, але вони не дуже відповідають на те, що я хочу:


5
Я надходжу з фону Java і вважаю це природним, але я чітко додаю "це" до кожного виклику, щоб зрозуміти, що я маю на увазі змінну екземпляра.
Урі

4
Чи знайомі ви з домовленістю m_префікса для всіх імен членів, які спостерігаються деякими програмістами C ++ / Java? Використання self.допомагає читати подібним чином. Також слід прочитати dirtsimple.org/2004/12/python-is-not-java.html .
Бені Чернявський-Паскін

2
Хоча зазвичай m_використовується лише для непублічних нестатичних членів даних (принаймні, на C ++).

@Beni велика пов'язана стаття, і так, я дійсно дотримуюся конвенції щодо використання mVariableNameдля змінних членів при кодуванні в Java. Я думаю, що коментар @ Anurag підсумовує це досить добре, для того, що повинен робити Java-розробник, коли вивчає пітон.
bguiz

11
Отже, як же всі кажуть на ОП, чому використовувати себе - це добре / необхідно / тощо. але ніхто не каже, чи можна цього якось уникнути? Навіть якщо якоюсь брудною хитрістю?
einpoklum

Відповіді:


99

Python вимагає уточнення себе. Результат - ніколи не виникає плутанини щодо того, що є членом, а що ні, навіть не видно повного визначення класу. Це призводить до корисних властивостей, таких як: ви не можете додавати членів, які випадково затінюють не членів і тим самим порушують код.

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

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

Ось повний код! (деяка_функція повертає тип, який використовується в якості основи.)

Інший, де методи класу динамічно складаються:

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

2
@Roger Pate: Будь ласка, припиніть редагувати моє питання, щоб видалити python з нього. Я думаю, що вона там належить. (і спасибі за відповідь!)
bguiz

3
@bguiz: Це звичайно не дублювати теги в заголовку. Однак, коли я редагував 2 дні тому, я не бачив, як ти змінив заголовок 7 місяців тому.

1
Було б добре, якби самості можна було звести до одного персонажа.
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 )

Що, знову ж, було б взагалі жахливою практикою.


4
Блискуче! Це найбільш (тільки?) Правильна відповідь. +1 Ви також однозначно даєте практичну причину для цього. + 1i
Девід Лоттс

Після створення класу та переміщення коду всередині нього: тепер усі методи та змінні вже не розпізнаються (немає самоврядування ...) Мені нагадується ще одна причина python, і я не ладжу. Дякую за це як ідею. Це не вирішує загальну проблему / головний біль / нечитабельність, але забезпечує скромний спосіб вирішення.
javadba

Чому б не просто оновити, localsа не використовувати exec?
Натан

Я спробував locals().update(self.__dict__)у python 2 та 3, але це не вийшло. У python3 навіть трюк "exec" вже не є варіантом. З іншого боку, globals().update(self.__dict__)це працює, але взагалі було б жахливою практикою.
argentum2f

26

Насправді selfце не ключове слово, це лише ім'я, умовно дане першому параметру методів екземпляра в Python. І цей перший параметр неможливо пропустити, оскільки це єдиний механізм, яким метод знає, який саме екземпляр вашого класу викликається.


1
Ця відповідь, особливо у 2-му реченні, набагато корисніша, ніж прийнята для мене відповідь, тому що я можу знати, що «явне самоврядування» є лише одним із обмежень 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, який дивиться на нього. (Сюди ввійшли ви, через півроку, намагаєтесь зрозуміти, що робить ваша стара програма!)
steveha

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 сильно відрізняється від способу програмування у java, отже, використання selfмає тенденцію до зменшення, оскільки ви не проектуєте все всередині об’єкта. Скоріше, ви більше використовуєте функцію рівня модуля, яку можна краще перевірити.

до речі. Я спочатку ненавидів це, зараз ненавиджу навпаки. те ж саме для управління потоком з відступом.


2
"Ви більше використовуєте функцію рівня модуля, яку можна краще перевірити" сумнівно, і мені доведеться категорично не погоджуватися. Це правда, що ви не змушені робити все методом (статичним чи ні) якогось класу, незалежно від того, "це логічно рівень модуля", але це не має нічого спільного з "я" і не має суттєвого впливу на тестування так чи інакше (для мене).

Це виникає з мого досвіду. Я не слідкую за цим як мантру кожен раз, але це полегшує тестування, якщо ви ставите що-небудь, що не потребує доступу до змінних членів, як окремий, незалежний метод. так, ви розділяєте логіку від даних, яка так, проти OOP, але вони разом, просто на рівні модуля. Я не даю тій чи іншій «найкращій» оцінці, це лише питання смаку. Іноді я можу вказати методи класу, які не мають нічого спільного з самим класом, оскільки вони selfжодним чином не торкаються . Тож який сенс мати їх на уроці?
Стефано Борині

Я не погоджуюся з обома його частинами (здається, я використовую "не методи" не частіше, ніж іншими мовами), але "які можна краще перевірити", це означає, що один із способів є вищим за інший (саме так я прочитати це? не здається ймовірним), поки я не знаходжу підтримки для цього. Зауважте, що я не кажу, що ви завжди повинні використовувати те чи інше, я лише кажу, що методи і не методи можуть однаково перевірятися.

5
так, звичайно, можна, але підхід інший. Об'єкт має стан, метод модульного рівня не має. Якщо виявити тест не вдається під час тестування методу рівня класу, дві речі могли бути помилковими: 1) стан об'єкта під час виклику 2) сам метод. Якщо у вас є метод на рівні модуля без стану, може статися лише випадок 2. Ви перемістили налаштування з об'єкта (там, де це тест - це чорна скринька, оскільки це керується з часом складною логікою всередині об'єкта), до тестового набору. Ви зменшуєте складність і тримаєте жорсткіший контроль налаштування.
Стефано Борині

1
"Якщо у вас є метод рівня без модуля без стану", що робити з методом стану стану модуля? Все, що ви мені говорите, - це те, що функції без громадянства простіше перевірити, ніж стаціонарні, і я погоджуся з цим, але це не має нічого спільного з методами проти неметодів. Перегляньте параметр self як саме це: просто ще один параметр функції.

4

"Я" - це звичайний заповнювач місця в поточному екземплярі об'єкта класу. Його використовують, коли ви хочете посилатися на властивість об'єкта або поле чи метод всередині класу так, ніби ви маєте на увазі "себе". Але щоб скоротити, хтось у царині програмування Python почав використовувати "self", інші сфери використовують "this", але вони роблять це як ключове слово, яке не можна замінити. Я скоріше використовував "його" для збільшення читабельності коду. Це одне з хороших речей у Python - у вас є свобода вибору власного заповнювача для екземпляра об'єкта, окрім як «я». Приклад для себе:

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)

Тепер ми замінюємо "Я" на "його":

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означає в контексті саме те саме
javadba

Обидва мені не читаються. Все ще нелогічно сприймати "себе" як зовнішній параметр, це не має сенсу.
Сезар

3

Я є частиною синтаксису python для доступу до членів об’єктів, тому я боюся, що ви з цим застрягли


2
self - це спосіб повідомити модифікатор доступу, не використовуючи його дійсно. +1
Perpetualcoder

1

Насправді ви можете скористатися рецептом "Неявна себе" з презентації Арміна Ронахера "5 років поганих ідей" (google it)

Це дуже розумний рецепт, як майже все від Armin Ronacher, але я не думаю, що ця ідея дуже приваблива. Я думаю, що я вважаю за краще явне це в C # / Java.

Оновлення. Посилання на "рецепт поганої ідеї": https://speakerdeck.com/mitsuhiko/5-years-of-bad-ideas?slide=58


Є чи це зв'язок ви маєте на увазі , що ви мали в виду? Якщо так, будь ласка, включіть у свою відповідь
reubenjohn

Ні, Армін "погана ідея" виглядає кумеднішим на мій смак. Я включив посилання.
Олексій Ю.

Перш ніж натиснути посилання на рецепт, зауважте, що це робить його неявним у списку параметрів def method(<del> self </del> ), але self.variableвсе-таки потрібен цей хитрому хаку.
Девід Лоттс

0

Так, самолюбство нудно. Але, чи краще?

class Test:

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

    def run(_):
        print _.test

10
_має особливе значення в оболонці Python, де він містить останнє повернене значення. Це безпечно використовувати так, але потенційно заплутано; Я б цього уникав.
Кайнарвон

Ні, це не краще, але чому б не один лист натомість, наприклад, sабо m(щоб імітувати C ++)
javadba

0

Від: Self Hell - Більш видатні функції.

... найкраще працює гібридний підхід. Усі ваші методи класу, які фактично роблять обчислення, слід перемістити у закриття, а розширення для очищення синтаксису слід зберігати у класах. Складіть закриття в класи, трактуючи клас так само, як простір імен. Закриття - це, по суті, статичні функції, і тому не потребує себе * навіть у класі ...


Закриття є чудовим для невеликих випадків використання, але розширене їх використання значно збільшить накладні витрати на пам'ять програми (як ви використовуєте OO, заснований на прототипі, а не OO на основі класу - тому кожен об'єкт вимагає власного набору функцій скоріше збереження загального набору функцій у класі). Крім того, це перешкодить вам мати можливість магічних методів (наприклад, __str__тощо), оскільки вони викликаються по-іншому до звичайних методів.
Дюни

0

Я думаю, що було б легше і легше читати, якби було висловлювання «член» так само, як є «глобальне», щоб ви могли сказати перекладачеві, які є об’єкти, що належать до класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.