如何从内部类访问外部类?

177

我有这样一种情况...

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.Outer.some_method()    # <-- this is the line in question

我怎样才能从Inner类中访问Outer类的方法?


你为什么要这样做?简单的对等关系有什么问题吗?你是在试图“隐瞒”什么吗? - S.Lott
5
这种情况的一个例子可能是有一个需要访问外部类的子类的类,就像平常一样,但是接着需要创建另一个从第一个类派生出来的顶级类。在这种情况下,第二个类的子类将尝试使用 self.<原始父类名称>来访问父级,却会获得原始的类,而不是它们所属的新类。希望读者能够想象这种困难的情况,并理解为什么会提出这样的问题。 - Edward
1
复制到https://dev59.com/HHE95IYBdhLWcg3wheNR,它有一个很好的答案。 - xmedeko
16个回答

142

您正在尝试从内部类实例访问Outer的类实例。因此,只需使用factory-method来构建Inner实例,并将Outer实例传递给它。

class Outer(object):

    def createInner(self):
        return Outer.Inner(self)

    class Inner(object):
        def __init__(self, outer_instance):
            self.outer_instance = outer_instance
            self.outer_instance.somemethod()

        def inner_method(self):
            self.outer_instance.anothermethod()

太棒了!如果你改变外部类的任何东西,内部类将相应地显示该值! - Vasco Cansado Carvalho

103

嵌套类的方法不能直接访问外部类的实例属性。

请注意,即使您创建了内部类的实例,外部类的实例也不一定存在。

事实上,通常建议不使用嵌套类,因为嵌套并不意味着内部类和外部类之间存在任何特定关系。


2
嗯,Python比Java/C++更加灵活...请看下面我的回答。如果我们要挑毛病,通常情况下,我无法确定我的“方法内嵌套类”是否算作内部类。然而,在这一点上,我必须引用鸭子类型:如果它能够完成内部类可能做的一切...从Pythonic的角度来看,现在是时候厌倦挑毛病了。 - mike rodent
50
内部类当然意味着与外部类有关系,通常与内部类的使用范围或组织命名空间有关。 - Asclepius
1
如果您按照@Kitlbast在另一个答案中建议的方式进行设置,那么它们就可以了。这样,如果您在外部类上更改变量的值并从内部类访问它,则会获取所述变量的更新值。 - Vasco Cansado Carvalho

46

也许我有点疯狂,但这似乎确实非常容易 - 关键是将你的内部类放在外部类的方法中...

def do_sthg(self):
    ...

def mess_around(self):

    outer_class_self = self

    class Mooble():
        def do_sthg_different(self):
            ...
            outer_class_self.do_sthg()

另外,"self"仅仅是一种惯例,所以你可以这样做:

def do_sthg(self):
    ...

def mess_around(outer_class_self):

    class Mooble():
        def do_sthg_different(self):
            ...
            outer_class_self.do_sthg()

可能会有反对意见认为你不能从外部类创建这个内部类...但这并不是真的:

class Bumblebee():

    def do_sthg(self):
        print "sthg"
    
    def give_me_an_inner_class(outer_class_self):

        class Mooble():
            def do_sthg_different(self):
                print "something diff\n"
                outer_class_self.do_sthg()
        return Mooble
    

然后,在几英里之外的某个地方:

blob = Bumblebee().give_me_an_inner_class()()
blob.do_sthg_different()    

甚至可以再深入一些,扩展这个内部类(注意,为了让super()起作用,你需要将Mooble的类签名更改为class Mooble(object))。

class InnerBumblebeeWithAddedBounce(Bumblebee().give_me_an_inner_class()):
    def bounce(self):
        print "bounce"
    
    def do_sthg_different(self):
        super(InnerBumblebeeWithAddedBounce, self).do_sthg_different()
        print "and more different"
    

ibwab = InnerBumblebeeWithAddedBounce()    
ibwab.bounce()
ibwab.do_sthg_different()

后来

mrh1997提出了一个关于使用这种技术传递的内部类不常见继承的有趣观点。但似乎解决方案非常直接:

class Fatty():
    def do_sthg(self):
        pass
    
    class InnerFatty(object):
        pass
            
    def give_me_an_inner_fatty_class(self):
        class ExtendedInnerFatty(Fatty.InnerFatty):
            pass
        return ExtendedInnerFatty
                
fatty1 = Fatty()
fatty2 = Fatty()

innerFattyClass1 = fatty1.give_me_an_inner_fatty_class()
innerFattyClass2 = fatty2.give_me_an_inner_fatty_class()

print (issubclass(innerFattyClass1, Fatty.InnerFatty))
print (issubclass(innerFattyClass2, Fatty.InnerFatty))

1
这对我有用。这个结构确切地叫什么?一个工厂函数?一个闭包? - nakedfanatic
3
我不知道它叫什么...但是我可以建议的是,其他帖子的作者没有看到这一点的原因可能是因为他们没有完全意识到Python中的大多数东西都是非神圣的,包括“self”(任意名称)和类 - 它们是“一等公民”,这似乎意味着你可以以相当离谱的方式操纵它们。 - mike rodent
1
@mikerodent 因为你的评论(对我的回答),我现在编辑了我的回答,删除了“社区维基”描述。像你一样,我对“社区维基”回答没有任何了解,所以很好你纠正了你的错误。 - Edward
@mikerodent,不要误会,但我认为“社区维基”回答没有“社区维基”标签,它们只是一种类型的回答。如果您感兴趣,Stack Overflow上可能会有一个关于“社区维基”回答的帮助页面。 - Edward
1
请注意,每次调用Bumblebee().giveMeAnInnerClass()时,该解决方案都会为内部类创建一个新的类型对象。这在检查继承时尤其成问题: ` class A(Bumblebee().giveMeAnInnerClass()): pass issubclass(A, Bumblebee().giveMeAnInnerClass() => False - mrh1997
显示剩余4条评论

11

我找到了这个

根据你的问题进行了修改:

class Outer(object):
    def some_method(self):
        # do something

    class _Inner(object):
        def __init__(self, outer):
            outer.some_method()
    def Inner(self):
        return _Inner(self)

我相信你可以通过编写装饰器或其他方式来解决这个问题。
相关链接:Python内部类的目的是什么?

10
几年晚了......但是为了扩展@mike rodent的优秀回答,我提供了自己的示例,展示了他的解决方案有多么灵活,并且为什么它应该是(或者应该已经是)被接受的答案。
Python 3.7
class Parent():

    def __init__(self, name):
        self.name = name
        self.children = []

    class Inner(object):
        pass

    def Child(self, name):
        parent = self
        class Child(Parent.Inner):
            def __init__(self, name):
                self.name = name
                self.parent = parent
                parent.children.append(self)
        return Child(name)



parent = Parent('Bar')

child1 = parent.Child('Foo')
child2 = parent.Child('World')

print(
    # Getting its first childs name
    child1.name, # From itself
    parent.children[0].name, # From its parent
    # Also works with the second child
    child2.name,
    parent.children[1].name,
    # Go nuts if you want
    child2.parent.children[0].name,
    child1.parent.children[1].name
)

print(
    # Getting the parents name
    parent.name, # From itself
    child1.parent.name, # From its children
    child2.parent.name,
    # Go nuts again if you want
    parent.children[0].parent.name,
    parent.children[1].parent.name,
    # Or insane
    child2.parent.children[0].parent.children[1].parent.name,
    child1.parent.children[1].parent.children[0].parent.name
)


# Second parent? No problem
parent2 = Parent('John')
child3 = parent2.Child('Doe')
child4 = parent2.Child('Appleseed')

print(
    child3.name, parent2.children[0].name,
    child4.name, parent2.children[1].name,
    parent2.name # ....
)

输出:

Foo Foo World World Foo World
Bar Bar Bar Bar Bar Bar Bar
Doe Doe Appleseed Appleseed John

再次感谢您的出色回答,麦克!


1
我担心如果您使用“pickle”将其保存,会出现无限递归的情况?(如果我有Python控制台,我会亲自测试!) - brandonscript
这个解决方案非常独特,应该要更加评估。拥有相同类别的函数Child是关键点。 - Keisuke Nagakawa 永川 圭介

4

您可以使用元类轻松地访问外部类:在创建外部类后,检查其属性字典以查找任何类(或应用您需要的任何逻辑-我的示例只是一种平凡的示例),并设置相应的值:

import six
import inspect


# helper method from `peewee` project to add metaclass
_METACLASS_ = '_metaclass_helper_'
def with_metaclass(meta, base=object):
    return meta(_METACLASS_, (base,), {})


class OuterMeta(type):
    def __new__(mcs, name, parents, dct):
        cls = super(OuterMeta, mcs).__new__(mcs, name, parents, dct)
        for klass in dct.values():
            if inspect.isclass(klass):
                print("Setting outer of '%s' to '%s'" % (klass, cls))
                klass.outer = cls

        return cls


# @six.add_metaclass(OuterMeta) -- this is alternative to `with_metaclass`
class Outer(with_metaclass(OuterMeta)):
    def foo(self):
        return "I'm outer class!"

    class Inner(object):
        outer = None  # <-- by default it's None

        def bar(self):
            return "I'm inner class"


print(Outer.Inner.outer)
>>> <class '__main__.Outer'>
assert isinstance(Outer.Inner.outer(), Outer)

print(Outer().foo())
>>> I'm outer class!
print(Outer.Inner.outer().foo())
>>> I'm outer class!
print(Outer.Inner().outer().foo())
>>> I'm outer class!
print(Outer.Inner().bar())
>>> I'm inner class!

通过这种方法,你可以轻松地将两个类绑定并相互引用。


4

我写了一些Python代码,使用了内部类中的外部类。这个好主意来自于这个问题的另一个答案。我认为它简短、简单且易于理解。

class higher_level__unknown_irrelevant_name__class:
    def __init__(self, ...args...):
        ...other code...
        # Important lines to access sub-classes.
        subclasses = self._subclass_container()
        self.some_subclass = subclasses["some_subclass"]
        del subclasses # Free up variable for other use.

    def sub_function(self, ...args...):
        ...other code...

    def _subclass_container(self):
        _parent_class = self # Create access to parent class.
        class some_subclass:
            def __init__(self):
                self._parent_class = _parent_class # Easy access from self.
                # Optional line, clears variable space, but SHOULD NOT BE USED
                # IF THERE ARE MULTIPLE SUBCLASSES as would stop their parent access.
                #  del _parent_class
        class subclass_2:
            def __init__(self):
                self._parent_class = _parent_class
        # Return reference(s) to the subclass(es).
        return {"some_subclass": some_subclass, "subclass_2": subclass_2}

主要代码,“生产就绪”(没有注释等)。记得用所需的值替换尖括号中的每个值(例如<x>)。
class <higher_level_class>:
    def __init__(self):
        subclasses = self._subclass_container()
        self.<sub_class> = subclasses[<sub_class, type string>]
        del subclasses

    def _subclass_container(self):
        _parent_class = self
        class <sub_class>:
            def __init__(self):
                self._parent_class = _parent_class
        return {<sub_class, type string>: <sub_class>}

该方法的工作原理(基本步骤)的解释:

  1. 创建一个名为_subclass_container的函数,作为访问变量self的包装器,它是对高级类的引用(从函数内部运行的代码)。

    1. 创建一个名为_parent_class的变量,它是这个函数的self变量的引用,_subclass_container的子类可以访问它(避免与子类中其他self变量的名称冲突)。

    2. 将子类/子类作为字典/列表返回,以便调用_subclass_container函数的代码可以访问其中的子类。

  2. 在高级类的__init__函数(或其他需要的地方)中,将从_subclass_container函数返回的子类接收到subclasses变量中。

  3. 将存储在subclasses变量中的子类分配给高级类的属性。

使场景更容易实现的几个技巧:

使代码更易于复制并用于从高级类派生的类中,这些类的__init__函数已更改:

在主代码的第12行之前插入:

def _subclass_init(self):

然后在此函数中插入第5-6行代码(主代码),并使用以下代码替换第4-7行代码:

self._subclass_init(self)

当有许多/未知数量的子类时,使子类指定到更高级别的类变得可能。

将第6行替换为以下代码:

for subclass_name in list(subclasses.keys()):
    setattr(self, subclass_name, subclasses[subclass_name])

这个解决方案适用的示例场景以及高级类名称不可能获得的情况:

创建一个名为“a”的类(class a:),它有需要访问它(父类)的子类。其中一个子类称为“x1”。在这个子类中,运行代码a.run_func()

然后创建另一个名为“b”的类,从类“a”派生(class b(a):)。之后,一些代码运行b.x1()(调用派生子类“b”的子函数“x1”)。该函数运行a.run_func(),调用类“a”的函数“run_func”,而不是其父类“b”的函数“run_func”(应该如此),因为在类“a”中定义的函数被设置为引用类“a”的函数,因为它是其父类。

这会导致问题(例如,如果删除了函数a.run_func),而不重写类a.x1中的代码的唯一解决方案是重新定义子类x1,并为所有派生自类“a”的类更新代码,这显然很困难且不值得。


谢谢您对我的回答发表的友好评论。我之前并不了解“社区维基回答”标签,现在已经将其移除!所以也感谢您指出这一点。为了避免给未来的读者造成困惑,您可能需要相应地编辑您的回答! - mike rodent

3
另一种可能性:
class _Outer (object):
    # Define your static methods here, e.g.
    @staticmethod
    def subclassRef ():
        return Outer

class Outer (_Outer):
    class Inner (object):
        def outer (self):
            return _Outer

        def doSomething (self):
            outer = self.outer ()
            # Call your static mehthods.
            cls = outer.subclassRef ()
            return cls ()

3

您是不是想使用继承,而不是像这样嵌套类?在Python中,您所做的事情并没有太多意义。

您可以通过在内部类方法中引用Outer.some_method来访问Outersome_method,但它不会按您期望的那样工作。例如,如果您尝试这样做:

class Outer(object):

    def some_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            Outer.some_method()

如果在初始化一个Inner对象时,你会收到一个TypeError错误,因为Outer.some_method希望接收一个Outer实例作为其第一个参数。(在上面的示例中,你基本上正在尝试将some_method作为Outer的类方法调用。)


1
可能不太合理的原因是因为我故意写得有些粗糙。在Django中添加自定义方法到QuerySet需要一些样板代码,我试图用Python想出一个聪明的方法来模板化这些样板代码,并且只需在我的模型代码中编写相关部分。 - T. Stone
抱歉 - 我不了解Django,因此无法建议模板化样板代码的方法,但您可能在尝试嵌套类时走错了方向。您的内部类并没有从外部类中获取任何东西。将其全部嵌套在外部类中只会强制您通过Outer.Inner访问它,而不是直接使用Inner。 - zenbot

2
我们可以将Outer类的self变量作为类参数传递到内部类中,在Outer init中使用传递到Inner中的Outer self初始化内部类。
class Outer:
    def __init__(self):
        self.somevalue=91
        self.Inner=self.Inner(self)
    def SomeMethod(self):
        print('This is Something from Outer Class')

    class Inner:
        def __init__(self,Outer)
            self.SomeMethod=Outer.SomeMethod
            self.somevalue=Outer.somevalue
    
        def SomeAnotherMethod(self):
            print(self.somevalue)
            self.SomeMethod()        

>>>f=Outer()
>>>f.Inner.SomeAnotherMethod() 
91
This is Something from Outer Class

现在运行此函数后,它可以正常工作。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接