Làm thế nào để truy cập lớp bên ngoài từ lớp bên trong?


101

Tôi có một tình huống như vậy ...

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

Làm cách nào để truy cập Outerphương thức của lớp từ Innerlớp?


Tại sao anh làm điều này? Có gì sai với các mối quan hệ đồng đẳng đơn giản? Bạn đang cố gắng "che giấu" điều gì đó?
S.Lott

1
Ví dụ về trường hợp này có thể có một lớp với các lớp con cần truy cập lớp ngoài như bình thường, nhưng sau đó cần tạo một lớp khác (cấp cao nhất) bắt nguồn từ lớp đầu tiên. Trong trường hợp đó, các lớp con của lớp thứ hai sẽ cố gắng truy cập lớp cha bằng cách sử dụng self.<original_parent_name>lấy lớp ban đầu, không phải lớp mới mà chúng là lớp con từ đó . Tôi hy vọng những người đọc này có thể hình dung ra viễn cảnh khó khăn này và thấy được điểm của những câu hỏi như thế này.
Edward

1
Nhân bản đến stackoverflow.com/questions/2278426 và nó có một câu trả lời hay.
xmedeko

Câu trả lời:


67

Các phương thức của một lớp lồng nhau không thể truy cập trực tiếp các thuộc tính cá thể của lớp bên ngoài.

Lưu ý rằng không nhất thiết phải có trường hợp một thể hiện của lớp bên ngoài tồn tại ngay cả khi bạn đã tạo một thể hiện của lớp bên trong.

Trên thực tế, người ta thường khuyên không nên sử dụng các lớp lồng nhau, vì việc lồng nhau không ngụ ý bất kỳ mối quan hệ cụ thể nào giữa các lớp bên trong và bên ngoài.


1
Hmm, Python nhanh hơn Java / C ++ ... hãy xem câu trả lời của tôi bên dưới. Nếu chúng ta đang chia nhỏ những sợi tóc, điều mà chúng ta thường làm, tôi thực sự không thể cho bạn biết liệu "lớp lồng nhau trong phương thức" của tôi có tính là lớp bên trong hay không. Tại thời điểm này, tuy nhiên, tôi phải gọi gõ vịt: nếu nó làm mọi thứ một lớp bên trong có thể có thể làm ... từ một điểm Pythonic xem nó có thể là thời gian để có được chán với lông tách
mike động vật gặm nhấm

21
Tất nhiên, một lớp bên trong bao hàm mối quan hệ với lớp bên ngoài, thường liên quan đến phạm vi sử dụng ngụ ý của lớp bên trong hoặc nói cách khác là không gian tên tổ chức.
Acumenus

60

Bạn đang cố gắng truy cập cá thể lớp của Outer, từ cá thể lớp bên trong. Vì vậy, chỉ cần sử dụng factory-method để xây dựng cá thể Bên trong và truyền cá thể Bên ngoài cho nó.

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()

Đây chính xác là câu trả lời chính xác - nên được chọn là câu trả lời đúng vì nó cung cấp giải pháp cho câu hỏi của OP - cảm ơn rất nhiều!
Daniel

28

có lẽ tôi điên nhưng điều này có vẻ rất dễ dàng - vấn đề là làm cho lớp bên trong của bạn bên trong một phương thức của lớp bên ngoài ...

def do_sthg( self ):
    ...

def messAround( self ):

    outerClassSelf = self

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Ngoài ra ... "self" chỉ được sử dụng theo quy ước, vì vậy bạn có thể làm điều này:

def do_sthg( self ):
    ...

def messAround( outerClassSelf ):

    class mooble():
        def do_sthg_different( self ):
            ...
            outerClassSelf.do_sthg()

Có thể bị phản đối rằng bạn không thể tạo lớp bên trong này từ bên ngoài lớp bên ngoài ... nhưng điều này không đúng:

class Bumblebee():

    def do_sthg( self ):
        print "sthg"

    def giveMeAnInnerClass( outerClassSelf ):

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

sau đó, ở đâu đó dặm:

blob = Bumblebee().giveMeAnInnerClass()()
blob.do_sthg_different()    

thậm chí đẩy thuyền ra ngoài một chút và mở rộng lớp bên trong này (NB để có được super () hoạt động, bạn phải thay đổi chữ ký lớp của mooble thành "lớp mooble (đối tượng)"

class InnerBumblebeeWithAddedBounce( Bumblebee().giveMeAnInnerClass() ):
    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()

một lát sau

mrh1997 đã nêu ra một điểm thú vị về sự kế thừa không phổ biến của các lớp bên trong được phân phối bằng kỹ thuật này. Nhưng có vẻ như giải pháp khá đơn giản:

class Fatty():
    def do_sthg( self ):
        pass

    class InnerFatty( object ):
        pass

    def giveMeAnInnerFattyClass(self):
        class ExtendedInnerFatty( Fatty.InnerFatty ):
            pass
        return ExtendedInnerFatty

fatty1 = Fatty()
fatty2 = Fatty()

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

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

1
Điều này làm việc cho tôi. Cấu trúc này được gọi chính xác là gì? Một chức năng nhà máy? Một sự đóng cửa?
nakedfanatic

Tôi không biết nó được gọi là gì ... nhưng tôi có thể gợi ý rằng lý do tại sao các áp phích khác không nhìn thấy điều này là vì có lẽ người ta không đánh giá đầy đủ rằng hầu hết mọi thứ trong Python đều không thiêng liêng, bao gồm cả "tự "(tên tùy ý) và các lớp - họ là 'đối tượng hạng nhất', mà dường như có nghĩa là bạn có thể thao tác chúng theo những cách khá kỳ quặc
mike động vật gặm nhấm

Công việc tuyệt vời. Lấy cảm hứng từ ý tưởng giải pháp này và một chút suy nghĩ, tôi đã mở rộng các phần của câu trả lời này để tạo ra một câu trả lời khác bên dưới với một số giải thích thêm.
Edward

1
@mikerodent Vì nhận xét của bạn (về câu trả lời của tôi), tôi hiện đã chỉnh sửa câu trả lời của mình để xóa mô tả "wiki cộng đồng". Giống như bạn, tôi không có bất kỳ kiến ​​thức nào về câu trả lời của "wiki cộng đồng", vì vậy tốt hơn là bạn đã sửa lỗi của mình.
Edward

@mikerodent Ngoài ra, không có gì xúc phạm, nhưng tôi không nghĩ rằng các câu trả lời "wiki cộng đồng" có thẻ "wiki cộng đồng", chúng phải chỉ là một loại câu trả lời. Có thể sẽ có một trang trợ giúp với mô tả các câu trả lời "wiki cộng đồng" trên Stack Overflow nếu bạn tình cờ quan tâm.
Edward

4

Ý của bạn là sử dụng kế thừa, thay vì lồng các lớp như thế này? Những gì bạn đang làm không có ý nghĩa gì trong Python.

Bạn có thể truy cập Outersome_method của 's chỉ bằng cách tham chiếu Outer.some_methodtrong các phương thức của lớp bên trong, nhưng nó sẽ không hoạt động như bạn mong đợi. Ví dụ: nếu bạn thử điều này:

class Outer(object):

    def some_method(self):
        # do something

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

... bạn sẽ nhận được TypeError khi khởi tạo một Innerđối tượng, bởi vì nó Outer.some_methodsẽ nhận một Outerthể hiện làm đối số đầu tiên của nó. (Trong ví dụ trên, về cơ bản bạn đang cố gọi some_methoddưới dạng một phương thức lớp của Outer.)


1
Lý do tại sao nó có lẽ không có ý nghĩa là vì tôi đang cố ý hack. Việc thêm các phương thức tùy chỉnh vào QuerySet trong Django yêu cầu một chút mã soạn sẵn và tôi đã cố gắng tìm ra một cách thông minh để thực hiện điều đó bằng cách sử dụng python cho phép tôi tạo mẫu mã soạn sẵn và chỉ cần viết các phần thích hợp trong mã Mô hình của tôi.
T. Stone

Xin lỗi - Tôi không biết Django và vì vậy không thể đề xuất cách tạo mẫu mã soạn sẵn, nhưng có thể bạn đang tìm nhầm cây khi cố lồng các lớp của mình. Lớp Bên trong của bạn không thu được bất cứ thứ gì từ lớp Bên ngoài của bạn. Tất cả những gì lồng nó vào bên trong Outer là buộc bạn phải truy cập nó qua Outer.Inner, thay vì chỉ bên trong đơn thuần.
zenbot

3

Bạn có thể dễ dàng truy cập vào lớp bên ngoài bằng cách sử dụng metaclass: sau khi tạo lớp bên ngoài, hãy kiểm tra thuộc tính của nó cho bất kỳ lớp nào (hoặc áp dụng bất kỳ logic nào bạn cần - của tôi chỉ là một ví dụ nhỏ) và đặt các giá trị tương ứng:

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!

Sử dụng cách tiếp cận này, bạn có thể dễ dàng liên kết và tham chiếu hai lớp giữa nhau.


3

Tôi đã tạo một số mã Python để sử dụng lớp ngoài từ lớp bên trong của nó , dựa trên một ý tưởng hay từ một câu trả lời khác cho câu hỏi này. Tôi nghĩ nó ngắn gọn, đơn giản và dễ hiểu.

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}

Mã chính, "sẵn sàng sản xuất" (không có chú thích, v.v.). Hãy nhớ thay thế tất cả từng giá trị trong dấu ngoặc nhọn (ví dụ <x>) bằng giá trị mong muốn.

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>}

Giải thích về cách hoạt động của phương pháp này (các bước cơ bản):

  1. Tạo một hàm có tên _subclass_containerđể hoạt động như một trình bao bọc để truy cập vào biến self, một tham chiếu đến lớp cấp cao hơn (từ mã chạy bên trong hàm).

    1. Tạo một biến có tên _parent_classlà một tham chiếu đến biến selfcủa hàm này, mà các lớp con của _subclass_containercó thể truy cập (tránh xung đột tên với các selfbiến khác trong lớp con).

    2. Trả về lớp con / lớp con dưới dạng từ điển / danh sách để mã gọi _subclass_containerhàm có thể truy cập các lớp con bên trong.

  2. Trong __init__hàm bên trong lớp cấp cao hơn (hoặc bất cứ nơi nào khác cần thiết), nhận các lớp con được trả về từ hàm _subclass_containervào biến subclasses.

  3. Gán các lớp con được lưu trữ trong subclassesbiến cho các thuộc tính của lớp cấp cao hơn.

Một số mẹo để làm cho các tình huống dễ dàng hơn:

Làm cho mã để gán các lớp con cho lớp cấp cao hơn dễ sao chép hơn và được sử dụng trong các lớp dẫn xuất từ ​​lớp cấp cao hơn đã thay đổi chức năng của chúng __init__ :

Chèn trước dòng 12 trong mã chính:

def _subclass_init(self):

Sau đó chèn vào dòng chức năng này 5-6 (của mã chính) và thay thế dòng 4-7 bằng mã sau:

self._subclass_init(self)

Việc gán lớp con cho lớp cấp cao hơn có thể thực hiện được khi có nhiều / số lượng chưa biết của lớp con.

Thay thế dòng 6 bằng mã sau:

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

Kịch bản ví dụ về nơi giải pháp này sẽ hữu ích và nơi không thể lấy tên lớp cấp cao hơn:

Một lớp, có tên "a" ( class a:) được tạo. Nó có các lớp con cần truy cập nó (lớp cha). Một lớp con được gọi là "x1". Trong lớp con này, mã a.run_func()được chạy.

Sau đó, một lớp khác, tên là "b" được tạo ra, có nguồn gốc từ lớp "a" ( class b(a):). Sau đó, một số mã chạy b.x1()(gọi hàm con "x1" của b, một lớp con dẫn xuất). Hàm này chạy a.run_func(), gọi hàm "run_func" của lớp "a", không phải hàm "run_func" của lớp cha của nó, "b" (nếu cần), bởi vì hàm được định nghĩa trong lớp "a" được đặt để tham chiếu cho chức năng của lớp "a", vì đó là lớp cha của nó.

Điều này sẽ gây ra sự cố (ví dụ: nếu hàm a.run_funcđã bị xóa) và giải pháp duy nhất mà không cần viết lại mã trong lớp a.x1sẽ là xác định lại lớp con x1với mã cập nhật cho tất cả các lớp có nguồn gốc từ lớp "a" rõ ràng sẽ khó và không có giá trị nó.


cảm ơn vì bình luận tốt đẹp của bạn về câu trả lời của tôi. Tôi không biết gì về thẻ "câu trả lời của wiki cộng đồng" và hiện đã xóa thẻ này! Vì vậy, cảm ơn cũng đã chỉ ra điều này. Bạn có thể muốn chỉnh sửa câu trả lời của mình cho phù hợp, để tránh nhầm lẫn cho người đọc trong tương lai!
mike gặm nhấm

3

Tôi đã tìm thấy điều này .

Đã chỉnh sửa để phù hợp với câu hỏi của bạn:

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)

Tôi chắc rằng bằng cách nào đó bạn có thể viết một người trang trí cho cái này hoặc cái gì đó

liên quan: Mục đích của các lớp bên trong của python là gì?


2

Khả năng khác:

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 ()

1

Mở rộng tư duy cogent của @ tsnorri, rằng phương pháp bên ngoài có thể là một phương thức tĩnh :

class Outer(object):

    @staticmethod
    def some_static_method(self):
        # do something

    class Inner(object):
        def __init__(self):
            self.some_static_method()    # <-- this will work later

    Inner.some_static_method = some_static_method

Bây giờ dòng được đề cập sẽ hoạt động vào thời điểm nó thực sự được gọi.

Dòng cuối cùng trong đoạn mã trên cung cấp cho lớp Inner một phương thức tĩnh, một bản sao của phương thức Outer static.


Điều này tận dụng hai tính năng của Python, đó là các hàm là đối tượngphạm vi là dạng văn bản .

Thông thường, phạm vi cục bộ tham chiếu đến tên cục bộ của hàm hiện tại (về mặt văn bản).

... hoặc lớp hiện tại trong trường hợp của chúng ta. Vì vậy các đối tượng "cục bộ" trong định nghĩa của lớp Outer ( Innersome_static_method) có thể được tham chiếu trực tiếp trong định nghĩa đó.


1

Một vài năm muộn để đảng .... nhưng để mở rộng @mike rodent's câu trả lời tuyệt vời, tôi đã cung cấp ví dụ riêng của tôi dưới đây cho thấy chỉ cách linh hoạt giải pháp của mình là gì, và tại sao nó phải là (hoặc nên đã được) được chấp nhận câu trả lời.

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 # ....
)

Đầu ra:

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

Một lần nữa, một câu trả lời tuyệt vời, đạo cụ cho bạn mike!


-2

Nó quá đơn giản:

Đầu vào:

class A:
    def __init__(self):
        pass

    def func1(self):
        print('class A func1')

    class B:
        def __init__(self):
            a1 = A()
            a1.func1()

        def func1(self):
            print('class B func1')

b = A.B()
b.func1()

Đầu ra

lớp A func1

lớp B func1


2
Hmm, lưu ý câu hỏi và các từ truy cập lớp ngoài . Một sự trùng hợp vui vẻ, khá thú vị, lớp bên ngoài và bên trong của bạn đều có một phương pháp func1. Và, thú vị không kém, bạn tạo một phương thức khởi tạo Abên trong B, mà hoàn toàn KHÔNG phải là Ađối tượng lớp bên ngoài của đối tượng cụ thể đó B.
mike gặm nhấm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.