Skip to content

Mutable datatypes initialized with unsafe defaults #758

@rafmudaf

Description

@rafmudaf

Mutable datatypes initialized with unsafe defaults

Some of the attrs-derived classes in floris.simulation may be initialized with unsafe defaults for mutable data types (mutable vs immutable types in Python). In Python, list, set, and dict types are all mutable, and variables referring to mutable types act like pointers to the same memory. For mutable-type attributes in attrs-derived classes with defaults of empty instances of the mutable type, any instance of the class will refer to the same memory for these particular attributes.

See #750 (comment) for the initial report.

Example

from attrs import define, field

@define
class A:
    x: list = field(default=[])
    y: list = field(factory=list)

a1 = A()
a2 = A()

print(a1, a2)
a1.x.append(4)
a1.y.append(5)
print(a1, a2)

The code above yields the following output:

A(x=[], y=[]) A(x=[], y=[])
A(x=[4], y=[5]) A(x=[4], y=[])

The first print-statement shows that the instances of class A are initialized with empty lists for both x and y attributes. In the second print-statement, appending a value to both attributes for the a1 instance is shown to also affect the x attribute of the a2 instance. The y attribute is not affected because it is declared with factory=list so a new instance of list is created for new instances of A.

This is also true of NumPy arrays, as shown below. NumPy arrays don't have an append method, but they are also mutable and their values can be changed directly leading to this same side effect.

from attrs import define, field
import numpy as np

@define
class A:
    x: np.array = field(default=np.array([1,2,3]))

a1 = A()
a2 = A()

print(a1, a2)
a1.x[0] = 9
print(a1, a2)
A(x=array([1, 2, 3])) A(x=array([1, 2, 3]))
A(x=array([9, 2, 3])) A(x=array([9, 2, 3]))

Impacts on FLORIS

The attrs library is used only in floris.simulation, so this issue is isolated to that package. While many class definitions have this issue, the use of append is generally discouraged for performance considerations. However, there are many instances where the values in NumPy arrays are modified directly including all of the solvers.

The ultimate impact is unclear, but I'll update this section as this issue is understood more.

Affected classes

Here's a first-pass list at affected classes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions