Check if a par or parGroup exists

Hello Touchdesigner People,

I was wondering what is the best way to check if a par or parGroup exists in a component and determine which type is it to get the value. For parameters, the method pars() is pretty useful but the equivalent for parameter Groups does not exists. Parameter groups allows to get pars and parGroups but it always return a list which is not the best when getting single value parameter.

When getting a parGroup with the
par[par_name] method a td.tdError is thrown but when the par_name does not exists it just returns None.

=> op(‘transform1’).par[‘t’]
td.tdError: Parameter groups not supported. Use OP.parGroup instead.
=> op(‘transform1’).parGroup[‘t’]
type:ParGroupUnit name:t owner:/project//transform1 value:(0.0, 0.0)

=> a.pars(‘t’)

=> hasattr(op(‘transform1’).par, ‘t’)
td.tdError: Parameter groups not supported. Use OP.parGroup instead.
=> hasattr(op(‘transform1’).parGroup, ‘t’)
True
=> op(‘transform1’).par[‘rotate’]
type:Par name:rotate owner:/project/transform1 value:0.0
=>op(‘transform1’).parGroup[‘rotate’]
type:ParGroup name:rotate owner:/project/transform1 value:(0.0)

=> hasattr(op(‘transform1’).par, ‘rotate’)
True
=> hasattr(op(‘transform1’).parGroup, ‘rotate’)
True

=> op(‘transform1’).par[‘thisisnottheparyourlookingfor’]
None

How would you do ?

1 Like

@Ivan
Yeah, this is pretty inconsistent.

  1. The thrown error should either be something like td.ParDoesNotExist or a simple KeyError Exception.
  2. It should be the same behaviour for par and parGroup.

Agreed. I’ll bring this up with the dev team.

3 Likes

As my goal is to get the par/parGroup value without getting errors (without having to enclose everything in try/excepts), I prefer getting Nones than errors. For the moment, there is no way to check with a par name (string) if it is a parGroup or a par.

Even using only parGroup, an error is thrown when getting a par belonging to a parGroup (which makes sense).

=> getattr(op(‘transform1’).parGroup, “tx”)
Use .par for individual parameters

In my opinion, the hasattr() method should returns False instead of an error which is more a warning.

But the default python behavior when accessing a member by key (like in a dict) is to throw a KeyError. Accessing a member and return a None would break with that convention which is bad practice.
For that behaviour in dicts you would have the get( key, default) method.
You can to the same with getattr btw.
getattr( my_operator.par, par_name, None )
This will throw no error and return None on empty. SHould work the same for parGroup.

Getattr is relatively slow though compared to the direct keyAccess.
You can speed the process up by writing your own wrapper around getattr and using lru_cach.

import functools
@functools.lru_cache(None)
def Get_Par( operator, par_name):
    return getattr( operator.par, par_name, None)

@functools.lru_cache(None)
def Get_Pargroup( operator, par_name):
    return getattr( operator.parGroup, par_name, None)

the lru_cache basicly stores the result of the functioncall in a dict. So after the first call you no longer run getattr but a simple dict-lookup which is much faster then getattr.

1 Like

You can speed the process up by writing your own wrapper around getattr and using lru_cach.

Nice trick !

Unfortunately even when calling hasattr with a default value it stills raise an error. Maybe it is because it is not an AttributeError

getattr(target_op.par, 'tx', None)
td.tdError: Use .par for individual parameters

getattr(target_op.par, 't', 'nope')
td.tdError: Parameter groups not supported. Use OP.parGroup instead.

Indeed, a call to hasattr() calls getattr() and check if AttributeError is raised.

Oh wow, this is def. not a good implementation then. ( Pingin you again here @Ivan )
So when we use the name of a parGroup in getAttr (and also with key-access) we get an error and notification that we should use parGroup instead.
I get the idea but def. not good behaviour! When I access the par member it should only account for the parMember!
Feels like this might actually result in breaking behavior for older projects!

The solution currently planned is that par[] and parGroup[] will return None when key is not found. Not really Pythonic, but best solution for backwards compatibility.

Note: It will be very important to test “is None” and not “== None” when using this key style access and testing to par collections. This is because pars that hold OP types will be auto-cast to None when the operator doesn’t exist. This means “== None” will be True but “is None” will be False.

1 Like

I would argue that changing the behaviour of par[] to the on of parGroup[] is less backward compatible then changing parGroup to the behaviour of par.
This is annecdotal, but at least for me I just slowly start to make use of parGroups. Basicly everything else is par and I am pretty sure I use try/catch in some palces to check if a par exists.
Hmm, tricky situation.

@alphamoonbase how would you expect parGroups to act in that case? Return None when it doesn’t exist unless a par with the same name exists and then exception?

my_op.parGroup[ group_name ]

Raise an exception when the ParGroup does not exist (KeyError or td.ParGroupDoesNotExists).
Return parGroup when it exists.

my_op.par[ par_name ]

Raise an exception when the Par does not exist (KeyError or td.ParDoesNotExists).
Return par when it exists.

One should ignore the other. No exception when trying to access a par with a parGroup name and vice versa.

I agree but think that will break a lot of old projects. Will discuss with the team