2

I have a simple scenario. I pass a kwarg prop_name and prop_func into my class init that I want to turn into property such as:

class A:
    def __init__(self, *args, prop_name=None, prop_func=None, **kwargs):
        if prop_name is not None and prop_func is not None:
            setattr(self, prop_name, property(prop_func))
        super().__init__(*args, **kwargs)

Why cannot I then do something like:

a = A(prop_name='some_name', prop_func=lambda _: 1)
print(a.some_name)

The result is:

>>> <property object at 0x7feb3f27cf98>

However, this works:

class A:
    some_name = property(lambda _: 1)

a = A()
print(a.some_name)

>>> 1

EDIT: Possible workaround to get the same bahaviour. Please correct if there is any practical difference.

class A:
    def __init__(self, *args, prop_name=None, prop_func=None, **kwargs):
        self._prop_name = prop_name
        self._prop_func = prop_func

    def __getattr__(self, item):
        if item == self._prop_name:
            return self._prop_func(self)  # this solution does not accept arguments
        super().__getattr__(self, item)

a = A(prop_name='some_name', prop_func=lambda _: 1)
print(a.some_name)
>>> 1

EDIT 2: Explanation of why this behaviour solution is required. This behaviour is supposed to happen in a Mixin for a django view. The goal is to be able to dynamically create related views between related objects. For example:

class Country(models.Model):
    ... some attributes ...


class City(models.Model):
    country = models.ForeignKey(Country)
    .... some attributes ...

It would be very nice if, directly in the view, we could refer to the City's object generically like so:

self.parent

I've done this in the Mixin that is now shown here, it's way out of scope. However, for convenience sake, it would also be nice if we could refer to this parent object directly as the field name itself, ie:

self.continent # basically just calls self.parent

This makes the code very intuitive to read. An example of a very simplified view:

class CountryDetailView(ParentMixin, DetailView):
    model = Country
    parent_model = Continent

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['continent'] = self.continent  # equivalent to self.parent
        return context
emihir0
  • 1,030
  • 2
  • 13
  • 33
  • Where have you even defined the `some_name` attribute in your class definition?? What happens when you try `print(a.prop_name)`? – Adi219 Mar 02 '18 at 19:31
  • @Adi219 He shows the output of when he prints it – user3483203 Mar 02 '18 at 19:32
  • 1
    If you remove the `property` wrapping around `prop_func` and give it any arguments, for example `prop_func(1)` it sets the value correctly. – user3483203 Mar 02 '18 at 19:33
  • @chrisz No, he's showing the output of `print(a.some_name)`, which isn't defined... – Adi219 Mar 02 '18 at 19:34
  • @Adi219 `prop_name` is a parameter name, not an attribute of `A` – user3483203 Mar 02 '18 at 19:35
  • 3
    You can't add properties to instances, [only classes](https://docs.python.org/3/howto/descriptor.html). – Ignacio Vazquez-Abrams Mar 02 '18 at 19:38
  • @chrisz yes, but then the value is calculated at `__init__` and not when it is called. I need to value to be actually calculated (with the function) when it is called, not just set a value. – emihir0 Mar 02 '18 at 19:43
  • 1
    @Adi219 `some_name` does not need a definition, it is the indented name for the property. Calling `a.prop_name` does not make sense as the `property` is set to be accessed by `a.some_name` in the example I've provided. – emihir0 Mar 02 '18 at 19:44
  • @IgnacioVazquez-Abrams what would be the best workaround to this then? Saving `prop_name` and `prop_func` to `self` in `__init__` and then make a `__getattr__` for this case? (I'll update my question with this possible solution for you to see) – emihir0 Mar 02 '18 at 19:47
  • 1
    As a general rule, all instances of the same class should support the same interface. What is your reason for violating this principle? – chepner Mar 02 '18 at 19:50
  • @chepner There is a reason for this that would take longer to explain than the comment allows. I'll write an explanation to the original question description. – emihir0 Mar 02 '18 at 19:55

0 Answers0