2

Is it possible to access the 'owner' class inside a descriptor during the __init__ function of that descriptor, without passing it in manually as in this example?

class FooDescriptor(object):
    def __init__(self, owner):
        #do things to owner here
        setattr(owner, 'bar_attribute', 'bar_value')


class BarClass(object):
    foo_attribute = FooDescriptor(owner=BarClass)
Steven Kampen
  • 631
  • 4
  • 13
  • 2
    Why `setattr(owner, 'bar_attribute', 'bar_value')` instead of `owner.bar_attribute = 'bar_value'`? –  May 21 '11 at 18:43
  • 1
    I am pretty sure *No* (without call-stack magic, which I would expect to see in a response). There is nothing special about invoking/creating `FooDecoractor` like that. The Pythonic way is generally "to be explicit". –  May 21 '11 at 18:48
  • For call-stack magic, see [SO: How to get the callers method name?](http://stackoverflow.com/questions/2654113/python-how-to-get-the-callers-method-name-in-the-called-method) –  May 21 '11 at 18:51
  • 2
    On closer inspection, I'm not even able to pass in the owning class as a reference since it isn't yet defined at that point. I'll have to find another solution. Thanks all of you. – Steven Kampen May 21 '11 at 19:15

2 Answers2

4

Since Python 3.6, you can use the __set_name__ special method:

class FooDescriptor(object):
    def __set_name__(self, owner, name):
        owner.foo = 42

class BarClass(object):
    foo_attribute = FooDescriptor()

# foo_attribute.__set_name__(BarClass, "foo_attribute") called after class definition

__set_name__ is automatically called on all descriptors in a class immediately after the class is created. See PEP 487 for more details.

Vash3r
  • 41
  • 2
4

One way to do something like that is with a metaclass. Just make sure it's really what you want, and don't just copy blindly if you don't understand how it works.

class Descriptor(object):
    pass

class Meta(type):
    def __new__(cls, name, bases, attrs):
        obj = type.__new__(cls, name, bases, attrs)
        # obj is now a type instance

        # this loop looks for Descriptor subclasses
        # and instantiates them, passing the type as the first argument
        for name, attr in attrs.iteritems():
            if isinstance(attr, type) and issubclass(attr, Descriptor):
                setattr(obj, name, attr(obj))

        return obj

class FooDescriptor(Descriptor):
    def __init__(self, owner):
        owner.foo = 42

class BarClass(object):
    __metaclass__ = Meta
    foo_attribute = FooDescriptor # will be instantiated by the metaclass

print BarClass.foo

If you need to pass additional arguments, you could use e.g. a tuple of (class, args) in the place of the class, or make FooDescriptor a decorator that would return a class that takes only one argument in the ctor.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • Probably should rename last argument passed to `__new__()` something other than `dict`... – martineau May 21 '11 at 19:49
  • @martineau: Well, *could*, maybe to `ns`; it's `dict` because it corresponds to the type's `__dict__`, though. Hiding built-in `dict` is not that terrible and pretty much irrelevant here. – Cat Plus Plus May 21 '11 at 19:54
  • `classdict` would be a good name. The real point is when `dict` is seen in *any* code, IMHO it ought mean the built-in -- regardless of whether the built-in is currently being used or even likely to ever be. Clever answer in any case. +1 – martineau May 21 '11 at 20:07