Defining and initializing object models using specklepy

Hi @izzylys,

I’m a little confused on how to define and initialize custom objects in Python, due to an example in the Speckle docs (see screenshot below).

In the example, instance attributes are set in the __init__(). However, as the class inherits from Base, the named arguments (kwargs) are already attributed to the object. And making attributes detachable is also possible by adding the named detachable argument. So I would say defining an __init__() would not be needed there.

But perhaps I am missing something? So if someone has some guidance on this, that would be much appreciated!

image

2 Likes

Hi @harmijsseldijk,

Welcome to the forum! :blush:

If you scroll a bit further down on the specklepy docs page, you’ll see more info about Base and creating subclasses / custom objects:
https://speckle.guide/dev/py-examples.html#base-object-serialization
As mentioned in the docs, you do not need an init method if you initialise all typed attributes with a non-mutable default value or a value that you will only change by reassignment.

The Block definition you screenshotted is a more verbose example with an init so that it is clearer to the reader what is going on. Here, default values for the length, width, height, and origin attributes are being set in the init. You could leave off the the init here, but then you would get an AttributeError trying to access attributes if they haven’t been set which isn’t very nice.

What I would do instead here if you don’t want to have an init method is to define the class differently:

class Block(Base, detachable={"origin"}):
    length: float = 0.0
    width: float = 0.0
    height: float = 0.0
    origin: Point = None

You can see more examples of this in the previous link to the docs or in specklepy.objects.geometry:


I’m not quite sure what you mean by this though. If the kwargs you’re referring to are the length, width, height, and origin, those are attributes on Block because they have been defined in the Block class definition - not because you’ve subclassed base. Subclassing Base simply gives you the decomposition api, serialisation capabilities, and light type checking Speckle Objects.

Hope that was helpful! If anything is unclear or you’re wanting more guidance, please feel free to ask further questions :+1:

Cheers,
Izzy

2 Likes

Many thanks for the elaborative response! The alternative way to define that example Block class you gave makes it all clear.

With this, I was referring to the init of the Base class, as shown in the screenshot below. If an object is initialized, the named (or keyword) arguments are set as attributes. So assigning them in the init of a subclass is not necessary, right?

image

One extra question actually: Do you mind elaborating a bit on the quote below, as it’s not completely clear to me? (e.g. with “typed attributes” you are referring to the class attributes of custom objects? And with “initialise” you mean assigning the default value?)

Hi @harmijsseldijk

check out this stackoverflow post ! ‘init’ methods are just there for the first instance of the class. Just some python syntax. You always need to assign them in the init function for python if you don’t want to assign them yourselves. It’s the same concept as a constructor in C#.

Cheers,
Reynold

Sure thing! By “typed attributes” I mean the class attributes you’ve defined on your custom object that are annotated with specific type (eg length: float). I’m referring to these in particular because we do some light type checking for these types of class attributes on assignment. By “initialise” yes I mean assigning it a default value which you can do when defining (eg length: float = 0.0) or in the init method as shown in the Block example.

So in summary, as long as you have default non-mutable values for the attributes you care about, you don’t need an init

more info 😎

To further expand on why there is this is, the attributes assigned to custom Speckle Objects are actually instance attributes that overshadow class attributes. This is a little hack that helps us in a few ways, but particularly in serialisation / deserialisation. For optimal performance, the Speckle Objects that come with specklepy do not have init methods. This significantly decreases deserialisation time for us because an unnecessary init isn’t getting called every single time the deserialiser creates an empty speckle object to fill with values from the incoming json. This works great and is perfectly safe, however it can be a footgun if you aren’t smart about how you’re defining your classes.

Since there is no init, a fresh object’s attributes if you haven’t assigned them will be referring to the class attribute. So if for example you have a default value for an attribute that is a mutable value (eg an empty list) then mutating it on an instance will mutate the class attribute and affect future instances of the class.

This is why the simplest and most failsafe method of defining classes is with an init and this is what’s shown to you first in the speckle.guide example. However, as long as you follow the simple rule of always initialising with non-mutable default values (None being the simplest way to go) or if the val will only ever be changed by reassignment (eg you only do attr = new_val rathre than attr.append(blah)) then it will not be a problem!

2 Likes