How to Force/Ensure Class Attributes Are a Specific Type

How to force/ensure class attributes are a specific type?

You can use a property like the other answers put it -
so, if you want to constrain a single attribute, say "bar",
and constrain it to an integer, you could write code like this:

class Foo(object):
def _get_bar(self):
return self.__bar
def _set_bar(self, value):
if not isinstance(value, int):
raise TypeError("bar must be set to an integer")
self.__bar = value
bar = property(_get_bar, _set_bar)

And this works:

>>> f = Foo()
>>> f.bar = 3
>>> f.bar
3
>>> f.bar = "three"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in _set_bar
TypeError: bar must be set to an integer
>>>

(There is also a new way of writing properties, using the "property" built-in as a decorator to the getter method - but I prefer the old way, like I put it above).

Of course, if you have lots of attributes on your classes, and want to protect all of them in this way, it starts to get verbose. Nothing to worry about - Python's introspection abilities allow one to create a class decorator that could automate this with a minimum of lines.

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

def auto_attr_check(cls):
new_dct = {}
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:
return type(cls)(cls.__name__, cls.__bases__, new_dct)

And you just use auto_attr_checkas a class decorator, and declar the
attributes you want in the class body to be equal to the types the attributes need to constrain too:

...     
... @auto_attr_check
... class Foo(object):
... bar = int
... baz = str
... bam = float
...
>>> f = Foo()
>>> f.bar = 5; f.baz = "hello"; f.bam = 5.0
>>> f.bar = "hello"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in setter
TypeError: bar attribute must be set to an instance of <type 'int'>
>>> f.baz = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in setter
TypeError: baz attribute must be set to an instance of <type 'str'>
>>> f.bam = 3 + 2j
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in setter
TypeError: bam attribute must be set to an instance of <type 'float'>
>>>


How to force an attribute to be int in python?

Good Python design avoids explicit type-checking: "if it quacks like a duck, it's a duck...". Therefore, you should first try to perform your data validation outside your class, or not at all.

Having said this, one way to perform your check is to redefine __setattr__ as described here:

class Point():

def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z

def __setattr__(self, name, value):
assert isinstance(value, str), "Value must be of type str"
super().__setattr__(name, value)

p = Point('a', 'b', 'c')
p.x = 3

# AssertionError: Value must be of type str

How to enforce a child class to set attributes using abstractproperty decorator in python?

You can do some like this,
parent class:

class Parent(object):
__metaclass__ = ABCMeta
@abstractproperty
def name(self):
pass

Child class:

class Child(Parent):
name = None
def __init__(self):
self.name = 'test'

Now

obj = Child()
obj.name

gives the required output of 'test'.

By doing this you can enforce a class to set an attribute of parent class and in child class you can set them from a method.
It is not a perfect solution that you require, but it is close. The only problem with this solution is you will need to define all the abstractproperty of parent as None in child class and them set them using a method.

Force one of two python class attributes to be always be assigned a value?

In the constructor, it's simple enough to raise a ValueError if they are both None or both set. The problem is later on in the code.

Following the principle of least surprise, I think you should mark the variables private and use setter methods (not property setter, plain old methods). This clearly suggests you're doing extra logic when the value is set, and it gives you an obvious place to add extra logic later if needed. Using getter property methods would be fine, though. So something like this:

class Foo(object):
def __init__(self, apple=None, orange=None):
super(Foo, self).__init__()
if apple is None and orange is None:
raise ValueError('apple and orange cannot both be None')
if apple is not None and orange is not None:
raise ValueError('apple and orange cannot both be set')

self._apple = apple
self._orange = orange

@property
def apple(self):
return self._apple

@property
def orange(self):
return self._orange

def setAppleClearOrange(self, value):
if value is None:
raise ValueError('Cannot set both to None')
self._orange = None
self._apple = value

def setOrangeClearApple(self, value):
if value is None:
raise ValueError('Cannot set both to None')
self._apple = None
self._orange = value

Yes, it's a bit verbose, but it's obvious what your intentions are, which is actually more important.

Ensure uniqueness of instance attribute in python

You can control instance creation with metaclasses (for example) and ensure that the name is unique. Let's assume that the __init__ method takes a parameter name which has no default value

class MyClass(object):

def __init__(self, name, *args, **kwargs):
self.name = name

Obviously, instances can have the same name with this. Let's use a metaclass (using compatible Python 2/3 syntax)

class MyMeta(type):
_names = set()

@classmethod
def as_metaclass(meta, *bases):
'''Create a base class with "this metaclass" as metaclass

Meant to be used in the definition of classes for Py2/3 syntax equality

Args:
bases: a list of base classes to apply (object if none given)
'''
class metaclass(meta):
def __new__(cls, name, this_bases, d):
# subclass to ensure super works with our methods
return meta(name, bases, d)
return type.__new__(metaclass, str('tmpcls'), (), {})

def __call__(cls, name, *args, **kwargs):
if name in cls._names:
raise AttributeError('Duplicate Name')

cls._names.add(name)
return type.__call__(cls, name, *args, **kwargs)

class MyClass(MyMeta.as_metaclass()):
def __init__(self, name, *args, **kwargs):
self.name = name

a = MyClass('hello')
print('a.name:', a.name)
b = MyClass('goodbye')
print('b.name:', b.name)

try:
c = MyClass('hello')
except AttributeError:
print('Duplicate Name caught')
else:
print('c.name:', c.name)

Which outputs:

a.name: hello
b.name: goodbye
Duplicate Name caught

Using the metaclass technique you could even avoid having name as a parameter and the names could be generated automatically for each instance.

import itertools

class MyMeta(type):
_counter = itertools.count()

@classmethod
def as_metaclass(meta, *bases):
'''Create a base class with "this metaclass" as metaclass

Meant to be used in the definition of classes for Py2/3 syntax equality

Args:
bases: a list of base classes to apply (object if none given)
'''
class metaclass(meta):
def __new__(cls, name, this_bases, d):
# subclass to ensure super works with our methods
return meta(name, bases, d)
return type.__new__(metaclass, str('tmpcls'), (), {})

def __call__(cls, *args, **kwargs):
obj = type.__call__(cls, *args, **kwargs)
obj.name = '%s_%d' % (cls.__name__, next(cls._counter))
return obj

class MyClass(MyMeta.as_metaclass()):
pass

a = MyClass()
print('a.name:', a.name)

b = MyClass()
print('b.name:', b.name)

c = MyClass()
print('c.name:', c.name)

Output:

a.name: MyClass_0
b.name: MyClass_1
c.name: MyClass_2

To complete the question and answering the comment about preventing a.name = b.name (or any other name already in use) one can use a descriptor based approach

class DescName(object):

def __init__(self):
self.cache = {None: self}

def __get__(self, obj, cls=None):
return self.cache[obj]

def __set__(self, obj, value):
cls = obj.__class__

if value in cls._names:
raise AttributeError('EXISTING NAME %s' % value)

try:
cls._names.remove(self.cache[obj])
except KeyError: # 1st time name is used
pass
cls._names.add(value)
self.cache[obj] = value

class MyClass(object):
_names = set()

name = DescName()

def __init__(self, name, *args, **kwargs):
self.name = name

a = MyClass('hello')
print('a.name:', a.name)
b = MyClass('goodbye')
print('b.name:', b.name)

try:
c = MyClass('hello')
except AttributeError:
print('Duplicate Name caught')
else:
print('c.name:', c.name)

a.name = 'see you again'
print('a.name:', a.name)

try:
a.name = b.name
except AttributeError:
print('CANNOT SET a.name to b.name')
else:
print('a.name %s = %s b.name' % (a.name, b.name))

With the expected output (names cannot be reused during __init__ or assignment)

a.name: hello
b.name: goodbye
Duplicate Name caught
a.name: see you again
CANNOT SET a.name to b.name

EDIT:

Since the OP favours this approach, a combined metaclass and descriptor approach which covers:

  • name class attribute as descriptoradded by the metaclass during class creation
  • name per instance initialization before the instance gets to __init__
  • name uniqueness also for assignment operations

  • storing the set and itertools.counter controlling name uniqueness inside the descriptor class which removes pollution from the class itself

import itertools

class MyMeta(type):
class DescName(object):
def __init__(self, cls):
self.cache = {None: self, cls: set()}
self.counter = {cls: itertools.count()}

def __get__(self, obj, cls=None):
return self.cache[obj]

def __set__(self, obj, value):
self.setname(obj, value)

def setname(self, obj, name=None):
cls = obj.__class__
name = name or '%s_%d' % (cls.__name__, next(self.counter[cls]))

s = self.cache[cls]
if name in s:
raise AttributeError('EXISTING NAME %s' % name)

s.discard(self.cache.get(obj, None))
s.add(name)
self.cache[obj] = name

def __new__(meta, name, bases, dct):
cls = super(MyMeta, meta).__new__(meta, name, bases, dct)
cls.name = meta.DescName(cls) # add the name class attribute
return cls

@classmethod
def as_metaclass(meta, *bases):
class metaclass(meta):
def __new__(cls, name, this_bases, d):
# subclass to ensure super works with our methods
return meta(name, bases, d)
return type.__new__(metaclass, str('tmpcls'), (), {})

def __call__(cls, *args, **kwargs):
# Instead of relying on type we do the new and init calls
obj = cls.__new__(cls, *args, **kwargs)
cls.name.setname(obj)
obj.__init__(*args, **kwargs)
return obj

class MyClass(MyMeta.as_metaclass()):
def __init__(self, *args, **kwargs):
print('__init__ with name:', self.name)

a = MyClass()
b = MyClass()
c = MyClass()

a.name = 'my new name'
print('a.name:', a.name)

try:
a.name = b.name
except AttributeError as e:
print(e)
else:
print('a.name %s == %s b.name' % (a.name, b.name))

Which outputs the expected:

__init__ with name: MyClass_0
__init__ with name: MyClass_1
__init__ with name: MyClass_2
a.name: my new name
EXISTING NAME MyClass_1

Enforcing Class Variables in a Subclass

Abstract Base Classes allow to declare a property abstract, which will force all implementing classes to have the property. I am only providing this example for completeness, many pythonistas think your proposed solution is more pythonic.

import abc

class Base(object):
__metaclass__ = abc.ABCMeta

@abc.abstractproperty
def value(self):
return 'Should never get here'

class Implementation1(Base):

@property
def value(self):
return 'concrete property'

class Implementation2(Base):
pass # doesn't have the required property

Trying to instantiate the first implementing class:

print Implementation1()
Out[6]: <__main__.Implementation1 at 0x105c41d90>

Trying to instantiate the second implementing class:

print Implementation2()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-4-bbaeae6b17a6> in <module>()
----> 1 Implementation2()

TypeError: Can't instantiate abstract class Implementation2 with abstract methods value

Getting an Exception even though class attribute is correct type

If it's a float, then type(value) != int will evaluate true, and you'll raise the exception.

Change the logic to "and" and it will work:

if type(value) != int and type(value) != float:

Prohibit class member from being set to None in Python

You'll need a @property:

class Dummy:
def __init__(self, x):
self.x = x

@property
def x(self):
return self._x

@x.setter
def x(self, value):
if value is None:
raise Exception("x cannot be None")
self._x = value

d = Dummy(8)
d.x = 16
d.x = None # Raises


Related Topics



Leave a reply



Submit