11

I am trying to do the equivalent of:

class A:
    def __init__(self):
        self.b = self.get_b()

    def get_b(self):
        return 1

using @dataclass. I want to use a @dataclass here because there are other (omitted) fields initialized from provided constructor arguments. b is one of the fields that is initialized from an instance method's result. Executing this:

@dataclass
class A:
    b = self.get_b()

    def get_b(self):
        return 1

shows that self is not defined in b's scope. Is it possible to get a reference to self here?

Mario Ishac
  • 5,060
  • 3
  • 21
  • 52

1 Answers1

10

Use the __post_init__ method.

from dataclasses import dataclass, field


@dataclass
class A:
    b: int = field(init=False)

    def __post_init__(self):
        self.b = self.get_b()

Not exactly an improvement, is it? The default value assigned to a field is equivalent to a default parameter value in __init__, e.g.,

def __init__(self, b=0):
    self.b = b

Just like you can't use self in such a parameter default, you can't use it as the field default value. The default value has to exist before the instance actually exists.

We create the field explicitly so that we can pass init=False, preventing the generated __init__ method from expecting an argument to initialize self.b.


If the function get_b is really independent of an instance (that is, you don't need an instance of A in order to return the value 1), you can use an already defined function as a default factory.

from dataclasses import dataclass, field


@dataclass
class A:
    def get_b(self=None):
        return 1

    b: int = field(default_factory=get_b, init=False)

Here, get_b will be called as a function with zero arguments (hence the default value for self in the definition) rather than be invoked from a class instance. This is, though, rather unorthodox and I wouldn't recommend structuring your code like this.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • for the final part of your answer, wouldn't it be more common to make the function static instead of saying `self=None`? – Arne Jul 24 '20 at 06:25
  • 1
    It would be, but I didn't want to make that change in case `get_b` was used as an instance method as well later (even though there's no reason for it to be one). – chepner Jul 24 '20 at 12:45
  • Also, an instance of `staticmethod` itself is not actually callable; you have to invoke it as an attribute of the class or instance in order for its `__get__` method to return a callable. Here, the actual function `get_b` needs to be passed to `field`. You would have to define `get_b`, use it as the `default_factory` argument, *then* rebind the name to a static method with `get_b = staticmethod(get_b)`. – chepner Jul 24 '20 at 12:47
  • Hm, maybe making it a static method, then using `field(default_factory=get_b.__func__, init=False)` isn't *that* bad. – chepner Jul 24 '20 at 12:50
  • I'm not thrilled with it, but I would want to see a real example of why `get_b` exists in the first place before deciding how I would want to handle it. – chepner Jul 24 '20 at 12:51
  • You're right, it's not pretty either way. And +1 on needing the context to make the better call. – Arne Jul 24 '20 at 20:05