5

When I make a class definition I always go

Class A(object):
    def __init__(self, arg):
        self.arg = arg
    def print_arg(self):
        print(self.arg)

a = A('hello')

print a.arg

'hello'

But what I found in line 133 and 134 of https://github.com/Pylons/webob/blob/master/src/webob/request.py made me think what is the difference between what I did in Class A with:

Class B(object):
    def __init__(self, arg):
        self.__dict__['arg'] = arg
    def print_arg(self):
            print(self.arg)

b = B('goodbye')

print b.arg

'goodbye'

Rodrigo E. Principe
  • 1,281
  • 16
  • 26
  • https://stackoverflow.com/questions/19907442/python-explain-dict-attribute – Albin Paul Dec 29 '17 at 16:00
  • There are several differences, but they don't apply (matter) in the example you showed. – MSeifert Dec 29 '17 at 16:00
  • Not sure why you got downvoted. This is an important question and it looks like you've made some effort to analyze the problem on your own. – Mad Physicist Dec 29 '17 at 16:14
  • Thank you @MadPhysicist, I always research and try to understand before I ask, I had already read https://stackoverflow.com/questions/19907442/python-explain-dict-attribute, but could't get the differences and implications.. – Rodrigo E. Principe Dec 29 '17 at 16:20
  • 1
    It's not an easy topic when you first start. I've posted an answer showing how the concept is used. It doesn't answer your question directly, but might help you visualize the differences. – Mad Physicist Dec 29 '17 at 17:08

3 Answers3

6

There are several major implications:

  1. Using self.__dict__ to add an attribute circumvents __setattr__, which might be overloaded with a certain behaviour that you might want to avoid in some places.

    In [15]: class Test(object):
        ...:     def __init__(self, a, b):
        ...:         self.a = a
        ...:         self.__dict__['b'] = b
        ...:     def __setattr__(self, name, value):
        ...:         print('Setting attribute "{}" to {}'.format(name, value))
        ...:         super(Test, self).__setattr__(name, value)
        ...:               
    
    In [16]: t = Test(1, 2)
    Setting attribute "a" to 1
    

    You can see that nothing was printed for attribute b.

  2. It is less flexible in some cases

    In [9]: class WithSlots(object):
       ...:     __slots__ = ('a',)
       ...:     def __init__(self, a):
       ...:         self.__dict__['a'] = a
       ...:         
    
    In [10]: instance = WithSlots(1)
    ---------------------------------------------------------------------------
    AttributeError                            Traceback (most recent call last)
    <ipython-input-10-c717fcc835a7> in <module>()
    ----> 1 instance = WithSlots(1)
    
    <ipython-input-9-2d23b670e0fc> in __init__(self, a)
          2     __slots__ = ('a',)
          3     def __init__(self, a):
    ----> 4         self.__dict__['a'] = a
          5 
    
    AttributeError: 'WithSlots' object has no attribute '__dict__'
    
    In [11]: class WithSlots(object):
        ...:     __slots__ = ('a',)
        ...:     def __init__(self, a):
        ...:         self.a = a
        ...:         
        ...:         
    
    In [12]: instance = WithSlots(1) # -> works fine
    
  3. You can't do that outside a class definition.

Eli Korvigo
  • 10,265
  • 6
  • 47
  • 73
3

The overall purpose is to circumvent the default manner in which Python sets a variable. A particular use-case for this technique is to hide property values. Compare the following two classes:

class Exposed:
    def __init__(self, x):
        self._x = x
    @property
    def x(self):
        rerurn self._x

class Hidden:
    def __init__(self, x):
        self.__dict__['x'] = x
    @property
    def x(self):
        return self.__dict__['x']

Both classes define a read-only property x. However, the first one ends up with an extra _x attribute that is directly modifiable by the user, while the second does not. While nothing is truly private in Python, the second class creates a much better approximation of a true read-only value, and it doesn't proliferate unnecessarily visible attributes.

Eli Korvigo
  • 10,265
  • 6
  • 47
  • 73
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Well, you can always do `hidden_instance.__dict__['x'] = new_value` to modify it. The only real way to have a private attribute is to create an extension type (either via Cython or Python C API) – Eli Korvigo Dec 29 '17 at 16:28
  • @Eli, even then, you can write a C extension that can modify the value. It's a question of how much work you're willing to do for the given result. I think removing "private" attributes is a good balance because it prevents accidental modification. – Mad Physicist Dec 29 '17 at 17:04
  • Fair enough, though you would have to use very low-level compiler-dependent hacks and know the memory layout of the underlying struct to access a private member, which is almost impossible without a ton of effort. Nevertheless, your example is a very good contribution to the thread. – Eli Korvigo Dec 29 '17 at 18:41
1

Without having looked at the code in request, the direct access to the object's __dict__ is probably purposefully placed there to circumvent the normal attribute lookup hierarchy of that object.

user2722968
  • 13,636
  • 2
  • 46
  • 67