Inner Classes: How can I get the outer-class object at construction time?

前端 未结 7 829
死守一世寂寞
死守一世寂寞 2020-12-08 23:15

Consider the following Python (runs in 2.x or 3.x):

class Outer(object):
  pass

  class Inner(object):
    def __init__(self):
      print(\"Inner.self\", s         


        
相关标签:
7条回答
  • 2020-12-09 00:01

    You can't (in any portable, reliable or efficient way.) Nested classes have no relation to the nesting class. They have no added value, either. Instead of using nested classes, use containment of instances -- define Outer and Inner separately, and create an instance of Inner in Outer.__init__, possibly passing Outer or the Outer instance if you need it. You can make Inner a class attribute of Outer if you insist, but it will have no special access to Outer.

    0 讨论(0)
  • 2020-12-09 00:03

    This is my solution for python 3 (I'm using 3.7, don't know for earlier versions or 2.x)

    from functools import wraps
    from inspect import signature as _signature
    
    class _Bound:
        '''type of bound methodsand classes'''
        def __init__(self, callable):
            self._callable = callable
        def __get__(self, instance, cls):
            'called when instance.callable or Class.callable'
            if instance is None: # called on class, not instance
                return self._callable
    
            @wraps(self._callable)
            def retf(*a, **k):
                try: 
                    return self._callable(instance, *a, **k)
                except: # information if failed
                    # user did `self.callable(*args, **kwargs)` and failed
                    print(f'failed call of {self._callable}')
                    print(f'self:   {instance}')
                    print(f'args:   {a}')
                    print(f'kwargs: {k}')
                    raise 
            # additional wrap to expell first argument
            sig = _signature(retf)
            retf.__signature__ = sig.replace(parameters=tuple(sig.parameters.values())[1:])
    
            return retf
        def __call__(self, *a, **k):
            'Class.__dict__['BoundMethod'](instance, *a, **k) (c(), 55, 8, key=9)'
            return self._callable(*a, **k)
        pass
    
    class UnBound:
        "similar to static, only doesn't change attribute at all (except for builtin-like method making)"
        def __init__(self, callable):
            self.callable = callable
        pass
    class class_(classmethod):
        pass
    class static(staticmethod):
        pass
    
    class NestedType(type):
        '''meta to support nested classes (and other callables)
        meta to outer class'''
        @staticmethod
        def bind(dict):
            for name, attr in dict.items():
                if callable(attr) and not isinstance(attr, (class_, static)): #functions, classes, ...
                    dict[name] = _Bound(attr)
                elif isinstance(attr, UnBound):
                    dict[name] = attr.callable
        def __new__(meta, object_or_name, bases, dict):
            NestedType.bind(dict)
            return type.__new__(meta, object_or_name, bases, dict)
    
    class NestedBase(metaclass=NestedType):
        pass
    

    example

    class Outer(<note0>):
        class Inner:
            def __init__(self, self_): # I prefer self_ above outer
    

    note0: here can come NestedBase or metaclass=NestedType (and beware of meta mismatch error)

    need to know
    Outer.Inner return class module.Outer.Inner
    Outer().Inner returns bound method
    Outer.__dict__['Inner'] returns bound object _Bound...

    note1
    imports are not that important, they only change pop-up when using IDE (VScode python extension doesn't recognize it, so it displays it wrongly, but IDLE does)

    classes explained
    _Bound should not be used

    used as a decorator:
    UnBound will make old-style binding
    class_ same as classmethod
    static same as staticmethod

    NestedType metaclass to the outer class
    NestedBase base to the outer class
    only one needed per outer class

    note2
    inner class that has no decorator UnBound needs to have init that receives <Outer object> as 2nd argument
    otherwise will write (see _Bound.__get__.retf.except) and throw TypeError: Inner(?) takes ? arguments

    meta mismatch
    to see meta mismatch write class M(type): pass, class A(NestedBase): pass, class B(metaclass=M): pass, class C(A, B): pass
    solution: create submeta of both metas and, if , call NestedType.bind(dict) (as in NestedType.__new__)
    note3: overriding __new__ or NestedMeta not 1st in subclassing order (class NewMeta(NotNestedMeta, NestedMeta) ( here NestedMeta is non-strict submeta of NestedType)

    0 讨论(0)
  • 2020-12-09 00:12

    You can use a metaclass to implement a __get__ descriptor that binds the inner class to the outer one. And since you seem to be interested in only binding to a class, consider modifying the inner class in-place, unlike a function which is wrapped into a method.

    >>> class Outer(object):
        class Inner(object):
            class __metaclass__(type):
                def __get__(self, instance, owner):
                    self.owner = owner
                    return self
    
    
    >>> Outer.Inner is Outer().Inner
    True
    >>> Outer.Inner.owner is Outer
    True
    

    If you'd rather wrap the inner class via a subclass then replace the __get__ body with:

    return type(self.__name__, (self,), {'owner': owner})
    
    0 讨论(0)
  • 2020-12-09 00:12

    See Access outer class from inner class in python (https://stackoverflow.com/a/35118717/3787376)
    for a simple reliable answer I made that only uses variables, attributes, functions and classes - no special code.

    0 讨论(0)
  • 2020-12-09 00:14

    In Python 2.6, a class decorator that's also a custom descriptor matches the specs you give:

    class InnerClassDescriptor(object):
      def __init__(self, cls):
        self.cls = cls
      def __get__(self, instance, outerclass):
        class Wrapper(self.cls):
          outer = instance
        Wrapper.__name__ = self.cls.__name__
        return Wrapper
    
    class Outer(object):
      @InnerClassDescriptor
      class Inner(object):
        def __init__(self):
          print self.outer
    
    o = Outer()
    i = o.Inner()
    print 'Outer is a', type(Outer)
    print 'Inner is a', type(o.Inner)
    

    This emits:

    <__main__.Outer object at 0x82f90>
    Outer is a <type 'type'>
    Inner is a <type 'type'>
    

    just to confirm that

    o.Inner [[is]] a class object, not something weird like a closure

    as per your peculiar specs. Of course it needs to be a different class each time for reentrancy -- even in a single-threaded world, the following:

    o1 = Outer()
    o2 = Outer()
    i1 = o1.Inner
    i2 = o2.Inner
    print i1(), i2(), i1(), i2()
    

    should work cleanly, and stashing o1 vs o2 anywhere else than in the classes returned by o1.Inner vs o2.Inner (e.g., in TLS) would mean horrible results for this use.

    But then you didn't specify "o.Inner has to be exactly the same class object for every possible o that's an instance of Outer", so this code fully meets the specs you did give;-).

    0 讨论(0)
  • 2020-12-09 00:14

    You should redesign your code not to use inner classes and to explicitly pass the instance of Outer to Inner when you make it, or not to require it.

    0 讨论(0)
提交回复
热议问题