1

Take a look at this code snippet:

class Face():
    pass

class Cube():
    def __init__(self):
        self.faces = {
                'front': Face(1),
                ...
                }

    @property
    def front(self):
        return self.faces['front']

    @front.setter
    def front(self, f):
        pass

I've created getters and setters for all the faces. Is there any way to make this code more compact, maybe by dynamically creating the getters and setters?

timgeb
  • 64,821
  • 18
  • 95
  • 124
saga
  • 1,375
  • 1
  • 9
  • 27
  • 2
    why do you put 'front' inside a dictionary instead of direct attribute of your Cube class ? No need for properties then. – Corentin Limier Nov 30 '18 at 15:25
  • @CorentinLimier I'm assuming OP is not showing the full setter and getter logic they want for keys just in self.faces. – timgeb Nov 30 '18 at 15:48

4 Answers4

2

The following code assumes that you

  • have a reason to have the self.faces dict instead of setting attributes like front directly on the instance

  • and/or want to implement some meaningful getter and setter logic for the keys in self.faces.

Otherwise, this exercise is pretty pointless because as Corentin Limier noted you can simply set self.front = Face(1), and so on.


You can use descriptors, a class variable holding the face names and a class decorator. Think of descriptors as reusable properties.

In the following sample code I added a num instance variable to Face and the face 'side' just for demonstration purposes.

class FaceDescriptor:
    def __get__(self, instance, owner):
        # your custom getter logic

        # dummy implementation
        if instance is not None:                
            return instance.faces[self.face]

    def __set__(self, instance, value):
        # your custom setter logic

        # dummy implementation
        instance.faces[self.face] = value

def set_faces(cls):
     for face in cls._faces:
         desc = FaceDescriptor()
         desc.face = face
         setattr(cls, face, desc)
     return cls

class Face():
    def __init__(self, num):
        self.num = num   

@set_faces
class Cube():
    _faces = ['front', 'side']

    def __init__(self):
        self.faces = {face:Face(i) for i, face in enumerate(self._faces, 1)}

In action:

>>> c = Cube()                                                                                                                                                                                         
>>> c.front.num                                                                                                                                                                                         
1
>>> c.side.num                                                                                                                                                                                          
2
>>> c.front = 'stuff'                                                                                                                                                                                   
>>> c.front                                                                                                                                                                                             
'stuff'
>>> c.faces                                                                                                                                                                                             
{'front': 'stuff', 'side': <__main__.Face at 0x7fd0978f37f0>}
timgeb
  • 64,821
  • 18
  • 95
  • 124
  • Actually it would be loads easier to use this class if I could iterate over the faces. Now I see that implementing `__iter__` would've solved that problem. – saga Dec 01 '18 at 13:20
  • @saga that's an entirely different question than how to code dynamic properties though. – timgeb Dec 01 '18 at 13:43
0

Assuming that's all your class does, you could do something like

class Cube:
   ...

def __getattr__(self, name):
    return self.faces[name]

def __setattr__(self, name, value):
    self.faces[name] = value
blue_note
  • 22,656
  • 6
  • 48
  • 67
0

if you really want to do that you could use __getattr__ and __setattr__:

class Cube:

   ...   

   def __getattr__(self, item):
        return self.faces[item]

   def __setattr__(self, item, value):
        self.faces[item] = value

but as you set front in the __init__ methoud you could just as well make it a regular member...

hiro protagonist
  • 36,147
  • 12
  • 68
  • 86
0

Your code is redundant, since instance attributes are already stored in a dictionary which is the __dict__ property. I recognize that you are focused on writing your code in fewer lines. It is a good challenge to keep yourself growing, but in the long term you should be focused on the clarity of your code instead.

Here is a simpler way to write your code without using properties:

class Face():
    pass

class Cube():
    def __init__(self):
        self.front = Face()
        self.rear = Face()

It is a tenet of encapsulation that you should hide your "attributes" behind "properties". Even though this isn't strongly enforced in python, it's not a bad idea to do that. Here's the proper way to do that:

class Face():
    pass

class Cube():
    def __init__(self):
        self._front = Face()

    @property
    def front(self):
        return self._front

    @front.setter
    def front(self, value):
        self._front = value

To answer your question at the end, yes you can dynamically create properties. https://stackoverflow.com/a/1355444/3368572

But keep in mind that writing dynamic code should be reserved for special cases since it will make it more difficult for your IDE to follow the flow of your program. If you use the conventions as they are intended then your code becomes self-explanatory to people and to your IDE.

Timothy Jannace
  • 977
  • 6
  • 16
  • I'm assuming OP is not showing the full setter and getter logic they want for keys just in `self.faces`. – timgeb Nov 30 '18 at 15:48