Can I declare Python class fields outside the constructor method?
Question:
I am absolutly new in Python (I came from Java) and I have the following doubts about class fields.
Considering a code like this:
class Toy():
def __init__(self, color, age):
self.color = color
self.age = age
action_figure = Toy('red', 10)
Ok, what is done it is clear and very simple:
it is defining a Toy class. In the constructor method is defining two fields and how to set the fields value. Finnally (in the "main") it is created a new Toy instance passing the values of the field in the constructor call.
Ok, clear…but I have a doubt. In Java to define the same class I do something like this:
public class Toy {
private String color;
private int age;
// CONSTRUCTOR:
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
}
Ok, it is similar but I have figoured out a pretty big difference. In my Java conde I declare the class fields as variable outside my constructor method. In Python I am defining the class fields directly inside the constructor method. So it means that in Java I can declare n class fields and use the constructor method to initialized only a subset of this fields, for example something like this:
public class Toy {
private String color;
private int age;
private String field3;
private String field4;
private String field5;
// CONSTRUCTOR:
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
}
where I have also the field3, field4 and field5 fields that will be not initialized by my constructor (in case I can set theyr value in a second time using a setter method).
Can I do something similar in Python? Can I declar class fields outside the constructor method?
Answers:
There isn’t really a need for this in Python. What you would call “instance variables” in Java can be added to an instance of a class whenever it is desired:
class Person:
def __init__(self, name):
self.name = name
def get_a_job(self):
self.job = "Janitor"
print(f"{self.name} now has a job!")
p1 = Person("Tom")
p2 = Person("Bob")
p1.get_a_job()
print(p1.job)
print(p2.job)
Output:
Tom now has a job!
Janitor
Traceback (most recent call last):
File "...", line 17, in <module>
print(p2.job)
AttributeError: 'Person' object has no attribute 'job'
>>>
In python, you could do something like this:
class Toy():
def__init__(self, color, age):
self.color = color
self.age = age
def another_method(self, f):
self.field3 = f + 4
return self.field3
But it is usually recommended for clarity (more arguments here: https://stackoverflow.com/a/38378757/4709400) to initialize all your instance variables inside the constructor, so you would do:
class Toy():
def__init__(self, color, age):
self.color = color
self.age = age
self.field3 = None
self.field4 = 0 # for instance
self.field5 = "" # for instance
def another_method(self, f):
self.field3 = f + 4
return self.field3
Classes in python vs in c++/java are fundamentally different in that a c++/java class has a fixed data structure and size (bytes) because each attribute is declared or defined outside of all methods (normally as private variables) but in python everything is on the fly(dynamically typed).
Choice of defining attribute in constructor vs other methods is about other people being able to quickly understand your code/data structure (although due to dynamic-ness calling python classes data structures would be inappropriate)
As an example of dynamic-ness You can even add new methods and attributes to classes and even instances at run time:
class A:
pass
Adding stuff to a class at runtime (These will be added to all existing and future instances of the class):
A.key = val
def f(self):
return 0
A.myfunction = f
a = A()
a.myfunction()
# 0
Adding stuff to a single instance at runtime:
a=A()
a.attr='something'
def f(self):
return 0
a.fun=f.__get__(a)
a.fun()
# 0
As Python is dynamically typed, you don’t declare variables beforehand, but they are initialised at runtime. This also means that you don’t have to add instance attributes to the constructor, but you can add them any time later. In fact, you can add attributes to any object, including the class object itself. Adding instance attributes to the constructor is mostly a question of consistency and readability.
Data attributes that are added to the class definition are called class attributes in Python (I don’t know Java, but I believe, this corresponds to static variables). This is useful e.g. to keep track of all class instances:
class Dog:
lineage = {'Phylum':'Chordata', 'Class':'Mammalia', 'Species':'Canis lupus'}
all_dogs = []
def __init__(self, fur_color, tail_length):
self.fur_color = fur_color
self.tail_length = tail_length
self.all_dogs.append(self) # class attributes can be accessed via the instance
Bello = Dog('white',50)
print(Dog.all_dogs)
print(Dog.[0].fur_color)
As you noted this is not part of the core python language.
This felt like a missing feature to me as well, for several reasons:
-
readability/maintainability: with the classic python way of defining attributes in the constructor or elsewhere dynamically, it is not obvious when reading the code what is the "contract" (or expected duck contract at least) of an object.
-
compacity: creating long constructors with just self.<foo> = <foo>
is not the most fun, and the more fields you need the more lines you have to write
-
ability to extend the contract of a field, for example to add a default value factory in case the default value is mutable, or to add value validators
-
ability to create mix-in classes, i.e. classes that implement some functionality relying on some fields, but not forcing the use of any constructor.
This is why I created pyfields
. With this library, each field is defined as a class member:
from pyfields import field
from typing import List
class Toy:
color: str = field(doc="The toy's color, a string.")
age: int = field(doc="How old is this Toy. An integer number of years.")
field3: str = field(default='hello', check_type=True)
field4: List[str] = field(default_factory=lambda obj: ['world'])
field5: str = field(default=None,
validators={'should be 1-character long': lambda x: len(x) == 1})
def __init__(self, color, age):
self.color = color
self.age = age
t = Toy(color='blue', age=12)
print(t.field3 + ' ' + t.field4[0])
print(t.field5 is None)
t.field5 = 'yo'
yields
hello world
True
Traceback (most recent call last):
...
valid8.entry_points.ValidationError[ValueError]: Error validating [Toy.field5=yo]. InvalidValue: should be 1-character long. Function [<lambda>] returned [False] for value 'yo'.
Note that I use the python 3.7+ type hint syntax above but pyfields
is compliant with older versions (python 2, python 3.5), see documentation.
You can even simplify this example further by auto-creating the constructor or using @autofields
to generate the field()
calls for you. pyfields
also provides an @autoclass
so you can even generate other class behaviours easily such as string representation, equality, conversion to dict, etc. See autoclass doc.
Note that pyfields
was inspired by giants such as attrs
, but is distinctive in that it preserves the segregation principle. So it does not fiddle with __init__
or __setattr__
behind your back. This therefore allows your fields to be validated on set (and not only in the constructor), and also to develop elegant mix-in classes defining fields and methods, but no constructor.
The desired behavior is provided since Python 3.7 by the @dataclass
decorator.
from dataclasses import dataclass
from typing import Optional
@dataclass
class Toy1:
color: str
age: int
field3: Optional[str] = None
fielg4: Optional[str] = None
field5: Optional[str] = None
def __init__(self, color:str, age: int) -> None:
self.color = color
self.age = age
toy1 = Toy('red', 2)
The constructor can even be suppressed and the behavior is the same.
@dataclass
class Toy2:
color: str
age: int
field3: Optional[str] = None
fielg4: Optional[str] = None
field5: Optional[str] = None
toy2 = Toy('red', 2)
@dataclass will enable other function like a nice string representation for the instance (__repr__
):
Toy(color='red', age=2, field3=None, fielg4=None, field5=None)
or a direct comparison between the instances:
toy1 == toy2
True
A comprehensive explanation as well as the rational behind it can be found at: https://realpython.com/python-data-classes/
If you want to enforce the types during runtime, pydantic
can be used in this model.
I am absolutly new in Python (I came from Java) and I have the following doubts about class fields.
Considering a code like this:
class Toy():
def __init__(self, color, age):
self.color = color
self.age = age
action_figure = Toy('red', 10)
Ok, what is done it is clear and very simple:
it is defining a Toy class. In the constructor method is defining two fields and how to set the fields value. Finnally (in the "main") it is created a new Toy instance passing the values of the field in the constructor call.
Ok, clear…but I have a doubt. In Java to define the same class I do something like this:
public class Toy {
private String color;
private int age;
// CONSTRUCTOR:
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
}
Ok, it is similar but I have figoured out a pretty big difference. In my Java conde I declare the class fields as variable outside my constructor method. In Python I am defining the class fields directly inside the constructor method. So it means that in Java I can declare n class fields and use the constructor method to initialized only a subset of this fields, for example something like this:
public class Toy {
private String color;
private int age;
private String field3;
private String field4;
private String field5;
// CONSTRUCTOR:
public Dog(String color, int age) {
this.color = color;
this.age = age;
}
}
where I have also the field3, field4 and field5 fields that will be not initialized by my constructor (in case I can set theyr value in a second time using a setter method).
Can I do something similar in Python? Can I declar class fields outside the constructor method?
There isn’t really a need for this in Python. What you would call “instance variables” in Java can be added to an instance of a class whenever it is desired:
class Person:
def __init__(self, name):
self.name = name
def get_a_job(self):
self.job = "Janitor"
print(f"{self.name} now has a job!")
p1 = Person("Tom")
p2 = Person("Bob")
p1.get_a_job()
print(p1.job)
print(p2.job)
Output:
Tom now has a job!
Janitor
Traceback (most recent call last):
File "...", line 17, in <module>
print(p2.job)
AttributeError: 'Person' object has no attribute 'job'
>>>
In python, you could do something like this:
class Toy():
def__init__(self, color, age):
self.color = color
self.age = age
def another_method(self, f):
self.field3 = f + 4
return self.field3
But it is usually recommended for clarity (more arguments here: https://stackoverflow.com/a/38378757/4709400) to initialize all your instance variables inside the constructor, so you would do:
class Toy():
def__init__(self, color, age):
self.color = color
self.age = age
self.field3 = None
self.field4 = 0 # for instance
self.field5 = "" # for instance
def another_method(self, f):
self.field3 = f + 4
return self.field3
Classes in python vs in c++/java are fundamentally different in that a c++/java class has a fixed data structure and size (bytes) because each attribute is declared or defined outside of all methods (normally as private variables) but in python everything is on the fly(dynamically typed).
Choice of defining attribute in constructor vs other methods is about other people being able to quickly understand your code/data structure (although due to dynamic-ness calling python classes data structures would be inappropriate)
As an example of dynamic-ness You can even add new methods and attributes to classes and even instances at run time:
class A:
pass
Adding stuff to a class at runtime (These will be added to all existing and future instances of the class):
A.key = val
def f(self):
return 0
A.myfunction = f
a = A()
a.myfunction()
# 0
Adding stuff to a single instance at runtime:
a=A()
a.attr='something'
def f(self):
return 0
a.fun=f.__get__(a)
a.fun()
# 0
As Python is dynamically typed, you don’t declare variables beforehand, but they are initialised at runtime. This also means that you don’t have to add instance attributes to the constructor, but you can add them any time later. In fact, you can add attributes to any object, including the class object itself. Adding instance attributes to the constructor is mostly a question of consistency and readability.
Data attributes that are added to the class definition are called class attributes in Python (I don’t know Java, but I believe, this corresponds to static variables). This is useful e.g. to keep track of all class instances:
class Dog:
lineage = {'Phylum':'Chordata', 'Class':'Mammalia', 'Species':'Canis lupus'}
all_dogs = []
def __init__(self, fur_color, tail_length):
self.fur_color = fur_color
self.tail_length = tail_length
self.all_dogs.append(self) # class attributes can be accessed via the instance
Bello = Dog('white',50)
print(Dog.all_dogs)
print(Dog.[0].fur_color)
As you noted this is not part of the core python language.
This felt like a missing feature to me as well, for several reasons:
-
readability/maintainability: with the classic python way of defining attributes in the constructor or elsewhere dynamically, it is not obvious when reading the code what is the "contract" (or expected duck contract at least) of an object.
-
compacity: creating long constructors with just
self.<foo> = <foo>
is not the most fun, and the more fields you need the more lines you have to write -
ability to extend the contract of a field, for example to add a default value factory in case the default value is mutable, or to add value validators
-
ability to create mix-in classes, i.e. classes that implement some functionality relying on some fields, but not forcing the use of any constructor.
This is why I created pyfields
. With this library, each field is defined as a class member:
from pyfields import field
from typing import List
class Toy:
color: str = field(doc="The toy's color, a string.")
age: int = field(doc="How old is this Toy. An integer number of years.")
field3: str = field(default='hello', check_type=True)
field4: List[str] = field(default_factory=lambda obj: ['world'])
field5: str = field(default=None,
validators={'should be 1-character long': lambda x: len(x) == 1})
def __init__(self, color, age):
self.color = color
self.age = age
t = Toy(color='blue', age=12)
print(t.field3 + ' ' + t.field4[0])
print(t.field5 is None)
t.field5 = 'yo'
yields
hello world
True
Traceback (most recent call last):
...
valid8.entry_points.ValidationError[ValueError]: Error validating [Toy.field5=yo]. InvalidValue: should be 1-character long. Function [<lambda>] returned [False] for value 'yo'.
Note that I use the python 3.7+ type hint syntax above but pyfields
is compliant with older versions (python 2, python 3.5), see documentation.
You can even simplify this example further by auto-creating the constructor or using @autofields
to generate the field()
calls for you. pyfields
also provides an @autoclass
so you can even generate other class behaviours easily such as string representation, equality, conversion to dict, etc. See autoclass doc.
Note that pyfields
was inspired by giants such as attrs
, but is distinctive in that it preserves the segregation principle. So it does not fiddle with __init__
or __setattr__
behind your back. This therefore allows your fields to be validated on set (and not only in the constructor), and also to develop elegant mix-in classes defining fields and methods, but no constructor.
The desired behavior is provided since Python 3.7 by the @dataclass
decorator.
from dataclasses import dataclass
from typing import Optional
@dataclass
class Toy1:
color: str
age: int
field3: Optional[str] = None
fielg4: Optional[str] = None
field5: Optional[str] = None
def __init__(self, color:str, age: int) -> None:
self.color = color
self.age = age
toy1 = Toy('red', 2)
The constructor can even be suppressed and the behavior is the same.
@dataclass
class Toy2:
color: str
age: int
field3: Optional[str] = None
fielg4: Optional[str] = None
field5: Optional[str] = None
toy2 = Toy('red', 2)
@dataclass will enable other function like a nice string representation for the instance (__repr__
):
Toy(color='red', age=2, field3=None, fielg4=None, field5=None)
or a direct comparison between the instances:
toy1 == toy2
True
A comprehensive explanation as well as the rational behind it can be found at: https://realpython.com/python-data-classes/
If you want to enforce the types during runtime, pydantic
can be used in this model.