The reason this behavior isn't implemented is that Tag.__contains__ checks whether the argument is a child of the tag, rather than an attribute.
soup = bs4.BeautifulSoup("""<a href="#">x</a>""")
'x' in soup.a # True
Here's the implementation:
def __contains__(self, x):
return x in self.contents
The Tag class needs to act both as a dictionary (of attributes) and as a list (of child elements). For any given piece of syntactic sugar, I could only make it work one way or the other. I generally made Tag work as a list, with exceptions like __setitem__ and __delitem__ where you're clearly treating the object as a dictionary.
Tag.attrs is a real dictionary, so as facelessuer says, anything you want to do to a tag's attributes can be done there.