DEV Community

Cover image for Python starred expressions: practical challenges
Vitaly Shchurov
Vitaly Shchurov

Posted on • Updated on

Python starred expressions: practical challenges

As I promised, I'm continuing my post on how to use starred expressions, this time with more practical and complicated examples.

RUNNING AN ONLINE SHOP

Let's write a simple programme that would calculate a discount for online orders. For ordinary purchases, we'll add up all customer's discounts (loyalty programme for regulars, promo codes, etc.). For special offers like a 30% or 50% discount, we'll just choose the biggest discount, otherwise your little shop will go bankrupt :)

The thing is, you don't know how many bonuses a customer might have, but you need to process them all. And that's how we'll do it:

data = [
    ('Abraham Lincoln', 'special', 50, 5, 25, 3, 15, 'address'),
    ('George Washington', 'regular', 5, 5, 'address'),
    ('Thomas Jefferson', 'regular', 10, 3, 'address'),
    ('James Maddison', 'special', 30, 10, 10, 'address')
]

def special_offer(discounts: list) -> int:
    """ We choose the biggest discount for a special offer. """
    return max(discounts)

def regular_offer(discounts: list) -> int:
    """ If there's no special offer, we sum up the discounts. """
    return sum(discounts)

if __name__ == '__main__':
    for line in data:
        customer, tag, *discounts, _ = line
        if tag == 'special':
            percent = special_offer(discounts)
        else:  # regular
            percent = regular_offer(discounts)
        print(f'Customer {customer} is offered a discount of {percent}%')
Enter fullscreen mode Exit fullscreen mode

Let's execute the script:

Customer Abraham Lincoln is offered a discount of 50%
Customer George Washington is offered a discount of 10%
Customer Thomas Jefferson is offered a discount of 13%
Customer James Maddison is offered a discount of 30%
Enter fullscreen mode Exit fullscreen mode

Nice, but we can do better! The interesting thing here is that in a for loop we have an implicit assignment, and we don't really need to assign line to variables in a separate line. So, let's rework our last chunk a bit:

if __name__ == '__main__':
    for customer, tag, *discounts, _ in data:
        if tag == 'special':
            percent = special_offer(discounts)
        else:  # regular
            percent = regular_offer(discounts)
        print(f'Customer {customer} is offered a discount of {percent}')
Enter fullscreen mode Exit fullscreen mode

An implicit tuple assignment means that a row of variables customer, tag, *discounts is treated here as a tuple being assigned values, so you may as well type (customer, tag, *discounts) and nothing will change. It's just more comfortable not to use ().

Python's PEP 3132 offers us another example. It's a little abstract, though, but demonstrates the concept:

for a, *b in [(1, 2, 3), (4, 5, 6, 7)]:
    print(b)
Enter fullscreen mode Exit fullscreen mode

After executing, you'll get:

[2, 3]
[5, 6, 7]
Enter fullscreen mode Exit fullscreen mode

SENDING A HOLIDAY CARD

Now, we'd like to send a holiday card to our online shop customers. See how clean our code looks when using *_ instead of awkward indexes with arbitrary-sized tuples:

data = [
    ('Abraham Lincoln', 'special', 50, 5, 25, 3, 15, 'address'),
    ('George Washington', 'regular', 5, 5, 'address'),
    ('Thomas Jefferson', 'regular', 10, 3, 'address'),
    ('James Maddison', 'special', 30, 10, 10, 'address')
]

if __name__ == '__main__':
    for customer, *_, address in data:
        print(f'A card for {customer} to some {address}')
Enter fullscreen mode Exit fullscreen mode

DOING BUSINESS RESEARCH

Let's solve another problem. You're a data scientist conducting a research on salaries (or whatever) in local companies. You've got raw data strings containing a company name and impersonated data on each employee's salary there. Since each company has a different number of employees, you'd want to write code that would be able to process them all regardless of the number of the employees.

You can use a string method .split(), and extract a company name using .pop():

salaries = 'ImageInc|100000|110000|80000|60000|115000|250000|20000'
salaries = salaries.split('|')
company = salaries.pop(0)
print(company, salaries)
Enter fullscreen mode Exit fullscreen mode

You'll get:

ImageInc ['100000', '110000', '80000', '60000', '115000', '250000', '20000']
Enter fullscreen mode Exit fullscreen mode

Let's just use starred expressions:

data = 'ImageInc|100000|110000|80000|60000|115000|250000|20000'
company, *salaries = data
print(company, salaries)
Enter fullscreen mode Exit fullscreen mode

Something wrong, eh? We forgot to split it, it's a string, so each character was treated as a value. Let's fix it:

data = 'ImageInc|100000|110000|80000|60000|115000|250000|20000'
company, *salaries = data.split('|')  # unpacking a list now
Enter fullscreen mode Exit fullscreen mode

We've saved ourselves and our fellow programmers some time. Nice!

DAILY STUFF

Starred expressions are something that could be used quite often even in very trivial assignments:

nums = [1, 2, 3, 4]
# first, rest = nums[0], nums[1:]  # the hard way
first, *rest = nums  # the Python way :)
Enter fullscreen mode Exit fullscreen mode

See? No need to wonder which index goes where.

SELF-PRACTICE

If you'd like to practice on your own a little, I've got a task for you. You've got some raw data here:

raw_data = [
    (10, 100, 349, 304, 203, 402, 798, 356),
    (465, 1254, 124, 875),
    (312, 46, 25, 96, 564, 183),
    (828, 4723, 7472, 588, 127, 872, 743, 400)
]
Enter fullscreen mode Exit fullscreen mode

Using starred expressions, first print the sum of the first values from each tuple in a raw_data list. Then, do the same with all values between the first and the last in each tuple.

Now, you're fully starred prepped to use starred expressions! Hope you enjoyed my post! Please don't forget to leave a like if you did :)

If you still feel like learning some more Python tips, check out my post about elegant exception handling.

Top comments (0)