Home > OS >  Pythonic way to get either one of two attributes from a Python object
Pythonic way to get either one of two attributes from a Python object

Time:12-01

I want to get either one of two attributes from a Python object. If neither of those attributes is present, I want to get a default value.

For instance, I want to get either the number of apples or of oranges in a FruitBasket, and if neither is present, I want to set n_fruits to 0 (because I am not interested in other fruits).

fruit_basket = FruitBasket(bananas=5)
try:
    n_fruits = getattr(fruit_basket, "apples")
except AttributeError as err:
    n_fruits = getattr(fruit_basket, "oranges")
except AttributeError as err:
    n_fruits = 0

This code breaks if there are neither apples or oranges:

AttributeError("'FruitBasket' object has no attribute 'oranges'")

The class FruitBasket can hold only one type of fruit. There is no risk that both apples and oranges are present. Even if that was the case, I still would store in n_fruits the first type of fruit (apples, in this case).

I want an elegant Pythonic way to solve this task: I have in mind many possible solutions, but all of them are ugly and convoluted!

By the way, FruitBasket is just an example. I am actually trying to get the centroids from a scikit-learn clusterer. KMeans and MeanShift algorithms store the centroids in the 'clusters_centers_' attribute, and GaussianMixture stores it in the 'means_' attribute (for my purposes, they are the same). If neither of them is present (in case of other clustering algorithms), I want to set centroids to an empty list.

CodePudding user response:

you code breaks because you need to do add an additional try-except when there is an exception:

fruit_basket = FruitBasket(bananas=5)
try:
    n_fruits = getattr(fruit_basket, "apples")
except AttributeError as err:
    try:
        n_fruits = getattr(fruit_basket, "oranges")
    except AttributeError as err:
        n_fruits = 0

A better way would be to just set a default value to the getattr:

fruit_basket = FruitBasket(bananas=5)
n_fruits = getattr(fruit_basket, "apples", None)
if n_fruits is None:
    n_fruits = getattr(fruit_basket, "oranges", 0)

CodePudding user response:

I accepted the answer by @RoseGod, and I am using it in my code. Still, I found another solution that may be useful to somebody.

My solution may be preferable if there are more than two possible attributes: in that case, manually chaining the getattr may be error-prone. In that case, I would do something like this:

fruit_basket = FruitBasket(bananas=5)
acceptable_fruits = ["apples", "oranges", "kiwis", "pineapples"]

fruits_in_basket = set(dir(fruit_basket)).intersection(acceptable_fruits)
if fruits_in_basket:
    fruit_selected = fruits_in_basket[0]
    n_fruits = getattr(fruit_basket, fruit_selected)
else:
    fruit_selected = None
    n_fruits = 0

dir(fruit_basket) is a list containing all attributes and methods names in fruit_basket. I then get only the attributes present in acceptable_fruits with the intersection method. If the intersection is not empty, I extract the first attribute, otherwise I use the default values.

  • Related