75

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.

martineau
  • 119,623
  • 25
  • 170
  • 301
NlightNFotis
  • 9,559
  • 5
  • 43
  • 66
  • 15
    When you initialize `self.radius` at the `__init__` aren't you doing exactly the same thing? – JBernardo Sep 24 '12 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. – NlightNFotis Sep 24 '12 at 16:27
  • @NlightNFotis No, you are doing the same thing because the `self` is just a variable like any other. – JBernardo Sep 24 '12 at 16:29
  • 4
    @NlightNFotis Also, [Python is not Java](http://dirtsimple.org/2004/12/python-is-not-java.html) 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) – JBernardo Sep 24 '12 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. –  Sep 24 '12 at 16:30
  • @delnan Could you elaborate a little bit more on that because it confused me? I thought `__init__()` is called during this statement: `my_circle = Circle()` not after it. `radius` is assigned after the `__init()__` method has been called and the object is already ***constructed***. – NlightNFotis Sep 24 '12 at 16:33
  • @NlightNFotis Yes, `__init__` is called before `radius` is added in your example. But it is called after the object is created, and you can replace it at any time. The syntax `def __init__(self, ...)` isn't a magic incantation. It simply defines a function which become an attribute of the class object and then is called after the object is created but before it is returned and stored in `my_circle`. –  Sep 24 '12 at 16:35
  • @NlightNFotis.. There are two special methods - `__init__` and `__new__`. It is the `__new__` method that is called to create a class's instance, whereas, `__init__` method is used to initialize the instance after it has been created.. So, there is a difference between the two methods.. Also, `__init__` method takes an argument `self` whereas `__new__` does not. (You can think why?? Because an instance has not been created yet..) – Rohit Jain Sep 24 '12 at 16:40
  • It's allowed because it's handy. Imagine having an object that represents a UI. You might want to create widgets on the fly, and keep their reference by storing them in new attributes of your "program window" class. – Guimoute Nov 08 '19 at 12:38

9 Answers9

59

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.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • 3
    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. – Junchen Liu Jun 30 '18 at 12:46
50

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.

Lennart Regebro
  • 167,292
  • 41
  • 224
  • 251
  • 1
    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! – rocksNwaves May 07 '21 at 14:32
  • It doesn't stop being OOP just because you aren't dogmatic about it. The second case is still OOP. – Lennart Regebro May 10 '21 at 14:54
20

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.

kindall
  • 178,883
  • 35
  • 278
  • 309
  • 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? – rocksNwaves May 07 '21 at 14:35
  • 1
    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. – kindall May 07 '21 at 21:58
19

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'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • But what happens when I want to enforce some error safety like java code does? I mean that in **java** if I don't have a public variable named radius, or a private one with accessor methods I can not reference it, or assign values to it. How could I do so in java? I know about the `_variable` convention, but it is only this: a convention. – NlightNFotis Sep 24 '12 at 16:25
  • 7
    @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. – Gareth Latty Sep 24 '12 at 16:25
  • 2
    @NlightNFotis: You generally *don't* in python. You use unit tests and embrace the flexibility. – Martijn Pieters Sep 24 '12 at 16:26
  • 7
    Share the joy of programming without a safety net :) – ypercubeᵀᴹ Sep 24 '12 at 16:26
  • 3
    @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. – Lennart Regebro Sep 24 '12 at 16:56
  • @LennartRegebro Could you explain it a little bit more? – NlightNFotis Sep 24 '12 at 17:02
  • 2
    @NlightNFotis He may refer to the facts that (1) access modifies and the like can be circumvented via reflection and (2) static typing can be circumvented using casts and (3) a type system as limited as Java's only encodes very basic guarantees, which cover only a very tiny subset of application correctness and security. (There are type systems which guarantee progressively more but become progressively harder to use and understand.) –  Sep 24 '12 at 17:09
  • 3
    @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. – Thomas K Sep 24 '12 at 17:15
  • 3
    `__slots__` is there as a memory-savings enhancement; it should not be used as a way to lockdown a class. – Ethan Furman Sep 24 '12 at 17:26
  • So if you want to lock it down, is it best to define your own \_\_setattr\_\_ function in your class? – RufusVS Sep 08 '14 at 20:24
  • @RufusVS: Even `__setattr__` can be bypassed, but yes. – Martijn Pieters Sep 08 '14 at 20:30
  • 1
    Just saved me from a slip of madness - was trying to figure out why `self.attr = value` was failing - the class had slots defined... – Mr_and_Mrs_D Mar 09 '16 at 23:06
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
inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
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..
Rohit Jain
  • 209,639
  • 45
  • 409
  • 525
  • 4
    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. –  Sep 24 '12 at 16:37
  • 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. –  Sep 24 '12 at 16:46
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

Conchylicultor
  • 4,631
  • 2
  • 37
  • 40
2

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
robertspierre
  • 3,218
  • 2
  • 31
  • 46
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'
P-Gn
  • 23,115
  • 9
  • 87
  • 104
  • 1
    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: ...` – Erik Aronesty Nov 22 '19 at 14:23