1

I have several classes which I treat like a containers for my data (figure representation). They all have same @staticmethods e.g. get_edges(), get_vertices(). I want to import specified figure class by given parameter. What I do now is:

class Figure:
    def __init__(self, figure_type):
        exec('import figures.' + figure_type.lower())
        self.element = eval('figures.' + figure_type.lower() + '.' + figure_type)()

    def create(self, vertices):
        glBegin(GL_LINES)
        for edge in self.element.get_edges():
            for vertex in edge:
                glColor3fv((0.5,0,0))
                glVertex3fv(vertices[vertex])
        glEnd()

class Cube:

    @staticmethod
    def get_edges():
        return ((0, 1),(0, 3),(0, 4),(2, 1),(2, 3),(2, 7),(6, 3),(6, 4),(6, 7),(5, 1),(5, 4),(5, 7))

I wonder if there is a way to get something like:

class Figure(figure_type):
    def __init__(self, figure_type):

To be able to use just e.g. self.get_edges() instead of self.element.get_edges(). How can I get it? Is it even possible?

8
  • In OOP you would use a Factory pattern to create Objects: python-3-patterns-idioms-test.readthedocs.io/en/latest/… Commented Nov 9, 2019 at 13:33
  • Why have you chosen this scheme? Does Figure have methods common to all figure_type's and each figure_type has specific edges and vertices methods? Do the objects need to be Figure instances for some reason? Commented Nov 9, 2019 at 13:50
  • @wwii Yes, Figure contains methods applied to each figure_type. figure_type contains only methods which returns sets with edges, vertices or surfaces to draw them in OpenGL in Figure class. Why have I chosen it? Well, it seems to have good readability for me and maybe in future I can add some specific methods to each figure, but honestly speaking I'm beginner, so probably I could do better design. At the moment I try to follow given links in answers, but those concepts seems to me a little bit advanced. Commented Nov 9, 2019 at 13:58
  • 2
    @Ethr, get rid of exec and use importlib instead. Using Factory pattern might be easier than metaclasses as stated by hspandher (but metclasses are more sophisticated and fun to play with). Commented Nov 9, 2019 at 15:06
  • 1
    A better design would be to simply put all your figure types in a single module. Defining separate modules like figure.cube and figure.whatever do anything useful, and complicates precisely what you are trying to do. Commented Nov 9, 2019 at 16:41

2 Answers 2

1

Your design looks like you are making mixins. Here is a class factory toy example that can produces the mixins dynamically.


figures.py

data = ((0, 1),(0, 3),(0, 4),(2, 1),(2, 3),(2, 7),(6, 3))

class FT:
    @staticmethod
    def ge():
        return data

class FT1:
    @staticmethod
    def ge():
        return [(x*x,y*y) for x,y in data]

def compose(ftype):
    '''Returns a class composed of F and ftype.'''
    return type(f'F_{ftype.__name__}',(F,ftype),{})

some_module.py:

import importlib

class F:
    def __init__(self):
        self.x = 'foo'
    def a(self):
        s = '|'.join(f'{thing}' for thing in self.ge())
        return s
    def b(self):
        return 'baz'

def compose(ftype):
    cls = getattr(importlib.import_module('figures'),ftype)
    return type(f'F_{ftype}',(F,cls),{})


z = compose('FT')()
y = compose('FT1')()

Both objects have the same b methods.

>>> z.b(), y.b()
('baz', 'baz')

Both have the same a methods using data from specific ge methods

>>> print(z.a())
(0, 1)|(0, 3)|(0, 4)|(2, 1)|(2, 3)|(2, 7)|(6, 3)
>>> y.a()
'(0, 1)|(0, 9)|(0, 16)|(4, 1)|(4, 9)|(4, 49)|(36, 9)'

Each object has specific ge methods

>>> z.ge()
((0, 1), (0, 3), (0, 4), (2, 1), (2, 3), (2, 7), (6, 3))
>>> y.ge()
[(0, 1), (0, 9), (0, 16), (4, 1), (4, 9), (4, 49), (36, 9)]
>>> 

z and y are instances of different classes.

>>> z.__class__, y.__class__
(<class '__main__.F_FT'>, <class '__main__.F_FT1'>)
>>> 

Multiple instances of a composed class.

>>> One = compose(FT)
>>> q,r,s = One(),One(),One()
>>> q,r,s
(<__main__.F_FT object at 0x0000000003163860>,
 <__main__.F_FT object at 0x000000000D334198>,
 <__main__.F_FT object at 0x000000000D334828>)
>>>

If everything is in the same module then compose becomes

def compose(ftype):
    return type(f'F_{ftype.__name__}',(F,ftype),{})

z = compose(FT)
y = compose(FT1)

What is the difference between a mixin and inheritance?


Similar solution using an abstract base class. - G cannot be instantiated unless ge is overridden.

import importlib
import abc

class G(abc.ABC):
    def __init__(self):
        self.x = 'foo'
    def a(self):
        s = '|'.join(f'{thing}' for thing in self.ge())
        return s
    def b(self):
        return 'baz'

    @staticmethod
    @abc.abstractmethod
    def ge():
        pass

# either of these work
def new(ftype):
    cls = getattr(importlib.import_module('figures'),ftype)
    return type(cls.__name__,(cls,G),{})
#def new(ftype):
#    cls = getattr(importlib.import_module('figures'),ftype)
#    return type(cls.__name__,(G,),{'ge':staticmethod(cls.ge)})
#usage
# A = new('FT')

Similar except the specific static methods are just plain functions and using the ABC from above

def FT_ge():
    return ((0, 1),(0, 3),(0, 4),(2, 1),(2, 3),(2, 7),(6, 3))
def other_new(f):
    return type(f.__name__.split('_')[0],(G,),{'ge':staticmethod(f)})
# usage
# B = other_new(FT_ge)
Sign up to request clarification or add additional context in comments.

1 Comment

I have no idea of any possible adverse consequences - feel free to weigh in with criticisms or edits.
0

You can use importlib.import_module to import the module. However, it would be recommended to inherit those classes from a base class that uses a metaclass to keep track of it subclasses and maps them, say, in a dictionary. Then you can use this base class as a abstraction to interact with all child classes Track subclasses in python

1 Comment

Can you show a working toy example mimicking OP's examples?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.