1

In class Wizard, I would like to set attribute wand to the value returned by coroutine magic.

class Wizard:
    async def acquire_wand(self):
        self.wand = await magic()

This code is considered "bad Python", however, because wand is not defined in __init__. I cannot define it in __init__, though, because await may only be used in asynchronous functions.

class Wizard:
    def __init__(self):
        self.wand = None

    async def acquire_wand(self):
        self.wand = await magic()

    async def perform_spell(self):
        if self.wand is None:
            await self.acquire_wand()
        self.wand.wave()

I could set wand to None in __init__ and use if self.wand is None: wherever it is accessed, but this seems messy and unwieldy.

How can I ensure that wand is defined throughout the class?

2Cubed
  • 3,401
  • 7
  • 23
  • 40

4 Answers4

5

Technically there is a trick with overriding __new__ method:

class InitCoroMixin:
    """ Mixin for create initialization coroutine
    """
    def __new__(cls, *args, **kwargs):
        """ This is magic!
        """
        instance = super().__new__(cls)

        @asyncio.coroutine
        def coro():
            instance.__init__(*args, **kwargs)
            yield from instance.__ainit__()
            return instance

        return coro()

    @asyncio.coroutine
    def __ainit__(self):
        raise NotImplementedError

see aiohttp_traversal code for full example.

But I highly discourage the method: having I/O in constructor is usually a bad idea, please think about it.

Andrew Svetlov
  • 16,730
  • 8
  • 66
  • 69
1

Wrap your functions that need self.wand inside a decorator, that will yield a clean and workable solution:

def with_wand(fn):
    def wrapper(self):
        if not self.wand:
            await self.acquire_wand()
        fn(self)
    return wrapper

@with_wand
async def perform_spell(self):
        self.wand.wave()

Haven't tested the code, let us know if it works!

j4hangir
  • 2,532
  • 1
  • 13
  • 14
  • This works, but it can get messy fairly quickly if I have multiple attributes (`wand`, `cloak`, `hat`, and `rabbit`). It can also mean that I'll need a `@decorator` for each attribute I'm using in a function, which seems unwieldy still. – 2Cubed Jun 05 '16 at 16:56
  • @2Cubed You could have a decorator that takes **optional** arguments and checks whether all those `properties` exist! – j4hangir Jun 05 '16 at 17:05
  • Jahangir: True. But it still seems like a messy way to go about it - I'd rather not have to put the same decorator on every function in a class. – 2Cubed Jun 05 '16 at 17:06
  • Then I'd suggest you to try this module to make the `async function wand` a synchronous one: [syncer](https://pypi.python.org/pypi/syncer/1.2.0) – j4hangir Jun 05 '16 at 17:16
  • Jahangir: I can't do that. What I'm doing *requires* my code to be asynchronous. – 2Cubed Jun 05 '16 at 17:19
  • Well then :) It's like eating the cake and having it at the same time. However, there's still one way for you to proceed and it's to have a `lazy` wand class that will check at run-time whether it needs to acquire *itself* or not. – j4hangir Jun 05 '16 at 17:23
0

It seems that using the following is the best way of going about this problem.

class Wizard:
    def __init__(self):
        self.wand = None

    async def learn(self):
        self.wand = await magic()

    async def perform_spell(self):
        if self.wand is None:
            raise Exception("You must first learn to use a wand!")
        self.wand.wave()
2Cubed
  • 3,401
  • 7
  • 23
  • 40
0

I think you got your advice, but I would like to question your premise. Who told you it's "considered bad Python"? I give my objects attributes all the time, whenever I need them to remember something. They have __dict__s for a reason. Python is not Java.

Veky
  • 2,646
  • 1
  • 21
  • 30
  • It's generally considered bad Python to not define an attribute in `__init__`. If you (or a user of your class) decides to access the attribute `self.wand`, it would raise an `AttributeError`, which is not "good". – 2Cubed Jul 20 '16 at 22:29
  • Do you have a reference for that "generally considered bad"?? I haven't seen it before, except in writing of people who think Python is just a Java variant. And of course you won't access self.wand if you don't have a wand, same as you don't access self.boat if you don't have a boat. What would be the point? `__init__` has nothing to do with it. If you want to know what attributes an object has, use dir (or programatically, use hasattr/getattr). – Veky Jul 21 '16 at 11:53
  • http://stackoverflow.com/questions/19284857/instance-attribute-attribute-name-defined-outside-init, http://pylint-messages.wikidot.com/messages:w0201 – 2Cubed Jul 21 '16 at 17:40
  • 2
    Ah, ok, I have misunderstood you a bit. So .wand is part of the _interface_ of class Wizard? That is, all wizards are guaranteed to have wands? (I understood that a wizard could sensibly just not have a wand.) Then your problem is the standard "I want awaitable X, but Python lets me only have awaitable Y" - your X is "object initialization", and Y is "iteration, context manager, or ordinary function/method". While it is possible to write awaitable object initialization (as @Andrew Svetlov shows), more ordinary solution would be to just use one of Ys: write your own factory. – Veky Jul 22 '16 at 05:36