79

I am studying python, and although I think I get the whole concept and notion of Python, today I stumbled upon a piece of code that I did not fully understand:

Say I have a class that is supposed to define Circles but lacks a body:

class Circle(): pass 

Since I have not defined any attributes, how can I do this:

my_circle = Circle() my_circle.radius = 12 

The weird part is that Python accepts the above statement. I don't understand why Python doesn't raise an undefined name error. I do understand that via dynamic typing I just bind variables to objects whenever I want, but shouldn't an attribute radius exist in the Circle class to allow me to do this?

EDIT: Lots of wonderful information in your answers! Thank you everyone for all those fantastic answers! It's a pity I only get to mark one as an answer.

9
  • 15
    When you initialize self.radius at the __init__ aren't you doing exactly the same thing? Commented Sep 24, 2012 at 16:22
  • @JBernardo yes you do, but in this case, you are explicitly defining a radius attribute for class Circle(). In my case I didn't create any attribute in the class body. Commented Sep 24, 2012 at 16:27
  • @NlightNFotis No, you are doing the same thing because the self is just a variable like any other. Commented Sep 24, 2012 at 16:29
  • 5
    @NlightNFotis Also, Python is not Java and a language that doesn't affect the way you think about programming, is not worth knowing - [Alan Perlis](en.wikiquote.org/wiki/Alan_Perlis) Commented Sep 24, 2012 at 16:30
  • @NlightNFotis No you aren't. You define a function, which assigns to an attribute of its first argument. It happens that this function is referenced by the __init__ attribute of a class which happens to be invoked after object construction. Commented Sep 24, 2012 at 16:30

9 Answers 9

60

A leading principle is that there is no such thing as a declaration. That is, you never declare "this class has a method foo" or "instances of this class have an attribute bar", let alone making a statement about the types of objects to be stored there. You simply define a method, attribute, class, etc. and it's added. As JBernardo points out, any __init__ method does the very same thing. It wouldn't make a lot of sense to arbitrarily restrict creation of new attributes to methods with the name __init__. And it's sometimes useful to store a function as __init__ which don't actually have that name (e.g. decorators), and such a restriction would break that.

Now, this isn't universally true. Builtin types omit this capability as an optimization. Via __slots__, you can also prevent this on user-defined classes. But this is merely a space optimization (no need for a dictionary for every object), not a correctness thing.

If you want a safety net, well, too bad. Python does not offer one, and you cannot reasonably add one, and most importantly, it would be shunned by Python programmers who embrace the language (read: almost all of those you want to work with). Testing and discipline, still go a long way to ensuring correctness. Don't use the liberty to make up attributes outside of __init__ if it can be avoided, and do automated testing. I very rarely have an AttributeError or a logical error due to trickery like this, and of those that happen, almost all are caught by tests.

Sign up to request clarification or add additional context in comments.

1 Comment

its nice in the way but flexibility, also raises readbility issues, when maintaining other peopls code, I constently forgot what attriute the object has at a certain point.
51

Just to clarify some misunderstandings in the discussions here. This code:

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

And this code:

class Foo(object): pass foo = Foo() foo.bar = 5 

is exactly equivalent. There really is no difference. It does exactly the same thing. This difference is that in the first case it's encapsulated and it's clear that the bar attribute is a normal part of Foo-type objects. In the second case it is not clear that this is so.

In the first case you can not create a Foo object that doesn't have the bar attribute (well, you probably can, but not easily), in the second case the Foo objects will not have a bar attribute unless you set it.

So although the code is programatically equivalent, it's used in different cases.

2 Comments

What would a use case of the second be? It sort of breaks OOP, which of course is fine... But if you aren't programming OOP why would you care about having a class anyway? These are not rhetorical questions, I'm genuinely curious!
It doesn't stop being OOP just because you aren't dogmatic about it. The second case is still OOP.
22

Python lets you store attributes of any name on virtually any instance (or class, for that matter). It's possible to block this either by writing the class in C, like the built-in types, or by using __slots__ which allows only certain names.

The reason it works is that most instances store their attributes in a dictionary. Yes, a regular Python dictionary like you'd define with {}. The dictionary is stored in an instance attribute called __dict__. In fact, some people say "classes are just syntactic sugar for dictionaries." That is, you can do everything you can do with a class with a dictionary; classes just make it easier.

You're used to static languages where you must define all attributes at compile time. In Python, class definitions are executed, not compiled; classes are objects just like any other; and adding attributes is as easy as adding an item to a dictionary. This is why Python is considered a dynamic language.

2 Comments

Hi, so are you saying that in Python, the purpose of a class is not to bundle data and behaviors (OOP), but rather just to make identify the purpose of a given dictionary by dressing it up with some human-readable syntax?
You can use them to bundle data and behaviors and the syntax encourages that, and it works basically like you'd expect for OOP (though not a strong version of OOP—encapsulation is pretty weak in Python since there are no private attributes). But, underneath the class syntax, classes are basically dictionaries with some extra (very useful) behavior on top. If you were writing a compiler you'd likely use a dictionary (hash) to keep track of the members of a class during definition; Python just does that at runtime.
21

No, python is flexible like that, it does not enforce what attributes you can store on user-defined classes.

There is a trick however, using the __slots__ attribute on a class definition will prevent you from creating additional attributes not defined in the __slots__ sequence:

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

12 Comments

@NlightNFotis You don't. Python isn't Java and you shouldn't try to write Java in Python. If you want to do that, write Java.
Share the joy of programming without a safety net :)
@NlightNFotis: The type safety of Java is a an illusion. It feels like it's safer and more secure and that you can trust the code more, but you really can't. The cake is a lie.
@NlightNFotis: Python has a philosophy described as "we're all consenting adults here". It means that if you want to break with convention and change foo._variable directly - perhaps for debugging, or to avoid some bug in foo - you can. But you don't get to complain to the author of foo if it breaks something.
__slots__ is there as a memory-savings enhancement; it should not be used as a way to lockdown a class.
|
7

It creates a radius data member of my_circle.

If you had asked it for my_circle.radius it would have thrown an exception:

>>> print my_circle.radius # AttributeError 

Interestingly, this does not change the class; just that one instance. So:

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

1 Comment

although you could do Circle.xyz = 5 and change the class rather than the instance...
4

There are two types of attributes in Python - Class Data Attributes and Instance Data Attributes.

Python gives you flexibility of creating Data Attributes on the fly.

Since an instance data attribute is related to an instance, you can also do that in __init__ method or you can do it after you have created your instance..

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 

So, you see that we have created two instance attributes, one inside __init__ and one outside, after instance is created..

But a difference is that, the instance attribute created inside __init__ will be set for all the instances, while if created outside, you can have different instance attributes for different isntances..

This is unlike Java, where each Instance of a Class have same set of Instance Variables..

  • NOTE: - While you can access a class attribute through an instance, you cannot delete it.. Also, if you try to modify a class attribute through an instance, you actually create an instance attribute which shadows the class attribute..

2 Comments

No, you don't declare class attributes either. You define them. Those definitions are executable statements and perfectly ordinary, just instead of manipulating some function's scope, they manipulate the class's attributes. And the class attributes aren't set in stone either: It's trivial to add, replace and remove class attributes.
I still don't see why you distinguish between class and instance attributes in the beginning. Both are explicitly defined, in both cases at runtime, and in both cases these definitions and re-definitions can happen at any time.
4

To add to Conchylicultor's answer, Python 3.10 added a new parameter to dataclass.

The slots parameter will create the __slots__ attribute in the class, preventing creation of new attributes outside of __init__, but allowing assignments to existing attributes.

If slots=True, assigning to an attribute that was not defined will throw an AttributeError.

Here is an example with slots and with frozen:

from dataclasses import dataclass @dataclass class Data: x:float=0 y:float=0 @dataclass(frozen=True) class DataFrozen: x:float=0 y:float=0 @dataclass(slots=True) class DataSlots: x:float=0 y:float=0 p = Data(1,2) p.x = 5 # ok p.z = 8 # ok p = DataFrozen(1,2) p.x = 5 # FrozenInstanceError p.z = 8 # FrozenInstanceError p = DataSlots(1,2) p.x = 5 # ok p.z = 8 # AttributeError 

Comments

2

How to prevent new attributes creation ?

Using class

To control the creation of new attributes, you can overwrite the __setattr__ method. It will be called every time my_obj.x = 123 is called.

See the documentation:

class A: def __init__(self): # Call object.__setattr__ to bypass the attribute checking super().__setattr__('x', 123) def __setattr__(self, name, value): # Cannot create new attributes if not hasattr(self, name): raise AttributeError('Cannot set new attributes') # Can update existing attributes super().__setattr__(name, value) a = A() a.x = 123 # Allowed a.y = 456 # raise AttributeError 

Note that users can still bypass the checking if they call directly object.__setattr__(a, 'attr_name', attr_value).

Using dataclass

With dataclasses, you can forbid the creation of new attributes with frozen=True. It will also prevent existing attributes to be updated.

@dataclasses.dataclass(frozen=True) class A: x: int a = A(x=123) a.y = 123 # Raise FrozenInstanceError a.x = 123 # Raise FrozenInstanceError 

Note: dataclasses.FrozenInstanceError is a subclass of AttributeError

Comments

1

As delnan said, you can obtain this behavior with the __slots__ attribute. But the fact that it is a way to save memory space and access type does not discard the fact that it is (also) a/the mean to disable dynamic attributes.

Disabling dynamic attributes is a reasonable thing to do, if only to prevent subtle bugs due to spelling mistakes. "Testing and discipline" is fine but relying on automated validation is certainly not wrong either – and not necessarily unpythonic either.

Also, since the attrs library reached version 16 in 2016 (obviously way after the original question and answers), creating a closed class with slots has never been easier.

>>> import attr ... ... @attr.s(slots=True) ... class Circle: ... radius = attr.ib() ... ... f = Circle(radius=2) ... f.color = 'red' AttributeError: 'Circle' object has no attribute 'color' 

1 Comment

Another mechanism to disable dynamic attributes that doesn't use slots and therefore doesn't break inheritance: from pystrict import strict \n @strict \n class Circle: ...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.