3

The title might not be the best description of what I'm trying to do but I'm not sure what to call this. I came across various seemingly related concepts with names like "decorators", "descriptors", and "metaclasses" but I don't know which of those approaches (if any) I should investigate further!

Given the following classes:

class AnimalGreeter(object):

    def __init__(self, greeting):
        self.greeting = greeting

    def greet(self, animal):
        print(self.greeting + ", " + animal + "!")

class MyGreeter(AnimalGreeter):

    animals = ['dog', 'parrot']

I'd like to be able to use an instance of MyGreeter like this:

greeter = MyGreeter("What's up")
greeter.parrot
# "What's up, parrot!"

Effectively, I would like it to function as if I had defined MyGreeter like this instead:

class MyGreeter(AnimalGreeter):

    @property
    def dog(self):
        self.greet('dog')

    @property
    def parrot(self):
        self.greet('parrot')

In other words, I want to dynamically define methods (dog(), etc.) with names derived from an attribute (animals) of the class (MyGreeter).

What is the best way to do this? Thanks a lot!

4
  • 2
    Just implement __getattr__(self, animal). Commented Sep 13, 2017 at 15:55
  • Cant help here, but I'm interested in your use case. Why do you need this behavior? Commented Sep 13, 2017 at 15:56
  • 1
    @FabienP, In my case AnimalGreeter is a Django model and MyGreeter is a custom mixin that handles images with configurable filenames. I want to have access to the image urls in Django's templates but Django's templating system does not permit passing arguments to instance methods. So I can't do the equivalent of model.image_url('large') in the template. But I can access instance properties in the templates, e.g., model.large_image_url. Commented Sep 13, 2017 at 16:25
  • @tino: nice to know, thanks for the explanation! Commented Sep 13, 2017 at 17:40

1 Answer 1

4

An easy way to do this would just be to implement __getattr__(), e.g.:

class MyGreeter(AnimalGreeter):
    animals = {'dog', 'parrot'}

    def __getattr__(self, animal):
        if animal in self.animals:
            return self.greet(animal)
        raise AttributeError("'{}' object unknown animal '{}'".format(type(self).__name__, animal))

>>> greeter = MyGreeter("What's up")
>>> greeter.parrot
What's up, parrot!
>>> greeter.cat
...
AttributeError: 'MyGreeter' object unknown animal 'cat'
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks! This was one of the suggestions I came across but was unsure if I should be concerned about overhead. AnimalGreeter has a lot of other attributes that are accessed frequently. Does this get executed every time I access any instance attribute?
@tino No, __getattr__ is only called if you access an attribute that "doesn't exist", i.e. it's called when an AttributeError would be thrown. Read the docs.
@Rawing, Ah, that makes perfect sense. Thanks for the link and the explanation!

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.