#! Ramblings
of an autodidact...
#! Ramblings
More advanced Dataclass sorting

A useful tip for making your dataclasses more useful

More advanced Dataclass sorting

While checking my email this morning I came across another useful Pybites tip.

dataclass-order

It was a great little tip, but I wondered, "what if I wanted to sort by bite level?"

Pretty up the output

I know it's just an example code, but I like to make my output a bit more readable. A simple loop of the results is all it takes:

for bite in sorted(bites):
    print(bite)

Output:

Bite(number=11, title='Enrich a class', level=<BiteLevel.ADVANCED: 4>)
Bite(number=21, title='Query a nested DS', level=<BiteLevel.BEGINNER: 2>)
Bite(number=25, title='No promo twice', level=<BiteLevel.INTERMEDIATE: 3>)

Ah, much better!

Using a sort_index

As you can see, the bites are being sorted by their number, but I want to see them sorted by difficulty. The first thing I did was add a sort_index to the dataclass and set it to the property that I wanted to sort by, level:

@dataclass(order=True)
class Bite:
    sort_index: int
    number: int
    title: str
    level: int = BiteLevel.BEGINNER

    def __post_init__(self):
        self.sort_index = self.level

Output:

Traceback (most recent call last):
  File "enum_play.py", line 28, in <module>
    Bite(21, "Query a nested DS"),
TypeError: __init__() missing 1 required positional argument: 'title'

Uh oh! Although it's complaining about a missing title, the problem is actually that it's looking for the sort_index.

Dataclasses field(init=False)

I don't want to initialize that variable, so I make the following change to the Bite class:

from dataclasses import dataclass, field
...

class Bite:
    sort_index: int = field(init=False)
    number: int
    ...

We first import field from dataclasses and then set it to init=False in order to prevent that variable from being initialized.

Bite(sort_index=<BiteLevel.BEGINNER: 2>, number=21, title='Query a nested DS', level=<BiteLevel.BEGINNER: 2>)
Bite(sort_index=<BiteLevel.INTERMEDIATE: 3>, number=25, title='No promo twice', level=<BiteLevel.INTERMEDIATE: 3>)
Bite(sort_index=<BiteLevel.ADVANCED: 4>, number=11, title='Enrich a class', level=<BiteLevel.ADVANCED: 4>)

Um, ok better but I don't want to see sort_index being displayed in the output.

Dataclasses field(repr=False)

In order to prevent sort_index from being displayed, we will have to set the repr to False.

class Bite:
    sort_index: int = field(init=False, repr=False)
    ...

Output:

Bite(number=21, title='Query a nested DS', level=<BiteLevel.BEGINNER: 2>)
Bite(number=25, title='No promo twice', level=<BiteLevel.INTERMEDIATE: 3>)
Bite(number=11, title='Enrich a class', level=<BiteLevel.ADVANCED: 4>)

Much better!

Dataclasses frozen

Looking much better! But what if I wanted to make the dataclass so that it could not be changed in the code? That's where frozen comes in:

@dataclass(order=True, frozen=True)
class Bite:
    sort_index: int = field(init=False, repr=False)
    ...

Output:

Traceback (most recent call last):
  File "enum_play.py", line 25, in <module>
    Bite(11, "Enrich a class", BiteLevel.ADVANCED),
  File "<string>", line 6, in __init__
  File "enum_play.py", line 21, in __post_init__
    self.sort_index = self.level
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'sort_index'

Ugh, well at least we know it works!

Setting class variables with setattr

It's complaining because we are setting the index_value in __post_init__. In order to get around it, we'll have to set that value a different way.

...

    def __post_init__(self):
        object.__setattr__(self, "sort_index", self.level)

Output:

Bite(number=21, title='Query a nested DS', level=<BiteLevel.BEGINNER: 2>)
Bite(number=25, title='No promo twice', level=<BiteLevel.INTERMEDIATE: 3>)
Bite(number=11, title='Enrich a class', level=<BiteLevel.ADVANCED: 4>)

There we go!

Conclusion

As you can see, working with dataclasses isn't that hard at all and will save you from having to type too much boilerplate code.

Final code:

from dataclasses import dataclass, field
from enum import IntEnum


class BiteLevel(IntEnum):
    BEGINNER = 2
    INTERMEDIATE = 3
    ADVANCED = 4


@dataclass(order=True, frozen=True)
class Bite:
    sort_index: int = field(init=False, repr=False)
    number: int
    title: str
    level: int = BiteLevel.BEGINNER

    def __post_init__(self):
        object.__setattr__(self, "sort_index", self.level)


bites = [
    Bite(11, "Enrich a class", BiteLevel.ADVANCED),
    Bite(21, "Query a nested DS"),
    Bite(25, "No promo twice", BiteLevel.INTERMEDIATE),
]

for bite in sorted(bites):
    print(bite)

If you would like to learn more about Dataclasses, check out the documentation.


Benchmarking Python Code

How much faster is one implementation compared to another?

Need to compare which code is faster, read on to find out how A friend of mine, Mridu Bhatnagar, has been posting some really cool articles explaining how she's gone about solving some LeetCode challenges. They are all interesting, but due to time constraints, I have only attempted to see if I can do a couple of them. Move Zeroes One of the first that I attempted to do was Move Zeroes. I thought that I was...

Read More


Advanced type annotations

Some concrete examples on how to type hint more advanced class objects

How to add type annotations to Python dataclasses During the COVID19 outbreak, a lof of companies are either giving away free books, tutorials, or access their training material. One such company is Pluralsight. For the whole month of April 2020, they have given everyone free access to their complete library! The content is just awesome, some of the best I've seen. I have been working through their Python - Beyond the Basics when I came across their section on...

Read More


Cleaning up Anaconda environment.yml files

Cleaning up Anaconda exported environment files is pretty tedious work

Recreating virtual environments with Anaconda I love working with Anaconda. It makes things so much easier. I enjoy using it so much that I even wrote a guest post article on PyBit.es about my workflow. It goes into much more detail on the subject, but I'll cover a few basics here. With that being the case, there is one aspect of it that I really hate and that is creating the environment.yml project file for distribution. Table of Contents What...

Read More


Testing ABC's with abstract methods with Pytest

What I had to do to get 100% coverage on my tests

So you want to test ABC's with pytest I was working on writing a new code challenge for Codechalleng.es the other day. It's based on inheritance and composition and uses Abstract Base Classes to define the interfaces that should be implemented in classes derived from it. I was just finishing up with the tests and everything was passing. Then when I checked the code coverage on it, I saw that it was complaining about my Site...

Read More


Pytest fixtures with tear down

Figured out how to tear down a pytest fixture

Setting up pytest fixtures with teardown code Today after listening to Brian Okken's Test & Code podcast with Anthony Shaw, I was reminded that I wanted to install Anthony's Python Security Plugin for JetBrain's PyCharm IDE. That was a breeze to install and it just worked out of the box. I fired it up and it automatically checked my code and called it good. I then started to write some tests and while checking...

Read More


Into the cryptography rabbit hole

Learning about how to encrypt files

Venture Into Cryptography with PyCryptodome Learning about how to encrypt files As dumb as it is, I have an unsecured text file that I use to keep track of all my username and passwords for all of the sites that I frequent. In order to not be so "hackable", I've decided to encrypt the file. I had recently written a couple of coding challenges for Pybites Code Challenges site, using the cryptography module. One was a coding...

Read More


Receive Updates

ATOM