0
votes

I am looking for a way in python to build classes in python that:

  • setter check the type of the value before assignment
  • impossible to add new class attribute

for the time being I found those two decorators:


    def getter_setter_gen(name, type_):
        def getter(self):
            return getattr(self, "__" + name)
        def setter(self, value):
            print "setter", value
            if not isinstance(value, type_):
                raise TypeError("%s attribute must be set to an instance of %s" % (name, type_))
            setattr(self, "__" + name, value)
        return property(getter, setter)


    def auto_attr_check(cls):
        new_dct = {}
        print "auto_attr_check", cls.__dict__.items()
        for key, value in cls.__dict__.items():
            if isinstance(value, type):
                value = getter_setter_gen(key, value)
            new_dct[key] = value
        # Creates a new class, using the modified dictionary as the class dict:
        n = type(cls)(cls.__name__, cls.__bases__, new_dct)
        return n

and


    def froze_it(cls):
        def frozensetattr(self, key, value):
            print key
            key = ''+key
            print(dir(self))
            print key
            if not hasattr(self, key):
                raise TypeError("Class {} is frozen. Cannot set {} = {}"
                  .format(cls.__name__, key, value))
            else:
                object.__setattr__(self, key, value)
        cls.__setattr__ = frozensetattr
        return cls

but I have a lot of trouble to join those two approach. Can you help me? Do you have ideas? Thanks a lot

1

1 Answers

1
votes

The issue you're encountering is that your property is using setattr to set the value of your attributes, but that you've overridden __setattr__ in such a way that it can't ever succeed. The hasattr check will fail for both the main property name, and for the underscore-prefixed name of the actual attribute that underlies the property (if the setter code could get that far).

I suggest two complementary fixes. First, change the hasattr check to be a little more careful. If it fails, it should also check the class dict for a property object:

    def frozensetattr(self, key, value):
        if not hasattr(self, key) and type(cls.__dict__.get(key)) is not property:
            raise TypeError("Class {} is frozen. Cannot set {} = {}"
              .format(cls.__name__, key, value))
        else:
            object.__setattr__(self, key, value)

The second fix is to the setter function, which should bypass the __setattr__ method for the underlying attribute:

    def setter(self, value):
        print "setter", value
        if not isinstance(value, type_):
           raise TypeError("%s attribute must be set to an instance of %s" % (name, type_))
        object.__setattr__(self, "__" + name, value)

A final note: The double-underscore prefix on the attribute names created by your property is not going to invoke name mangling, which I suspect it what you intend. Name mangling only happens at compile time. When a method contains an name like __bar, the name gets transformed by the compiler to _NameOfTheClass__bar (where NameOfTheClass is the name of the class the method is defined in).

At runtime, the code behaves exactly as if the mangled name was written directly in the source. This means that __setattr__ and friends don't treat mangled names (or double-underscore prefixed names that would have been mangled by the compiler if they were written as regular variable names instead of strings) in any kind of special way. So there's no easy way to do name mangling on dynamically created variables.

If you just want your names to be informally marked as private (that is, they're not part of the public API), you should probably use a single leading underscore.