Зачем вам явно нужен аргумент «self» в методе Python?

197

При определении метода для класса в Python он выглядит примерно так:

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

Но в некоторых других языках, таких как C #, у вас есть ссылка на объект, к которому привязан метод, с ключевым словом "this" без объявления его в качестве аргумента в прототипе метода.

Было ли это преднамеренным решением о дизайне языка в Python или есть некоторые детали реализации, которые требуют передачи «я» в качестве аргумента?

Readonly
источник
15
Могу поспорить, что вам также будет интересно узнать, почему вам нужно явно писать selfдля доступа к участникам - stackoverflow.com/questions/910020/…
Петр Доброгост
1
Но это выглядит как котельная плита
Raghuveer
Немного сбивает с толку, но стоит понять stackoverflow.com/a/31367197/1815624
CrandellWS

Ответы:

91

Мне нравится цитировать Питерса Zen of Python. «Явное лучше, чем неявное».

В Java и C ++ « this.» может быть выведено, кроме случаев, когда у вас есть имена переменных, которые делают невозможным вывод. Так что иногда это нужно, а иногда нет.

Python решает сделать такие вещи явными, а не основанными на правиле.

Кроме того, поскольку ничего не подразумевается или не предполагается, части реализации раскрыты. self.__class__, self.__dict__И другие «внутренние» структуры доступны очевидным образом.

С. Лотт
источник
53
Хотя было бы неплохо иметь менее загадочное сообщение об ошибке, когда вы его забудете.
Мартин Беккет
9
Однако когда вы вызываете метод, вам не нужно передавать объектную переменную, не нарушает ли это правило явности? Если оставить этот дзен, это должно быть что-то вроде: object.method (object, param1, param2). Выглядит как-то противоречиво ...
Ведмант
10
«явный лучше, чем неявный» - разве «стиль» Python строится на неявных вещах? например, неявные типы данных, неявные границы функций (без {}), неявная область видимости переменных ... если глобальные переменные в модулях доступны в функциях ... почему бы не применить ту же логику / рассуждение к классам? Разве упрощенное правило не будет просто «что-либо, объявленное на более высоком уровне, доступно на более низком уровне», как определено отступом?
Саймон
13
«Явное лучше, чем неявное» Обнаружена ерунда
Вахид Амири
10
давайте посмотрим правде в глаза, это просто плохо. Этому нет оправдания. Это просто ужасная реликвия, но все в порядке.
Тоскан,
63

Это минимизировать разницу между методами и функциями. Это позволяет вам легко генерировать методы в метаклассах или добавлять методы во время выполнения к уже существующим классам.

например

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

Это также (насколько я знаю) облегчает реализацию среды выполнения Python.

Райан
источник
10
+1 за это, чтобы минимизировать разницу между методами и функциями. Это должен быть принят ответ
пользователь
Это также лежит в основе многих связанных объяснений Гвидо.
Марчин
1
Это также показывает, что в Python, когда вы делаете c.bar (), сначала он проверяет экземпляр на наличие атрибутов, а затем проверяет атрибуты класса . Таким образом, вы можете в любое время «прикрепить» данные или функцию (объекты) к классу и ожидать доступа к его экземпляру (т. Е. К dir (instance) будет как). Не только когда вы «создали» экземпляр. Это очень динамично.
Nishant
10
Я действительно не покупаю это. Даже в тех случаях, когда вам нужен родительский класс, вы все равно можете определить его при исполнении. И эквивалентность между методами экземпляра и функциями класса, переданными экземплярами, глупа; Руби прекрасно обходится без них.
zachaysan
2
JavaScript позволяет добавлять методы к объекту во время выполнения, и selfв объявлении функции это не требуется (учтите, возможно, это бросает камни из стеклянного дома, поскольку JavaScript имеет довольно сложную thisсемантику связывания)
Джонатан Бенн
55

Я предлагаю прочитать блог Гвидо ван Россума на эту тему - Почему явное «я» должно остаться .

Когда определение метода оформлено, мы не знаем, будет ли ему автоматически задан параметр «self» или нет: декоратор может превратить функцию в статический метод (у которого нет «self») или метод класса (который имеет забавный вид self, который ссылается на класс, а не на экземпляр), или он может сделать что-то совершенно другое (тривиально написать декоратор, который реализует '@classmethod' или '@staticmethod' в чистом Python). Нет никакого способа не знать, что делает декоратор, наделять ли определяемый метод неявным аргументом self или нет.

Я отвергаю такие хаки, как "@classmethod" и "@staticmethod" в специальном корпусе.

бхадра
источник
16

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

Виктор Ноагбоджи
источник
По соглашению, однако, это должно быть 'self' для экземпляров или 'cls', где задействованы типы (метаклассы mmmm)
pobk
1
Это заставляет указывать self в качестве первого параметра в каждом методе, просто дополнительный текст, который не имеет большого смысла, как для меня. Другие языки прекрасно работают с этим.
Ведмант
я прав ? всегда первый параметр является ссылкой на объект.
Мохаммед Махди Кушак Язди
@MMKY Нет, например с @staticmethodэтим нет.
Марк
1
«Вы просто должны помнить, что первый аргумент в определении метода ...» Я экспериментировал с изменением слова «я» на «kwyjibo», и это все еще работало. Поэтому, как часто объясняют, важно не слово «я», а положение того, что занимает это пространство (?)
RBV
7

Также позволяет вам сделать это: (короче говоря, вызов Outer(3).create_inner_class(4)().weird_sum_with_closure_scope(5)вернет 12, но сделает это самым безумным способом.

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

Конечно, это сложнее представить в таких языках, как Java и C #. Сделав ссылку на себя явной, вы можете ссылаться на любой объект по этой ссылке. Кроме того, такой способ игры с классами во время выполнения сложнее сделать на более статичных языках - не обязательно это хорошо или плохо. Просто явное я допускает существование всего этого безумия.

Кроме того, представьте себе следующее: мы хотели бы настроить поведение методов (для профилирования или какой-то безумной черной магии). Это может привести нас к мысли: что если бы у нас был класс Method, поведение которого мы могли бы переопределить или контролировать?

Ну вот оно:

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

А теперь: InnocentClass().magic_method()будет действовать как ожидалось. Метод будет связан с innocent_selfпараметром InnocentClassи magic_selfэкземпляром MagicMethod. Странно, да? Это как иметь 2 ключевых слова this1и this2на таких языках, как Java и C #. Подобная магия позволяет фреймворкам делать вещи, которые в противном случае были бы гораздо более многословными.

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

влад-Ardelean
источник
4
Когда я рассматриваю ваш первый пример, я могу сделать то же самое в Java: внутренний класс должен вызывать, OuterClass.thisчтобы получить 'self' из внешнего класса, но вы все равно можете использовать его thisкак ссылку на себя; очень похоже на то, что вы делаете здесь, в Python. Для меня не было сложнее представить это. Может быть, это зависит от уровня владения данным языком?
Клар
Но можете ли вы по-прежнему ссылаться на любые области, когда вы находитесь внутри метода анонимного класса, который определен внутри анонимного класса, который определен внутри анонимной реализации интерфейса Something, который, в свою очередь, определен внутри еще одной анонимной реализации Something? В Python вы можете, конечно, обратиться к любой из областей.
vlad-ardelean
Вы правы, в Java вы можете ссылаться на внешний класс только путем вызова его явного имени класса и использования его в качестве префикса this. Неявные ссылки невозможны в Java.
Клар
Интересно, будет ли это работать, хотя: в каждой области (каждый метод) есть локальная переменная, которая ссылается на thisрезультат. Например Object self1 = this;(либо используйте Object, либо что-то менее общее). Тогда, если у вас есть доступ к переменной в высших областях, вы могли бы иметь доступ к self1, self2... selfn. Я думаю, что они должны быть объявлены окончательными или что-то, но это может сработать.
vlad-ardelean
3

Я думаю, что настоящая причина, помимо "Дзен Питона", заключается в том, что функции - это люди первого класса в Python.

Что по существу делает их Объектом. Теперь фундаментальный вопрос: если ваши функции также являются объектами, то в объектно-ориентированной парадигме как бы вы отправляли сообщения объектам, если сами сообщения являются объектами?

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

Это означает, что единственно возможное решение - явно передать «я» (контекст выполнения).

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

pankajdoharey
источник
Привет, я новичок в Python (из Java-фона), и я не совсем понял, что вы сказали "как бы вы отправляли сообщения объектам, когда сами сообщения являются объектами". Почему это проблема, вы можете уточнить?
Цюланг,
1
@ Qiulang Ааа, в объектно-ориентированном программировании вызов методов для объектов эквивалентен отправке сообщений в объекты с полезной нагрузкой или без нее (параметры вашей функции). Внутренние методы будут представлены как блок кода, связанный с классом / объектом, и использует неявную среду, доступную ему через объект, против которого он вызывается. Но если ваши методы являются объектами, они могут существовать независимо от того, связаны ли они с классом / объектом, что вызывает вопрос, если вы вызовете этот метод, против какой среды он будет работать?
pankajdoharey
Таким образом, должен существовать механизм для предоставления среды, под self подразумевается текущая среда в момент выполнения, но вы также можете предоставить другую среду.
pankajdoharey
2

Я думаю, что это связано с ПКП 227:

Имена в области видимости не доступны. Имена разрешаются в самой внутренней области действия функции. Если определение класса встречается в цепочке вложенных областей, процесс разрешения пропускает определения класса. Это правило предотвращает нечетные взаимодействия между атрибутами класса и доступом к локальной переменной. Если в определении класса происходит операция связывания имени, она создает атрибут в результирующем объекте класса. Чтобы получить доступ к этой переменной в методе или в функции, вложенной в метод, необходимо использовать ссылку на атрибут либо через себя, либо через имя класса.

daole
источник
1

Как объяснил в себе в Python, Демистифицированный

что-нибудь вроде obj.meth (args) становится Class.meth (obj, args). Вызывающий процесс является автоматическим, в то время как процесс получения не является (его явным). По этой причине первым параметром функции в классе должен быть сам объект.

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

Вызовы:

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

init () определяет три параметра, но мы только что передали два (6 и 8). Точно так же для distance () требуется один, но передано ноль аргументов.

Почему Python не жалуется на несоответствие номера аргумента ?

Обычно, когда мы вызываем метод с некоторыми аргументами, вызывается соответствующая функция класса, помещая объект метода перед первым аргументом. Таким образом, что-нибудь вроде obj.meth (args) становится Class.meth (obj, args). Вызывающий процесс является автоматическим, в то время как процесс получения не является (его явным).

По этой причине первым параметром функции в классе должен быть сам объект. Запись этого параметра в качестве self - это просто соглашение . Это не ключевое слово и не имеет особого значения в Python. Мы могли бы использовать другие имена (как это), но я настоятельно рекомендую вам не делать этого. Использование имен, отличных от self, осуждается большинством разработчиков и ухудшает читабельность кода («Количество читабельности»).
...
В первом примере self.x является атрибутом экземпляра, тогда как x является локальной переменной. Они не одинаковы и лежат в разных пространствах имен.

Я здесь, чтобы остаться

Многие предлагали сделать себя ключевым словом в Python, например, в C ++ и Java. Это исключило бы избыточное использование явного self из списка формальных параметров в методах. Хотя эта идея кажется многообещающей, этого не произойдет. По крайней мере, не в ближайшее время. Основная причина - обратная совместимость . Вот блог от самого создателя Python, объясняющий, почему явное «я» должно остаться.

понедельник
источник
-5

Есть и другой очень простой ответ: согласно дзену python «явное лучше, чем неявное».

Flávio Amieiro
источник