Подтвердить что ты не робот

Почему добавление атрибутов к уже созданному объекту разрешено?

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

Скажем, у меня есть класс, который должен определять Круги, но не имеет тела:

class Circle():
    pass

Так как я не определил никаких атрибутов, как это сделать:

my_circle = Circle()
my_circle.radius = 12

Странная часть заключается в том, что Python принимает вышеупомянутое утверждение. Я не понимаю, почему Python не создает undefined name error. Я понимаю, что с помощью динамической типизации я просто привязываю переменные к объектам всякий раз, когда хочу, но не должен ли атрибут radius существовать в классе Circle, чтобы позволить мне это сделать?

EDIT: в ваших ответах много замечательной информации! Спасибо всем за все эти фантастические ответы! Жаль, что я только могу отметить один как ответ.

4b9b3361

Ответ 1

Главный принцип заключается в том, что нет такой вещи, как декларация. То есть вы никогда не заявляете, что "этот класс имеет метод foo" или "экземпляры этого класса имеют панель атрибутов", не говоря уже о том, чтобы сделать выражение о типах объектов, которые будут там храниться. Вы просто определяете метод, атрибут, класс и т.д., И он добавляется. Как указывает Дж. Бернардо, любой метод __init__ делает то же самое. Было бы бессмысленно произвольно ограничивать создание новых атрибутов методами с именем __init__. И иногда полезно хранить функцию как __init__, которая на самом деле не имеет этого имени (например, декораторы), и такое ограничение нарушит это.

Теперь это не универсально. Встроенные типы опускают эту возможность в качестве оптимизации. Через __slots__ вы также можете предотвратить это в пользовательских классах. Но это просто оптимизация пространства (нет необходимости в словаре для каждого объекта), а не в правильности.

Если вам нужна система безопасности, ну, слишком плохо. Python не предлагает один, и вы не можете разумно добавить его, и, самое главное, его избегают программисты Python, которые используют этот язык (читайте: почти все из тех, с которыми вы хотите работать). Тестирование и дисциплина по-прежнему имеют большое значение для обеспечения правильности. Не используйте свободу для создания атрибутов вне __init__, если этого можно избежать, и выполните автоматическое тестирование. У меня очень редко есть AttributeError или логическая ошибка из-за обмана, подобного этому, и из тех, которые происходят, почти все попадают под тесты.

Ответ 2

Просто разъяснить некоторые недоразумения в обсуждениях здесь. Этот код:

class Foo(object):
    def __init__(self, bar):
        self.bar = bar

foo = Foo(5)

И этот код:

class Foo(object):
    pass

foo = Foo()
foo.bar = 5

в точности эквивалентен. Там нет никакой разницы. Он делает то же самое. Это различие состоит в том, что в первом случае он инкапсулирован и ясно, что атрибут bar является нормальной частью объектов типа Foo. Во втором случае неясно, что это так.

В первом случае вы не можете создать объект Foo, у которого нет атрибута bar (ну, возможно, вы, возможно, можете, но не легко), во втором случае объекты Foo не будут иметь атрибут bar, если вы не установите он.

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

Ответ 3

Python позволяет хранить атрибуты любого имени практически на любом экземпляре (или на каком-либо классе). Можно блокировать это либо путем записи класса в C, как и встроенных типов, либо с помощью __slots__, который допускает только определенные имена.

Причина, по которой он работает, заключается в том, что большинство экземпляров хранят свои атрибуты в словаре. Да, обычный словарь Python, который вы бы определили с помощью {}. Словарь хранится в атрибуте экземпляра, который называется __dict__. На самом деле, некоторые люди говорят, что "классы - это просто синтаксический сахар для словарей". То есть вы можете делать все, что вы можете сделать с классом со словарем; классы просто упрощают.

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

Ответ 4

Нет, python такой гибкий, он не обеспечивает, какие атрибуты вы можете хранить в пользовательских классах.

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

>>> class Foo(object):
...     __slots__ = ()
... 
>>> f = Foo()
>>> f.bar = 'spam'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> class Foo(object):
...     __slots__ = ('bar',)
... 
>>> f = Foo()
>>> f.bar
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: bar
>>> f.bar = 'spam'

Ответ 5

Он создает элемент данных radius my_circle.

Если бы вы запросили его my_circle.radius, это вызвало бы исключение:

>>> print my_circle.radius # AttributeError

Интересно, что это не меняет класс; просто один экземпляр. Итак:

>>> my_circle = Circle()
>>> my_circle.radius = 5
>>> my_other_circle = Circle()
>>> print my_other_circle.radius # AttributeError

Ответ 6

В Python есть два типа атрибутов - Class Data Attributes и Instance Data Attributes.

Python дает вам гибкость при создании Data Attributes на лету.

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

class Demo(object):
    classAttr = 30
    def __init__(self):
         self.inInit = 10

demo = Demo()
demo.outInit = 20
Demo.new_class_attr = 45; # You can also create class attribute here.

print demo.classAttr  # Can access it 

del demo.classAttr         # Cannot do this.. Should delete only through class

demo.classAttr = 67  # creates an instance attribute for this instance.
del demo.classAttr   # Now OK.
print Demo.classAttr  

Итак, вы видите, что мы создали два атрибута экземпляра: один внутри __init__ и один внешний, после создания экземпляра.

Но разница в том, что атрибут экземпляра, созданный внутри __init__, будет установлен для всех экземпляров, а если он создан снаружи, вы можете иметь разные атрибуты экземпляра для разных isntances..

Это не похоже на Java, где каждый экземпляр класса имеет одинаковый набор переменных экземпляра..

  • ПРИМЕЧАНИЕ. - Хотя вы можете получить доступ к атрибуту класса через экземпляр, его нельзя удалить. Кроме того, если вы попытаетесь изменить атрибут класса через экземпляр, вы фактически создадите атрибут экземпляра, который затеняет атрибут класса.