Deal three Poker hands of five cards each in Python
You've got a reasonable implementation for steps 1-3.  I see no glaring issues with the code.  Perhaps, we can over-engineer some parts of it.

General

Add docstrings.

class Card

It is mutable.  I can be dealt the '6 of Spades' and by tweaking it card.rank = "Ace" all of a sudden have a better card for my hand than I was dealt.  Trick decks aside, cards should be immutable, so we could change this into a tuple to keep in from changing.  Since we want named fields, we need a namedtuple, and since we want to customize the str(card), we'll derive our own class from the namedtuple.

from collections import namedtuple

class Card(namedtuple("Card", ['rank', 'suit'])):
    __slots__ = ()   # Allows no additional fields, keeps memory footprint small

    def __str__(self):
        return f"{self.rank} of {self.suit}"

# Use like:
ace_of_spades = Card('Ace', 'Spades')


Note: I'm using the Python3.6 f-strings.  If you're not using 3.6+, you'll have to adapt it to .format() ... or upgrade ;-)

Rank &amp; Suit

We only want "Clubs", "Diamonds", "Hearts", and "Spades" as suits.
And we only want "Ace", "2", "3" ... "10", "Jack", "Queen", and "King" as ranks.

Perhaps we might want an Enum for suit, and (maybe) another Enum for rank.

from enum import Enum

Suit = Enum('Suit', 'Clubs Diamonds Hearts Spades')
Rank = Enum('Rank', 'Ace Two Three Four Five Six Seven Eight Nine Ten Jack Queen King')

card = Card(Rank.Ace, Suit.Spades)
print(card)


Ew.  This prints "Rank.Ace of Suit.Spades".  We can fix this by changing the __str__(self) function of Card to include the .name of the rank and suit fields:

    def __str__(self):
        return f"{self.rank.name} of {self.suit.name}"


Much better.

But do we want to do this?  It restricts the possible ranks and suits of our cards.
If you wanted a Tarot deck, we might want the Cups, Wands, Pentacles and Swords for our suits.  There are other specialty decks too, so maybe this is too restrictive.  In which case, feel free to ignore this whole section.  But if so, then add something like:

SUITS = ['Clubs', 'Hearts', 'Diamonds', 'Spades']


so you can iterate over a named constant, instead of needing to spell out the valid values each time you need them.

class Deck

What is a deck of cards?  Is it always 52 standard playing cards?  You can shuffle a deck and deal from a deck.  But you can also shuffle two decks together and get 104 cards to deal from (perhaps you're playing Kanasta!  No, wait, you'd need the Jokers too).  What would you call two decks of cards mixed together?  Is it still a deck?  Maybe you want to pass some creation function into the Deck initializer, to control how it is created, instead of always building a deck of standard playing cards.  Again, just a thought.

With the Enums created above, Deck.build() can be simplified:

def build(self):
    self.cards = [ Card(rank, suit) for rank in Rank for suit in Suit ]


Shuffling the cards: def shuffle(self, num=1).  What is the num for?  It isn't used.

Dealing the cards.  Ok.  It deal can deal 3 player a hand of 5 cards each.  Good.  Now, player #1 wants to draw a card ... and they get ... uhm ... one of their first cards again???  The deal() method needs to keep track of which cards you've already distributed, and which remain in the deck.



Is a hand of cards just a list of 5 cards?  If so, why isn't a Deck just a list of 52 cards?  Perhaps you need to deal the cards into a Hand class.  The Hand class would be a good container for evaluating the hand, discarding cards and adding additional drawn cards to, etc.