Làm thế nào xấu là bóng tên được xác định trong phạm vi bên ngoài?


207

Tôi mới chuyển sang Pycharm và tôi rất vui về tất cả các cảnh báo và gợi ý nó cung cấp cho tôi để cải thiện mã của mình. Ngoại trừ điều này mà tôi không hiểu:

This inspection detects shadowing names defined in outer scopes.

Tôi biết đó là thực tế xấu để truy cập biến từ phạm vi bên ngoài nhưng vấn đề với bóng tối phạm vi bên ngoài là gì?

Đây là một ví dụ, nơi Pycharm đưa cho tôi thông điệp cảnh báo:

data = [4, 5, 6]

def print_data(data): # <-- Warning: "Shadows 'data' from outer scope
    print data

print_data(data)

1
Ngoài ra, tôi đã tìm kiếm chuỗi "Kiểm tra này phát hiện ..." nhưng không tìm thấy gì trong trợ giúp trực tuyến của pycharm
Framester

1
Để tắt thông báo này trong PyCharm: <Ctrl> + <Alt> + s (cài đặt), Trình chỉnh sửa , Kiểm tra , " Tên bóng từ phạm vi bên ngoài ". Bỏ chọn
ChaimG

Câu trả lời:


222

Không có vấn đề lớn trong đoạn trích trên của bạn, nhưng hãy tưởng tượng một hàm có thêm một vài đối số và khá nhiều dòng mã. Sau đó, bạn quyết định đổi tên datađối số của mình thành yaddanhưng bỏ lỡ một trong những vị trí được sử dụng trong cơ thể của hàm ... Bây giờ datađề cập đến toàn cầu và bạn bắt đầu có hành vi kỳ lạ - nơi bạn sẽ rõ ràng hơn nhiều NameErrornếu bạn không có tên toàn cầu data.

Cũng cần nhớ rằng trong Python, mọi thứ đều là một đối tượng (bao gồm các mô-đun, lớp và hàm) vì vậy không có không gian tên riêng biệt cho các hàm, mô-đun hoặc lớp. Một kịch bản khác là bạn nhập hàm fooở đầu mô-đun và sử dụng nó ở đâu đó trong thân hàm. Sau đó, bạn thêm một đối số mới cho chức năng của mình và đặt tên cho nó - thật không may - foo.

Cuối cùng, các hàm và kiểu dựng sẵn cũng sống trong cùng một không gian tên và có thể bị che khuất theo cùng một cách.

Không có vấn đề gì trong số này là vấn đề nếu bạn có các chức năng ngắn, đặt tên tốt và phạm vi bảo hiểm không đáng tin cậy, nhưng tốt, đôi khi bạn phải duy trì ít hơn mã hoàn hảo và được cảnh báo về các vấn đề có thể có thể giúp ích.


21
May mắn thay, PyCharm (được OP sử dụng) có một hoạt động đổi tên rất hay, đổi tên biến ở mọi nơi mà nó được sử dụng trong cùng một phạm vi, điều này làm cho việc đổi tên ít xảy ra hơn.
wojtow

Ngoài hoạt động đổi tên của PyCharm, tôi rất thích có các điểm nhấn cú pháp đặc biệt cho các biến tham chiếu phạm vi bên ngoài. Hai cái này sẽ khiến trò chơi giải quyết bóng tối tốn thời gian này không liên quan.
Leo

Lưu ý bên lề: Bạn có thể sử dụng nonlocaltừ khóa để làm cho điểm giới thiệu bên ngoài (như trong bao đóng) rõ ràng. Lưu ý rằng điều này khác với bóng, vì nó rõ ràng không bóng các biến từ bên ngoài.
Felix D.

147

Các câu trả lời hiện đang được bình chọn và chấp nhận nhiều nhất và hầu hết các câu trả lời ở đây bỏ lỡ điểm.

Không quan trọng chức năng của bạn là bao lâu hoặc cách bạn đặt tên cho biến của mình một cách mô tả (để hy vọng giảm thiểu khả năng xảy ra xung đột tên).

Thực tế là biến cục bộ của hàm hoặc tham số của hàm xảy ra để chia sẻ tên trong phạm vi toàn cục là hoàn toàn không liên quan. Và trên thực tế, cho dù bạn chọn tên biến địa phương cẩn thận đến mức nào, chức năng của bạn không bao giờ có thể thấy trước "liệu tên mát mẻ của tôi yaddacũng sẽ được sử dụng làm biến toàn cầu trong tương lai?". Giải pháp? Đơn giản là đừng lo lắng về điều đó! Suy nghĩ đúng đắn là thiết kế chức năng của bạn để tiêu thụ đầu vào từ và chỉ từ các tham số của nó trong chữ ký , theo cách đó bạn không cần quan tâm cái gì (hoặc sẽ) trong phạm vi toàn cầu, và sau đó, bóng tối hoàn toàn không phải là vấn đề.

Nói cách khác, vấn đề đổ bóng chỉ quan trọng khi hàm của bạn cần sử dụng cùng tên biến cục bộ VÀ biến toàn cục. Nhưng bạn nên tránh thiết kế như vậy ở nơi đầu tiên. Mã của OP KHÔNG thực sự có vấn đề thiết kế như vậy. Chỉ là PyCharm không đủ thông minh và nó đưa ra cảnh báo chỉ trong trường hợp. Vì vậy, chỉ để làm cho PyCharm hài lòng và cũng làm cho mã của chúng tôi sạch sẽ, hãy xem giải pháp này trích dẫn từ câu trả lời của silyevsk để loại bỏ hoàn toàn biến toàn cục.

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()

Đây là cách thích hợp để "giải quyết" vấn đề này, bằng cách sửa / xóa thứ toàn cầu của bạn, không điều chỉnh chức năng cục bộ hiện tại của bạn.


11
Chà, chắc chắn, trong một thế giới hoàn hảo, bạn đã tạo ra một lỗi đánh máy hoặc quên một trong những thay thế tìm kiếm của bạn khi bạn thay đổi tham số, nhưng sai lầm xảy ra và đó là những gì PyCharm đang nói - "Cảnh báo - không có gì là lỗi về mặt kỹ thuật, nhưng điều này có thể dễ dàng trở thành một vấn đề "
lùn 22/2/2017

1
@dwanderson Tình huống bạn đề cập không có gì mới, nó được mô tả rõ ràng trong câu trả lời hiện được chọn. Tuy nhiên, điểm tôi cố gắng đưa ra là chúng ta nên tránh biến toàn cục, không tránh bóng tối biến toàn cầu. Cái sau bỏ lỡ điểm. Hiểu rồi? Hiểu rồi?
RayLuo

4
Tôi hoàn toàn đồng ý về thực tế rằng các hàm phải "thuần túy" nhất có thể nhưng bạn hoàn toàn bỏ lỡ hai điểm quan trọng: không có cách nào để hạn chế Python tìm kiếm một tên trong phạm vi kèm theo nếu nó không được xác định cục bộ và mọi thứ (mô-đun , hàm, lớp, v.v.) là một đối tượng và sống trong cùng một không gian tên như bất kỳ "biến" nào khác. Trong đoạn trích trên của bạn, print_dataIS là một biến toàn cục. Hãy suy nghĩ về điều đó ...
bruno Desthuilliers 17/03/2017

2
Tôi đã kết thúc chủ đề này bởi vì tôi đang sử dụng các hàm được xác định trong các hàm, để làm cho chức năng bên ngoài dễ đọc hơn mà không làm lộn xộn không gian tên toàn cầu hoặc sử dụng các tệp riêng biệt. Ví dụ này ở đây không áp dụng cho trường hợp chung đó, các biến không cục bộ không cục bộ bị che khuất.
micseydel

2
Đồng ý. Vấn đề ở đây là phạm vi Python. Không truy cập rõ ràng vào các đối tượng bên ngoài phạm vi hiện tại đang yêu cầu sự cố. Ai sẽ muốn điều đó! Thật xấu hổ vì nếu không thì Python là một ngôn ngữ được suy nghĩ khá kỹ (không chịu được sự mơ hồ tương tự trong việc đặt tên mô-đun).
CodeCabbie

24

Một cách giải quyết tốt trong một số trường hợp có thể là di chuyển mã vars + sang chức năng khác:

def print_data(data):
    print data

def main():
    data = [4, 5, 6]
    print_data(data)

main()

Đúng. Tôi nghĩ rằng một ide tốt có thể xử lý các biến cục bộ và biến toàn cục bằng cách tái cấu trúc. Mẹo của bạn thực sự giúp loại bỏ những rủi ro tiềm tàng như vậy đối với ide nguyên thủy
stanleyxu2005

5

Nó phụ thuộc vào thời gian của chức năng. Chức năng càng dài, càng có nhiều cơ hội để ai đó sửa đổi nó trong tương lai sẽ viết datasuy nghĩ rằng nó có nghĩa là toàn cầu. Trong thực tế, nó có nghĩa là địa phương nhưng vì chức năng quá dài nên họ không rõ ràng rằng có tồn tại một địa phương có tên đó.

Đối với chức năng ví dụ của bạn, tôi nghĩ rằng phủ bóng toàn cầu không tệ chút nào.


5

Làm cái này:

data = [4, 5, 6]

def print_data():
    global data
    print(data)

print_data()

3
data = [4, 5, 6] #your global variable

def print_data(data): # <-- Pass in a parameter called "data"
    print data  # <-- Note: You can access global variable inside your function, BUT for now, which is which? the parameter or the global variable? Confused, huh?

print_data(data)

47
Tôi cho một người không nhầm lẫn. Đó rõ ràng là thông số.

2
@delnan Bạn có thể không bị nhầm lẫn trong ví dụ tầm thường này, nhưng nếu các hàm khác được định nghĩa gần đó sử dụng toàn cầu data, tất cả nằm sâu trong vài trăm dòng mã thì sao?
John Colanduoni

13
@HevyLight Tôi không cần xem các chức năng khác gần đó. Tôi chỉ nhìn vào chức năng này và có thể thấy đó datalà một tên địa phương trong chức năng này , vì vậy tôi thậm chí không bận tâm đến việc kiểm tra / ghi nhớ liệu một thế giới cùng tên có tồn tại hay không , chứ đừng nói đến những gì nó chứa.

4
Tôi không nghĩ lý do này là hợp lệ, chỉ vì để sử dụng toàn cầu, bạn sẽ cần xác định "dữ liệu toàn cầu" bên trong hàm. Nếu không, toàn cầu là không thể truy cập.
CodyF

1
@CodyF False- nếu bạn không xác định, nhưng chỉ cần cố gắng để sử dụng data, nó sẽ tra cứu thông qua phạm vi cho đến khi nó tìm thấy một, vì vậy nó không tìm ra toàn cầu data. data = [1, 2, 3]; def foo(): print(data); foo()
lùn

3

Tôi thích nhìn thấy một đánh dấu màu xanh lá cây ở góc trên bên phải trong pycharm. Tôi nối các tên biến với dấu gạch dưới chỉ để xóa cảnh báo này để tôi có thể tập trung vào các cảnh báo quan trọng.

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

2

Có vẻ như mẫu mã 100% pytest

xem:

https://docs.pytest.org/en/latest/fixture.html#conftest-py-shaming-fixture-fifts

Tôi đã có cùng một vấn đề với, đây là lý do tại sao tôi tìm thấy bài đăng này;)

# ./tests/test_twitter1.py
import os
import pytest

from mylib import db
# ...

@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

Và nó sẽ cảnh báo với This inspection detects shadowing names defined in outer scopes.

Để khắc phục điều đó, chỉ cần di chuyển twittervật cố của bạn vào./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


@pytest.fixture
def twitter():
    twitter_ = db.Twitter()
    twitter_._debug = True
    return twitter_

Và loại bỏ twittervật cố định như trong./tests/test_twitter2.py

# ./tests/test_twitter2.py
import os
import pytest

from mylib import db
# ...

@pytest.mark.parametrize("query,expected", [
    ("BANCO PROVINCIAL", 8),
    ("name", 6),
    ("castlabs", 42),
])
def test_search(twitter: db.Twitter, query: str, expected: int):

    for query in queries:
        res = twitter.search(query)
        print(res)
        assert res

Điều này sẽ làm cho QA, Pycharm và mọi người hạnh phúc

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.