From string to constant to check type

Hello !

I've wrote a simple function to store some constants in a list, from a string ( because I'm lazy) :

def geoTypeList():
    geo_constants = "tube,cube,plane" # not the full list
    geo_types = [("c4d.O" + i) for i in geo_constants.split(",")]  
    return geo_types # return the list

I was happy with my solution until I've tried to use it :

    for i in objs:
        if i.GetType() in geoTypeList():
            print(i.GetName(), "is a", i.GetTypeName(), i.GetRealType())
        else:
            print("Not a geometry object")

I've tried many times and many variant until I've realized that I'm trying to check a constant against some strings, so it can't work. But I'm stuck here, I don't know how to pass the list items as constants and not as string.
Also before you ask, why I'm not just using ID integers ? Because I would like to keep the code easily readable, and the docs use the constants and symbols.

Thank you,

Hi @John_Do,

thank you for reaching out to us. You could use Python's eval to evaluate the string you do build. Although I do not quite see the point in doing it, since you open the can of worms that is eval with it (if something goes south, it could do do bad things). So I would strongly recommend not to try something like this (although the chances of something going south here are probably slim). Consider yourself warned about the pitfalls of eval ;)

Cheers,
Ferdinand

import c4d

def geoTypeList():
    geo_constants = "tube,cube,plane" # not the full list
    # Use eval to store the integer repr and not a string repr of the symbol
    geo_types = [eval("c4d.O" + i)
                 for i in geo_constants.split(",")]  
    return geo_types # return the list

nodes = [c4d.BaseList2D(c4d.Ocube), 
        c4d.BaseList2D(c4d.Otube), 
        c4d.BaseList2D(c4d.Oplane),
        c4d.BaseList2D(c4d.Osphere)]

for i in nodes:
    if i.GetType() in geoTypeList():
        print(i.GetName(), "is a", i.GetTypeName(), i.GetRealType())
    else:
        print(f"Not a geometry object: {i}")

# Although I do not quite see the point in doing it like that. This seems to
# be much more convenient and more importantly less dangerous:

typeSymbols = (c4d.Otube, c4d.Ocube, c4d.Oplane)

for i in nodes:
    if i.GetType() in typeSymbols:
        print(i.GetName(), "is a", i.GetTypeName(), i.GetRealType())
    else:
        print(f"Not a geometry object: {i}")

edit: Here is some context about the pitfalls of eval: https://realpython.com/python-eval-function/#minimizing-the-security-issues-of-eval

Hi @John_Do,

my solution was not very good due to the inherent diceyness of eavl. Since you are only interested in an attribute, you could of course use getattr(). Something like attached to the end of the posting.

Cheers,
Ferdinand

import c4d

def yieldSymbolsFromString(symbolString):
    """Just gets the attributes from the module.
    """
    for s in symbolString.split(","):
        val = getattr(c4d, f"O{s}")
        yield val

nodes = [c4d.BaseList2D(c4d.Ocube), 
         c4d.BaseList2D(c4d.Otube), 
         c4d.BaseList2D(c4d.Oplane),
         c4d.BaseList2D(c4d.Osphere)]

for node in nodes:
    if node.GetType() in yieldSymbolsFromString("tube,cube,plane"):
        print(f"{node.GetName()} is a geometry.")
    else:
        print(f"{node.GetName()} is not a geometry.")

# Will raise an attribute error on Ocar.       
for s in yieldSymbolsFromString("tube,cube,plane,car"):
    pass
Cube is a geometry.
Tube is a geometry.
Plane is a geometry.
Sphere is not a geometry.
Traceback (most recent call last):
  File "...\scribbles_a.py", line 22, in <module>
    for s in yieldSymbolsFromString("tube,cube,plane,car"):
  File "...\scribbles_a.py", line 7, in yieldSymbolsFromString
    val = getattr(c4d, f"O{s}")
AttributeError: module 'c4d' has no attribute 'Ocar'
[Finished in 4.1s]

Hi @ferdinand,

In fact I think I was looking for the eval() method which I know but somewhat have forgotten.

Thanks for pointing the fact that using it could be dangerous, but in this particular context I'm not sure to understand why ? I'm not a seasoned coder ( far from it ) but the linked article seems to point security issues in the context of using eval() in conjunction with input(), which is not the case here.

I'm doing this mainly to practice Python as much as possible, but I guess it is wasted computing power since these symbols are not changing anytime soon.

Anyway, I've learned some things, thank you for the help, much appreciated !

Hi @John_Do,

the reason why eval is bad is because you always have to sanitize your inputs (in your case the string). Which might sound trivial to do, but is actually hard when you have to guarantee that nothing can go wrong. eval is of course not as bad as exec, runpy or similar things, but this whole idea of cobbeling code together at runtime often produces more problems than it solves. It can of course be a powerful tool, but there are often more "boring" and safer alternatives.

If then something goes wrong, finding the bug is usually hard, because you have then to untangle the code created dynamically at runtime. In this case problems are unlikely as I said, but something like this could happen for example:

import c4d

code = "c4d.Ocone, c4d.Ocube + c4d.Osphere"

for token in code.split(","):
    print(f"{token.strip()}: {eval(token)}")
c4d.Ocone: 5162
c4d.Ocube + c4d.Osphere: 10319

So there is nothing which prevents you from accidentally adding two symbols together. Here it is easy to spot, but just hide that bit in 5k lines of code and dynamically build that code string at runtime and it becomes a nightmare to debug. So I would consider this bad practice which is why I was so verbal about my warnings. getattr for example is a much better solution for your problem, because it does not come with these problems.

Cheers,
Ferdinand

Thank you for the explanation @ferdinand.

The example makes sense even for a beginner like me, that's great :)