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_check
as 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 asdescriptor
added by themetaclass
during class creationname
per instance initialization before the instance gets to__init__
name
uniqueness also for assignment operationsstoring the
set
anditertools.counter
controlling name uniqueness inside thedescriptor
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
Do You Use the "Global" Statement in Python
Removing List of Words from a String
Python List Comprehension - Want to Avoid Repeated Evaluation
Concat Dataframe Reindexing Only Valid with Uniquely Valued Index Objects
Using the Multiprocessing Module for Cluster Computing
Safe Way to Parse User-Supplied Mathematical Formula in Python
Access Class Variable from Instance
How to Access the Type Arguments of Typing.Generic
What Is the Best Approach to Change Primary Keys in an Existing Django App
Thread Local Storage in Python
Installing MySQLclient in Python 3.6 in Windows
Finding a Substring Within a List in Python
Python Parsing Bracketed Blocks
How to Change Foreignkey Display Text in the Django Admin
From ... Import or Import ... as for Modules
Error Installing Psycopg2 on MACos 10.9.5