12
@dataclass
class Stock:
    symbol: str
    price: float = get_price(symbol)

Can a dataclass attribute access to the other one? In the above example, one can create a Stock by providing a symbol and the price. If price is not provided, it defaults to a price which we get from some function get_price. Is there a way to reference symbol?

This example generates error NameError: name 'symbol' is not defined.

2
  • 2
    Complicated initialization behaviour like that belongs in a proper class instead of a dataclass. Commented Sep 4, 2022 at 13:28
  • 1
    I agree, but we may differ if this case should be considered complex. There is no business logic except getting a price if not provided. Function as default value works fine in dataclasses except the problem I discussed (not able to refer to another parameter). I think the answer from @S.B. below can help use dataclass in such simple cases. Commented Sep 4, 2022 at 23:46

2 Answers 2

13

You can use __post_init__ here. Because it's going to be called after __init__, you have your attributes already populated so do whatever you want to do there:

from typing import Optional
from dataclasses import dataclass


def get_price(name):
    # logic to get price by looking at `name`.
    return 1000.0


@dataclass
class Stock:
    symbol: str
    price: Optional[float] = None

    def __post_init__(self):
        if self.price is None:
            self.price = get_price(self.symbol)


obj1 = Stock("boo", 2000.0)
obj2 = Stock("boo")
print(obj1.price)  # 2000.0
print(obj2.price)  # 1000.0

So if user didn't pass price while instantiating, price is None. So you can check it in __post_init__ and ask it from get_price.


There is also another shape of the above answer which basically adds nothing more to the existing one. I just added for the records since someone might attempt to do this as well and wonder how is it different with the previous one:

@dataclass
class Stock:
    symbol: str
    price: InitVar[Optional[float]] = None

    def __post_init__(self, price):
        self.price = get_price(self.symbol) if price is None else price

You mark the price as InitVar and you can get it with a parameter named price in the __post_init__ method.

Sign up to request clarification or add additional context in comments.

2 Comments

This is brilliant - the cleanest solution.
Based on the post-init docs referenced, better yet (if you explicitly don't want price to be part of the constructor), then price: float = dataclasses.field(init=False)
5

If the price is always derivable from the symbol, I would go with a @property. Keeps the code lean and it looks like an attribute from the outside:

def get_price(symbol):
    return 123

@dataclass
class Stock:
    symbol: str

    @property
    def price(self):
        return get_price(symbol)

stock = Stock("NVDA")
print(stock.price) # 123

If you want to have a settable attribute that also has a default value that is derived from the other fields, you may just want to implement your own __init__ or __post_init__ as suggested.

If this is something you encounter a lot and need more sophisticated ways to handle it, I would recommend looking into pydantic and validators.

from typing import Optional

from pydantic import validator
from pydantic.dataclasses import dataclass

def get_price(symbol):
    return 123.0

@dataclass
class Stock:
    symbol: str
    price: Optional[float] = None

    @validator('price')
    def validate_price(cls, v, values, **kwargs):
        return v or get_price(values["symbol"])


print(Stock("NVDA"))
#> Stock(symbol='NVDA', price=123.0)
print(Stock("NVDA", 456))
#> Stock(symbol='NVDA', price=456.0)

1 Comment

I thought of property before asking, but that changes the workflow if someone really has a value to provide (default only in case no value provided). pydantic looks nice but looks more involved in this use case. I'll go through the docs to understand the use cases.

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.