0

I have the following python code that converts an object into a view-like mapping.

from collections.abc import Mapping

class ObjView(Mapping):
    def __init__(self, obj):
        self.obj = obj

    def __contains__(self, key):
        return key in dir(self.obj)
        #return hasattr(self.obj, key)  # Better, see the answers.

    def __getitem__(self, key):
        return getattr(self.obj, key)

    def __len__(self):
        return len(dir(self.obj))
    
    def __iter__(self):
        return iter(dir(self.obj))

The use-case I have in mind is pattern-matching class elements:

class Bird():
    ...
    def type(self):
        match ObjView(self):
            case {'quacks': True}:
                return 'Duck'
            case {'wings': [w1, w2, w3], 'flies': False, 'colors': ['black', 'white']}:
                return 'Three-winged penguin'
            ...

The good thing is that an AttributeError is raised if the key is not an attribute, instead of the KeyError that gets caught in dict pattern-matching. I know the example is a bit silly, but in my situation a lot of the attributes are tuples of length 0 to 3, and I want to pattern-match them.

Question. Can I achieve this type of pattern-matching behaviour using builtins instead of creating a custom ObjView class?

2
  • 2
    Why not use class patterns like case Bird(quacks=True)? Commented Mar 11 at 13:46
  • @chepner Thanks! I did not know that was possible. Commented Mar 11 at 14:09

2 Answers 2

2

For the most part, you are re-implementing class patterns.

class Bird():
    ...
    def type(self):
        match self:
            case Bird(quacks=True):
                return 'Duck'
            case Bird(wings=[_, _, _], flies=False, colors=['black', 'white']):
                return 'Three-winged penguin'
            ...

Note that pattern-matching, as provided by the match statement, is not always a replacement for an ordinary if-elif statement, though guards can help. For instance, if you want a bird with at most three wings, instead of exactly three wings, you can do something like

case Bird(wings=list(), flies=False, colors=['black', 'white']) if len(wings) <= 3:
Sign up to request clarification or add additional context in comments.

4 Comments

There is a difference, though: class patterns don't raise AttributeError when the attribute is not found. My code checker also does not warn me, so this may lead to subtle bugs. But my situation is simple enough to ignore this.
If Bird(...) matches but, say, quack doesn't exist, that's more likely a problem with how Bird is defined than it is a problem with your pattern.
Even the simple class Bird(): pass has this problem: match Bird(): ; case Bird(quaks = False): ; print("The pattern above should raise an error.") ; case Bird(): ; print("But instead this is printed.") I will try tomorrow using __slots__...
Again, that's more a problem of usage: Bird() doesn't have a quacks attribute, so the pattern will not match. Patterns aren't expressions: you aren't doing "real" attribute lookup. It's not an error to look for an attribute that doesn't exist; it's just a failed match.
1

Yes,

This is perfectly valid.

You might however prefer to use hasattr instead of dir in __contains__, as the later will serialize all of one objects attributes on a list, and them match your your attribute linearly.

Otherwise it can be more or less specialized - for example, it might be useful not to expose any attributes starting with a _ , so that the "viewed" class can have internal attributes. (this can be accomplished with if statements in your __contains__, __getitem__ and __iter__.)

    def __contains__(self, key):
        return hasattr(self.obj, key)
   

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.