15

In normal python classes I can define class attributes like

class Example:
  x = 3
  def __init__(self):
    pass

And if I then do Example.x or Example().x, I get 3.

When I inherit pydantic's BaseModel, I can't figure out how to define class attributes, because the usual way of defining them is overwritten by BaseModel.

For example:

class Example(BaseModel):
  x = 3

print(Example.x)
--> type object 'Example' has no attribute 'x'

I want to be able to define class level attributes. What is the proper method/syntax for this?

Alan
  • 1,746
  • 7
  • 21
  • 1
    Can't you use dataclass instead if you just want to have a data container that you can call without instantiation ? – Bastien B Jan 28 '22 at 18:23
  • Afraid not. There are other features of Basemodel that I need in my class. Also, more broadly, what if I wanted some class attributes for a different context. E.g. mutable class attributes so I can update all classes if I update one, – Alan Jan 31 '22 at 16:34

2 Answers2

27

Okey-dokey, I eventually got to the bottom of it. The answer lies with how dataclasses handle class variables. (In retrospect that should have been obvious, but ya live and ya learn)

There exists a type called ClassVar. When a class's attribute is typed with ClassVar, then it becomes a class variable.

class Example(BaseModel):
  x: ClassVar[int] = 3
  y: int


# Usage
print(Example().x)
>>> ValidationError

print(Example.x)
>>> 3

Alan
  • 1,746
  • 7
  • 21
  • 5
    `ClassVar` is defined in `typing`, so don't forget to import it (`from typing import ClassVar`) – ThibThib May 23 '22 at 16:56
  • Thanks for the finding. Just found it in doc as well: https://pydantic-docs.helpmanual.io/usage/models/#automatically-excluded-attributes – Billy Chan Oct 27 '22 at 12:52
-3

You define the class level attributes correctly, but you are reading it out wrong. When reading out variables from this class, you should access the class over another variable defining that class. Like this:

from pydantic import BaseModel
class User(BaseModel):
  x = 3
user = User()
print(user.x)

what gives out: 3

Leo_001
  • 47
  • 4
  • 1
    Hi Leo, that is just instantiating the class though. I mention in my question that class attributes can be accessed via both `Example.x` and `Example().x`. I am looking to access class attributes via `Example.x` specifically withOUT having to instantiate. The reason is, there a many times when we cannot instantiate a class for free. Maybe it mutates itself on `__init__` or maybe it just has non-optional field whose values I don't have in memory. And also, just to save memory, I don't want to have to instantiate the classes to get the info I know exists (and could get without BaseModel). – Alan Mar 29 '22 at 08:37