Search code examples
pythonclassoopmethodsclass-method

How to deal with mappingproxy when working with class methods?


I've recently been experimenting with the use of class methods to get a better understanding of object-oriented programming. As part of a sample program I've got a short class called circle, which can be used to create different circle instances.

class circle():
    def __init__(self):
        self.radius = 10
        self.color = 'blue'
    def change_color(self, color):
        self.color = color

    @classmethod
    def red_circle(cls):
        circle.radius = 10
        circle.color = 'red'
        return cls

I've added in the class method red_circle so that I can have a different default setting for circle instances. The problem I have is that when I use the red_circle method, the instance created is placed inside of mappingproxy()? For example:

circle_one = circle()
circle_one.__dict__

Gives the output:

{'radius': 10, 'color': 'blue'}

But using

circle_two = circle.red_circle()
circle_two.__dict__

Gives:

mappingproxy({'__dict__': <attribute '__dict__' of 'circle' objects>,
              '__doc__': None,
              '__init__': <function __main__.circle.__init__>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'circle' objects>,
              'change_color': <function __main__.circle.change_color>,
              'color': 'orange',
              'radius': 10,
              'red_circle': <classmethod at 0x7f0e94ab1a10>})

Is there a way that I can manipulate circle_two so that it appears the same as the circle_one instance? It's not causing any problems right now, but it would be good to understand what's going on with the mappingproxy().


Solution

  • The trouble with your existing code is that your red_circle method, as it stands, is attempting to alter attributes of the circle class rather than attributes of any one instance of the circle class.

    You can fix this by rewriting it like so:

    class Circle:
        def __init__(self):
            self.radius = 10
            self.color = 'blue'
    
        def change_color(self, color):
            self.color = color
    
        @classmethod
        def red_circle(cls):
            new_circle = cls()
            new_circle.change_color('red')
            return new_circle
    

    A few points to note here:

    • I've renamed your circle class to Circle. It's a strong convention in python code to capitalise names of classes, and you're likely to confuse other programmers if you don't do that.
    • Unlike with functions, you don't need brackets when declaring a class, unless you're inheriting from another class.
    • Just as an instance method always takes the instance of the class as its first parameter (and there's a strong convention to always call that parameter self), a class method will always take the class itself as its first parameter (and there's a strong convention to always call that parameter cls).

    mappingproxy objects

    mappingproxy objects are (to simplify a little here) immutable dictionaries. While ordinary dictionaries are used for instance dictionaries, mappingproxy objects are used for class dictionaries. There's a good explanation of why a different kind of dictionary is used for the class dict here: Why is a class __dict__ a mappingproxy?.

    Class dicts vs instance dicts

    In your circle class, the radius and color attributes are instance attributes. Each instance of circle has its own version of them, and keeps those versions in its own personalised instance dictionary. However, the methods in your circle class — __init__, change_color and red_circle — are class attributes. These methods are kept in the class dictionary — the mappingproxy object you came across. All instances of circle have access to the same class dictionary, and this is how they access these methods.

    You can find a good introductory tutorial on the different kinds of python methods here.