Using attrs to create dummy python π objects.
Published
Β· 4 years ago
pythonclass

At the end of this post you'll be able to understand
- How to write clean code to create classes.
- How to get human readable
__repr__
for classes. - How to create dummy objects easily for testing purposes.
Let's get started
Python π has been known for its simplicity.
Just like
You can define a class in python π in just a couple of lines of code. ππ»ππ»ππ»
class SimpleClass:
pass
class SimpleClass:
pass
Still devs figure out more ways to make it even simpler than ever before.
Let's say you've a vehicle and you want to put it to the test.
A vehicle π can have certain attributes like an id and load_capacity π° as mentioned below ππ»ππ»ππ».
class VehicleSimple:
def __init__(self, id, load_capacity):
self.id = id
self.load_capacity = load_capacity
class VehicleSimple:
def __init__(self, id, load_capacity):
self.id = id
self.load_capacity = load_capacity
You notice that it's just the class definition and you have repeated word id and load_capacity thrice π².
Isn't it too much work π€ ? Can't you do better β
Of course you can, because there's always a better way !!
ππ»ββοΈ Thanks to Hynek Schlawack for maintaining attrs library π. It further enhances the code simplicity by reducing boilerplate code.
Let's install attrs first.
pip install attrs
pip install attrs
Writing π a quick code snippet to use it. ππ»ππ»ππ»
import attr
@attr.s
class VehicleUsingAttr:
id = attr.ib()
load_capacity = attr.ib()
import attr
@attr.s
class VehicleUsingAttr:
id = attr.ib()
load_capacity = attr.ib()
Here you've done three things to put attrs into effect:
- Used
@attr.s
class decorator π. - Used
attr.ib()
as attributes' value. - Removed
__init__
method instead of typing id and load 3 times π.
Going further you'd instantiate class like this ππ»ππ»ππ».
test_vehicle_simple = VehicleSimple(id=1, load_capacity=600)
test_vehicle_attrs = VehicleUsingAttr(id=2, load_capacity=600)
test_vehicle_simple = VehicleSimple(id=1, load_capacity=600)
test_vehicle_attrs = VehicleUsingAttr(id=2, load_capacity=600)
You can see there's no difference in the way you instantiate the class.
Let's test the console outputs ππ»ππ»ππ»
print(test_vehicle_simple)
# Out: <__main__.VehicleSimple object at 0x7f8a7c973d10>
print(test_vehicle_simple)
# Out: <__main__.VehicleSimple object at 0x7f8a7c973d10>
π Gibberish π€·π»ββοΈ
__repr__
.print(test_vehicle_attrs)
# Out: VehicleUsingAttr(id=2, load_capacity=600)
print(test_vehicle_attrs)
# Out: VehicleUsingAttr(id=2, load_capacity=600)
Nice πlooking
__repr__
for humans. Isn't that amazing βΌοΈThings we achived:
- Reduced boilerplate code.
- Eased the implementation.
- A nice looking comprehensible
__repr__
.
I know you're an experienced developer and working on a project which still lacks the real world data to be fed in. So you instantiate your class and make a vehicle π object with sensible random data.
Suppose for testing purpose you need hundreds π― of such vehicle π objects. So you define two functions which randomly generates sane data. ππ»ππ»ππ»
import random
def random_vehicle_id():
return random.randint(1, 1000)
def random_load():
return random.randint(100,1000)
import random
def random_vehicle_id():
return random.randint(1, 1000)
def random_load():
return random.randint(100,1000)
Now you create your objects with the dummy πΆ data using these functions ππ»ππ»ππ»
def get_random_vehicle():
return VehicleSimple(
id=random_vehicle_id(),
load_capacity=random_load()
)
test_100_v = [get_random_vehicle() for _ in range(100)]
def get_random_vehicle():
return VehicleSimple(
id=random_vehicle_id(),
load_capacity=random_load()
)
test_100_v = [get_random_vehicle() for _ in range(100)]
Now you have 100 π― vehicles in hand for the testing purpose. We can move ahead and carry out your testing without having the real world π data in hand.
But you know attrs can make it further easy for you !
Wait what ? Yep!
Let's see how
attr.ib
's factory
argument takes callable and instantiate your vehicle with dummy data.As simple as ππ»ππ»ππ»
import attr
@attr.s
class VehicleUsingAttrs:
id = attr.ib(factory=lambda: random.randint(1,1000))
load_capacity = attr.ib(factory=lambda: random.randint(100,1000))
test_100_v = [VehicleUsingAttrs() for _ in range(100)]
import attr
@attr.s
class VehicleUsingAttrs:
id = attr.ib(factory=lambda: random.randint(1,1000))
load_capacity = attr.ib(factory=lambda: random.randint(100,1000))
test_100_v = [VehicleUsingAttrs() for _ in range(100)]
That's how you create dummy objects like a senior dev. π
Key takeaways:
@attr.s
class decorator makes the class definition simple and precise.- Gives you a nice human π€ readable
__repr__
that further helps in debugging ππ«. - Helps you to create dummy objects with using callable during testing.
- Simply you can create and test class objects on the fly βοΈ and use anywhere ππ».
For more information please refer attr's official documentation.