Scribbling

Python: Interfaces 본문

Computer Science/Python

Python: Interfaces

focalpoint 2022. 4. 18. 15:20

 

What is interface? Interface is a set of public methods. In python, 'X object', 'X protocol' and 'X interface' have the same meaning.

 

In Python, protocol is pretty dynamic. In below example, FrenchDeck class does not inherit any class (other than default 'object' class). However, the class has sequential protocol as it has two magic methods (__len__, __getitem__). Likewise, in Python, object's data type does not matter to the Python Interpreter if the object has valid set of protocols.

Card = namedtuple('Card', ['rank', 'suit'])

class FrenchDeck:
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = ['Spades', 'Diamonds', 'Hearts', 'Clubs']

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in FrenchDeck.suits for rank in FrenchDeck.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, index):
        return self._cards[index]

 

However, what should we do when we want to limit a certain protocol/interface to certain data types? In that case, 'abc' or 'Abstract Base Class' can be very helpful. 

We can either inherit pre-defined abcs from collections module or create one by ourselves. However, the first is usually what we should choose.

 

To make 'FrenchDeck' class properly function with 'random.shuffle', it inherits collections.MutableSequence. Pros and Cons of this are as below.

* Pros: Methods for MutableSequence are all available now. (append, reverse, extend, pop, remove, ...)

* Cons: Need to implement __delitem__ and insert methods as they are absract functions.

import collections

Card = collections.namedtuple('Card', ['rank', 'suit'])

class FrenchDeck(collections.MutableSequence):
    ranks = [str(n) for n in range(2, 11)] + list('JQKA')
    suits = ['Spades', 'Diamonds', 'Hearts', 'Clubs']

    def __init__(self):
        self._cards = [Card(rank, suit) for suit in FrenchDeck.suits for rank in FrenchDeck.ranks]

    def __len__(self):
        return len(self._cards)

    def __getitem__(self, index):
        return self._cards[index]

    def __setitem__(self, position, value):
        self._cards[position] = value

    def __delitem__(self, position):
        del self._cards[position]

    def insert(self, position, value):
        self._cards.insert(position, value)


deck = FrenchDeck()
import random
random.shuffle(deck)

 

Pre-defined abc are in collections.abc and numbers. If you need to check if an object is int type, isinstance(x, numbers.Integral) would work.

 

Although creating an ABC is not recommended, we will now make one here to practice.

import abc

class Tombola(abc.ABC):

    @abc.abstractmethod
    def load(self, iterable):
        """
        add iterable's elements
        """

    @abc.abstractmethod
    def pick(self):
        """
        pick one randomly and remove it from the list
        if empty, raise 'LookupError'
        """

    def loaded(self):
        """
        True if there's at least one element, else False
        """
        return bool(self.inspect())

    def inspect(self):
        """
        return sorted tuple with current elements
        """
        items = []
        while True:
            try:
                items.append(self.pick())
            except LookupError:
                break
        self.load(items)
        return tuple(sorted(items))

 

import random

class BingoCage(Tombola):
    
    def __init__(self, items):
        self._randomizer = random.SystemRandom()
        self._items = []
        self.load(items)
        
    def load(self, items):
        self._items.extend(items)
        self._randomizer.shuffle(self._items)
        
    def pick(self):
        try:
            return self._items.pop()
        except IndexError:
            raise LookupError('pick from empty BingoCage')
    
    def __call__(self):
        self.pick()
from random import randrange

@Tombola.register
class TomboList(list):
    
    def pick(self):
        if self:
            position = randrange(len(self))
            return self.pop(position)
        else:
            raise LookupError('pop from empty TomboList')
    
    load = list.extend
    
    def loaded(self):
        return bool(self)
    
    def inspect(self):
        return tuple(sorted(self))
print(issubclass(TomboList, Tombola))
print(isinstance(TomboList(range(100)), Tombola))

 

 

Summary:

Use pre-defined ABCs or register data types to them and use isinstance() to inspect data types.

'Computer Science > Python' 카테고리의 다른 글

Python: Operator Overloading  (0) 2022.04.20
Python: Inheritance  (0) 2022.04.19
Python: ABC Class  (0) 2022.04.07
Python: Sequence Protocol  (0) 2022.04.06
Python: Pythonic Object  (0) 2022.04.05