Phạm vi của các lớp lồng nhau?


116

Tôi đang cố gắng hiểu phạm vi trong các lớp lồng nhau trong Python. Đây là mã ví dụ của tôi:

class OuterClass:
    outer_var = 1
    class InnerClass:
        inner_var = outer_var

Việc tạo lớp không hoàn tất và tôi gặp lỗi:

<type 'exceptions.NameError'>: name 'outer_var' is not defined

Cố gắng inner_var = Outerclass.outer_varkhông hiệu quả. Tôi có:

<type 'exceptions.NameError'>: name 'OuterClass' is not defined

Tôi đang cố gắng truy cập tĩnh outer_var từ InnerClass.

Có cách nào để làm việc này không?

Câu trả lời:


105
class Outer(object):
    outer_var = 1

    class Inner(object):
        @property
        def inner_var(self):
            return Outer.outer_var

Điều này không hoàn toàn giống với những thứ tương tự hoạt động ở các ngôn ngữ khác và sử dụng tra cứu toàn cục thay vì xác định phạm vi quyền truy cập outer_var. (Nếu bạn thay đổi đối tượng mà tên Outerđược liên kết, thì mã này sẽ sử dụng đối tượng đó trong lần thực thi tiếp theo.)

Thay vào đó, nếu bạn muốn tất cả Innercác đối tượng có tham chiếu đến Outerbởi vì outer_varthực sự là một thuộc tính cá thể:

class Outer(object):
    def __init__(self):
        self.outer_var = 1

    def get_inner(self):
        return self.Inner(self)
        # "self.Inner" is because Inner is a class attribute of this class
        # "Outer.Inner" would also work, or move Inner to global scope
        # and then just use "Inner"

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

        @property
        def inner_var(self):
            return self.outer.outer_var

Lưu ý rằng lồng các lớp hơi không phổ biến trong Python và không tự động ngụ ý bất kỳ loại mối quan hệ đặc biệt nào giữa các lớp. Tốt hơn hết bạn không nên làm tổ. (Bạn vẫn có thể thiết lập một thuộc tính lớp trên Outerđể Inner, nếu bạn muốn).


2
Có thể hữu ích khi thêm (các) phiên bản python mà câu trả lời của bạn sẽ hoạt động.
AJ.

1
Tôi đã viết điều này với suy nghĩ 2.6 / 2.x, nhưng, nhìn vào nó, tôi thấy không có gì không hoạt động như vậy trong 3.x.

Tôi không hiểu bạn muốn nói gì trong phần này, "(Nếu bạn thay đổi đối tượng mà tên Outer bị ràng buộc, thì đoạn mã này sẽ sử dụng đối tượng đó trong lần thực thi tiếp theo.)" Bạn có thể giúp tôi hiểu được không?
batbrat

1
@batbrat có nghĩa là tham chiếu đến Outerđược tra cứu lại mỗi khi bạn làm Inner.inner_var. Vì vậy, nếu bạn rebind tên Outercho một đối tượng mới, Inner.inner_varsẽ bắt đầu trả về đối tượng mới đó.
Felipe

45

Tôi nghĩ bạn có thể đơn giản làm:

class OuterClass:
    outer_var = 1

    class InnerClass:
        pass
    InnerClass.inner_var = outer_var

Sự cố bạn gặp phải là do:

Một khối là một đoạn văn bản chương trình Python được thực thi như một đơn vị. Sau đây là các khối: mô-đun, thân hàm và định nghĩa lớp.
(...)
Phạm vi xác định khả năng hiển thị của tên trong một khối.
(...)
Phạm vi tên được xác định trong một khối lớp được giới hạn trong khối lớp; nó không mở rộng đến các khối mã của các phương thức - điều này bao gồm các biểu thức trình tạo vì chúng được triển khai bằng cách sử dụng một phạm vi hàm. Điều này có nghĩa là những điều sau sẽ không thành công:

   class A:  

       a = 42  

       b = list(a + i for i in range(10))

http://docs.python.org/reference/executionmodel.html#naming-and-binding

Điều trên có nghĩa là:
một thân hàm là một khối mã và một phương thức là một hàm, khi đó các tên được định nghĩa bên ngoài thân hàm có trong định nghĩa lớp không mở rộng đến thân hàm.

Diễn giải điều này cho trường hợp của bạn:
định nghĩa lớp là một khối mã, khi đó các tên được xác định ngoài định nghĩa lớp bên trong có trong định nghĩa lớp ngoài không mở rộng sang định nghĩa lớp bên trong.


2
Kinh ngạc. Ví dụ của bạn không thành công, xác nhận "tên toàn cầu 'a' không được xác định". Tuy nhiên, việc thay thế một cách hiểu danh sách [a + i for i in range(10)]đã liên kết thành công Ab vào danh sách mong đợi [42..51].
George

6
@George Lưu ý rằng ví dụ với lớp A không phải của tôi, nó là từ tài liệu chính thức của Python mà tôi đã cung cấp liên kết. Ví dụ này không thành công và thất bại đó là những gì muốn được hiển thị trong ví dụ này. Trong thực tế list(a + i for i in range(10))list((a + i for i in range(10)))đó là để nói list(a_generator). Họ nói rằng một bộ tạo được thực hiện với một phạm vi tương tự với phạm vi chức năng.
Eyquem

@George Đối với tôi, điều đó có nghĩa là các chức năng hoạt động khác nhau tùy theo nếu chúng nằm trong một mô-đun hoặc trong một lớp. Trong trường hợp đầu tiên, một hàm đi ra bên ngoài để tìm đối tượng được liên kết với một định danh tự do. Trong trường hợp thứ hai, một hàm, nghĩa là một phương thức, không đi ra ngoài cơ thể của nó. Trên thực tế, các hàm trong một mô-đun và các phương thức trong một lớp là hai loại đối tượng. Các phương thức không chỉ là các hàm trong lớp. Đó là ý tưởng của tôi.
Eyquem

5
@George: FWIW, cả lệnh list(...)gọi và khả năng hiểu đều không hoạt động trong Python 3. Tài liệu cho Py3 cũng hơi khác nhau phản ánh điều này. Bây giờ nó cho biết " Phạm vi của các tên được xác định trong một khối lớp được giới hạn trong khối lớp; nó không mở rộng đến các khối mã của các phương thức - điều này bao gồm các biểu thức hiểu và trình tạo vì chúng được triển khai bằng phạm vi hàm. " (Tôi nhấn mạnh ).
martineau

Tôi tò mò về lý do tại sao danh sách (Aa + i cho tôi trong phạm vi (10)) cũng không hoạt động, trong đó tôi đã sửa a thành Aa Tôi nghĩ A có thể là tên toàn cầu.
gaoxinge

20

Bạn có thể sẽ tốt hơn nếu bạn không sử dụng các lớp lồng nhau. Nếu bạn phải lồng, hãy thử điều này:

x = 1
class OuterClass:
    outer_var = x
    class InnerClass:
        inner_var = x

Hoặc khai báo cả hai lớp trước khi lồng chúng:

class OuterClass:
    outer_var = 1

class InnerClass:
    inner_var = OuterClass.outer_var

OuterClass.InnerClass = InnerClass

(Sau đó, bạn có thể del InnerClassnếu bạn cần.)


3

Giải pháp dễ dàng nhất:

class OuterClass:
    outer_var = 1
    class InnerClass:
        def __init__(self):
            self.inner_var = OuterClass.outer_var

Nó đòi hỏi bạn phải rõ ràng, nhưng không tốn nhiều công sức.


NameError: tên 'OuterClass' không được xác định - -1
Mr_and_Mrs_D

3
Vấn đề là truy cập nó từ phạm vi lớp bên ngoài - cộng với lỗi tương tự sẽ xảy ra nếu bạn gọi nó từ phạm vi tĩnh bên trong Outer. Tôi khuyên bạn nên xóa bài đăng này
Mr_and_Mrs_D

1

Trong Python, các đối tượng có thể thay đổi được truyền dưới dạng tham chiếu, vì vậy bạn có thể chuyển một tham chiếu của lớp bên ngoài cho lớp bên trong.

class OuterClass:
    def __init__(self):
        self.outer_var = 1
        self.inner_class = OuterClass.InnerClass(self)
        print('Inner variable in OuterClass = %d' % self.inner_class.inner_var)

    class InnerClass:
        def __init__(self, outer_class):
            self.outer_class = outer_class
            self.inner_var = 2
            print('Outer variable in InnerClass = %d' % self.outer_class.outer_var)

2
Xin lưu ý rằng bạn có một chu trình tham chiếu ở đây và trong một số trường hợp, trường hợp của lớp này sẽ không được giải phóng. Một ví dụ, với cPython, nếu bạn đã định nghĩa __del__ phương thức, trình thu gom rác sẽ không thể xử lý chu trình tham chiếu và các đối tượng sẽ đi vào gc.garbage. Tuy nhiên, đoạn mã trên không có vấn đề gì. Cách đối phó với nó là sử dụng một tham chiếu yếu . Bạn có thể đọc tài liệu về yếuref (2.7) hoặc yếuref (3.5)
guyarad

0

Tất cả các giải thích có thể được tìm thấy trong Tài liệu Python Hướng dẫn Python

Đối với lỗi đầu tiên của bạn <type 'exceptions.NameError'>: name 'outer_var' is not defined. Lời giải thích là:

Không có cách viết tắt để tham chiếu các thuộc tính dữ liệu (hoặc các phương thức khác!) Từ bên trong các phương thức. Tôi thấy rằng điều này thực sự làm tăng khả năng đọc của các phương thức: không có khả năng nhầm lẫn giữa các biến cục bộ và biến phiên bản khi xem qua một phương thức.

được trích dẫn từ Hướng dẫn Python 9.4

Đối với lỗi thứ hai của bạn <type 'exceptions.NameError'>: name 'OuterClass' is not defined

Khi một định nghĩa lớp được để lại bình thường (thông qua phần cuối), một đối tượng lớp được tạo.

được trích dẫn từ Hướng dẫn Python 9.3.1

Vì vậy, khi bạn thử inner_var = Outerclass.outer_var, nó Quterclassvẫn chưa được tạo, đó là lý do tại saoname 'OuterClass' is not defined

Một lời giải thích chi tiết hơn nhưng tẻ nhạt cho lỗi đầu tiên của bạn:

Mặc dù các lớp có quyền truy cập vào phạm vi bao gồm các hàm, tuy nhiên, chúng không hoạt động như các phạm vi bao quanh đối với mã được lồng trong lớp: Python tìm kiếm các hàm bao quanh cho các tên được tham chiếu, nhưng không bao giờ có bất kỳ lớp bao quanh nào. Nghĩa là, một lớp là một phạm vi cục bộ và có quyền truy cập để bao quanh các phạm vi cục bộ, nhưng nó không đóng vai trò là một phạm vi cục bộ bao quanh cho mã lồng nhau.

trích dẫn từ Learning.Python (thứ 5) .Mark.Lutz

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.