0
\$\begingroup\$

I m writting some API which would use configuration-like arguments.

Here is an example configuration which will be used by my API

quail.run(
    quail.SolutionPacked(path='Allum1'),
    quail.Installer(
        name='Allum1',
        icon='icon.jpeg',
        binary='allum1',
        console=True
    ),
    quail.builder.Builder(
        quail.builder.CmdIcon('icon.ico'),
        quail.builder.CmdZip(solution_path, 'solution.zip')
    )
)

The only problem with that: we will always instantiate, even when we won't use the instance, (for example if we want to uninstall we won't use the install class)

One way around I have found is to use metaclass and override __call__ to get an "EggInstance" so I can instantiate the actual class later on.

Example implementation:

import six

class InstanceEgg:
    def __init__(self, cls, *args, **kwargs):
        self._cls = cls
        self._args = args
        self._kwargs = kwargs

    def __call__(self):
        return self._cls(_get_instance=True, *self._args, **self._kwargs)

class Egg(type):
    def __call__(cls, *args, **kwargs):
        get_instance = kwargs.pop("_get_instance", False)
        if get_instance:
            return super().__call__(*args, **kwargs)
        return InstanceEgg(cls, *args, **kwargs)

@six.add_metaclass(Egg)
class Test:
    def __init__(self, a, b):
        print("init %d %d" % (a, b))

Example use:

egg = Test(1, 2)
print("got egg")
f = egg()

Output:

got egg
init 1 2

Do you think it is acceptable to use metaclass for this purpose?

And what do you recommend?

\$\endgroup\$
3
  • 1
    \$\begingroup\$ What's the difference between instantiating an object and instantiating a factory? It's hard to advise you on whether this is appropriate, when we don't know what the constructors for these quail objects look like, and what the objects do. \$\endgroup\$ – 200_success May 7 '18 at 20:01
  • \$\begingroup\$ Installer class will be used to install a solution with install() method and others. Solution class will be used to retrieve a solution (download or unzip) with open() retrieve() and close() Which means we are already initializing stuff in the constructor: like where will be the install folder. if you want more informations, here is the project github.com/mouuff/Quail \$\endgroup\$ – mou May 7 '18 at 20:18
  • \$\begingroup\$ Also I forgot to mention, this classes could possibily be inherited by the "user developer" to change the default behavior \$\endgroup\$ – mou May 7 '18 at 21:16
1
\$\begingroup\$

From the looks of it you have objects that you only want to partially create, when you create them the first time.

To do this you can use functools.partial:

quail.run(
    partial(quail.SolutionPacked, path='Allum1'),
    partial(
        quail.Installer,
        name='Allum1',
        icon='icon.jpeg',
        binary='allum1',
        console=True
    ),
    partial(
        quail.builder.Builder,
        partial(quail.builder.CmdIcon, 'icon.ico'),
        partial(quail.builder.CmdZip, solution_path, 'solution.zip')
    )
)

Yeah, this isn't really that attractive. However it is what you want.

And so you can use a metaclass to perform this action automagically. I'd remove the _get_instance stuff from your Egg, remove InstanceEgg and use a partial call.

from functools import partial


class PartialInit(type):
    def __call__(cls, *args, **kwargs):
        return partial(super().__call__, *args, **kwargs)


@six.add_metaclass(PartialInit)
class Test:
    def __init__(self, a, b):
        print('init {} {}'.format(a, b))


t = Test('a')
print('Made t: {}'.format(t))
t('b')
\$\endgroup\$
4
  • \$\begingroup\$ I didn't know about functools.partial but this solution forces to write additionnal stuff while writting configuration and stilll keeping metaclass's magic stuff, I don't see the advantage \$\endgroup\$ – mou May 8 '18 at 9:46
  • \$\begingroup\$ @mou "forces to write additionnal stuff while writting configuration". How and where does it do this? \$\endgroup\$ – Peilonrayz May 8 '18 at 9:54
  • \$\begingroup\$ Writting partial() every time passing a class \$\endgroup\$ – mou May 8 '18 at 9:55
  • 1
    \$\begingroup\$ @mou You don't have to do that... As I said that's what you'd do without a metaclass, and so "you can use a metaclass to perform this action automagically" \$\endgroup\$ – Peilonrayz May 8 '18 at 9:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.