ชื่อเงาที่กำหนดไว้ในขอบเขตด้านนอกนั้นแย่แค่ไหน?


208

ฉันเพิ่งเปลี่ยนมาใช้ Pycharm และฉันมีความสุขมากเกี่ยวกับคำเตือนทั้งหมดและคำแนะนำที่ให้ฉันปรับปรุงรหัสของฉัน ยกเว้นอันนี้ซึ่งฉันไม่เข้าใจ:

This inspection detects shadowing names defined in outer scopes.

ฉันรู้ว่ามันเป็นแนวปฏิบัติที่ไม่ดีในการเข้าถึงตัวแปรจากขอบเขตด้านนอก แต่ปัญหาของ Shadowing ขอบเขตด้านนอกคืออะไร

นี่คือตัวอย่างหนึ่งที่ Pycharm ส่งข้อความเตือนให้ฉัน:

data = [4, 5, 6]

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

print_data(data)

1
นอกจากนี้ฉันค้นหาสตริง "การตรวจสอบนี้ตรวจพบ ... " แต่ไม่พบสิ่งใดในวิธีใช้แบบออนไลน์ของpycharm
Framester

1
หากต้องการปิดข้อความนี้ใน PyCharm: <Ctrl> + <Alt> + s (การตั้งค่า), เครื่องมือแก้ไข , การตรวจสอบ , " ชื่อแชโดว์จากขอบเขตภายนอก " ยกเลิกการเลือก
ChaimG

คำตอบ:


222

ไม่มีเรื่องใหญ่ในตัวอย่างข้างต้นของคุณ แต่ลองจินตนาการถึงฟังก์ชั่นที่มีอาร์กิวเมนต์เพิ่มขึ้นเล็กน้อยและโค้ดอีกไม่กี่บรรทัด จากนั้นคุณตัดสินใจที่จะเปลี่ยนชื่อdataอาร์กิวเมนต์ของคุณเป็นyaddaแต่คิดถึงหนึ่งในสถานที่ที่ใช้ในเนื้อหาของฟังก์ชั่น ... ตอนนี้dataหมายถึงทั่วโลกและคุณเริ่มมีพฤติกรรมแปลก ๆ - ที่คุณจะมีความชัดเจนมากขึ้นNameErrorถ้าคุณไม่ได้ dataมีชื่อระดับโลก

โปรดจำไว้ว่าใน Python ทุกอย่างเป็นวัตถุ (รวมถึงโมดูลคลาสและฟังก์ชั่น) ดังนั้นจึงไม่มีเนมสเปซที่แตกต่างกันสำหรับฟังก์ชั่นโมดูลหรือคลาส อีกสถานการณ์หนึ่งคือคุณนำเข้าฟังก์ชั่นfooที่ด้านบนของโมดูลของคุณและใช้ที่ใดก็ได้ในร่างกายของคุณฟังก์ชั่น จากนั้นคุณเพิ่มอาร์กิวเมนต์ใหม่ในการทำงานของคุณและตั้งชื่อมัน - โชคดี foo-

ในที่สุดฟังก์ชั่นและประเภทในตัวก็ยังอยู่ในเนมสเปซเดียวกันและสามารถเป็นเงาในแบบเดียวกัน

สิ่งเหล่านี้ไม่มีปัญหาหากคุณมีฟังก์ชั่นสั้นการตั้งชื่อที่ดีและการครอบคลุมที่เหมาะสม แต่บางครั้งคุณต้องรักษารหัสน้อยกว่าที่สมบูรณ์แบบและการเตือนเกี่ยวกับปัญหาที่อาจเกิดขึ้นอาจช่วยได้


21
โชคดี PyCharm (ตามที่ใช้โดย OP) มีการดำเนินการเปลี่ยนชื่อที่ดีมากที่เปลี่ยนชื่อตัวแปรทุกที่จะใช้ในขอบเขตเดียวกันซึ่งทำให้การเปลี่ยนชื่อข้อผิดพลาดมีโอกาสน้อยกว่า
wojtow

นอกเหนือจากการดำเนินการเปลี่ยนชื่อของ PyCharm ฉันชอบที่จะมีการเน้นไวยากรณ์พิเศษสำหรับตัวแปรที่อ้างถึงขอบเขตด้านนอก สองสิ่งนี้ควรทำให้เสียเวลาในการแก้ปัญหาเงาที่ไม่เกี่ยวข้อง
Leo

หมายเหตุด้านข้าง: คุณสามารถใช้nonlocalคำหลักเพื่อทำการอ้างอิงคะแนนภายนอก (เช่นในการปิด) อย่างชัดเจน โปรดทราบว่านี่เป็นสิ่งที่แตกต่างจากการแชโดว์เนื่องจากไม่มีตัวแปรเงาจากภายนอกอย่างชัดเจน
เฟลิกซ์ดี.

149

คำตอบที่ได้รับการโหวตมากที่สุดและเป็นที่ยอมรับและคำตอบส่วนใหญ่ที่นี่พลาดประเด็นไป

ไม่สำคัญว่าฟังก์ชั่นของคุณจะใช้งานได้นานแค่ไหนหรือคุณตั้งชื่อตัวแปรของคุณอย่างมีความหมาย (หวังว่าจะลดโอกาสที่จะเกิดการชนกันของชื่อ)

ความจริงที่ว่าตัวแปรโลคัลของฟังก์ชันหรือพารามิเตอร์ของฟังก์ชันของคุณเกิดขึ้นเพื่อใช้ชื่อร่วมกันในขอบเขตส่วนกลางนั้นไม่เกี่ยวข้องอย่างสมบูรณ์ และในความเป็นจริงไม่ว่าคุณจะเลือกชื่อตัวแปรท้องถิ่นอย่างระมัดระวังเพียงใดฟังก์ชั่นของคุณจะไม่สามารถคาดการณ์ได้ว่าyaddaจะใช้ชื่อเย็นของฉันเป็นตัวแปรทั่วโลกในอนาคตหรือไม่ " การแก้ไขปัญหา? ไม่ต้องห่วงเรื่องนั้น! ความคิดที่ถูกต้องคือการออกแบบฟังก์ชั่นของคุณให้ใช้อินพุตจากพารามิเตอร์เฉพาะในลายเซ็นด้วยวิธีนี้คุณไม่จำเป็นต้องสนใจสิ่งที่ (หรือจะเป็น) ในขอบเขตทั่วโลกจากนั้นเงาจะไม่เป็นปัญหาเลย

กล่าวอีกนัยหนึ่งปัญหาการแรเงาจะเกิดขึ้นเมื่อฟังก์ชันของคุณจำเป็นต้องใช้ตัวแปรโลคอลชื่อเดียวกันและตัวแปรกลางเท่านั้น แต่คุณควรหลีกเลี่ยงการออกแบบในตอนแรก รหัสของ OP ไม่มีปัญหาการออกแบบดังกล่าว มันเป็นเพียงแค่ PyCharm ที่ไม่ฉลาดพอและให้คำเตือนในกรณีที่ ดังนั้นเพื่อให้ PyCharm มีความสุขและทำให้รหัสของเราสะอาดดูโซลูชันนี้โดยอ้างอิงจากคำตอบของ silyevskเพื่อลบตัวแปรทั่วโลกอย่างสมบูรณ์

def print_data(data):
    print data

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

main()

นี่เป็นวิธีที่เหมาะสมในการ "แก้ปัญหา" โดยแก้ไข / ลบสิ่งที่เป็นส่วนกลางของคุณไม่ปรับฟังก์ชันท้องถิ่นปัจจุบันของคุณ


11
แน่นอนว่าในโลกที่สมบูรณ์คุณสามารถพิมพ์ผิดหรือลืมหนึ่งในการค้นหาแทนที่เมื่อคุณเปลี่ยนพารามิเตอร์ แต่มีข้อผิดพลาดเกิดขึ้นและนั่นคือสิ่งที่ PyCharm พูด - "คำเตือน - ไม่มีอะไรผิดพลาดทางเทคนิค แต่สิ่งนี้ อาจกลายเป็นปัญหาได้อย่างง่ายดาย "
dwanderson

1
@dwanderson สถานการณ์ที่คุณกล่าวถึงไม่มีอะไรใหม่มันมีคำอธิบายอย่างชัดเจนในคำตอบที่เลือกในปัจจุบัน อย่างไรก็ตามจุดที่ฉันพยายามทำคือเราควรหลีกเลี่ยงตัวแปรโกลบอลไม่ควรหลีกเลี่ยงตัวแปรโกลบอลแชโดว์ หลังพลาดจุด รับมัน เข้าใจแล้วใช่ไหม
RayLuo

4
ฉัน wholefully เห็นด้วยกับความจริงที่ว่าฟังก์ชั่นควรจะเป็น "บริสุทธิ์" เป็นไปได้ แต่คุณทั้งหมดพลาดสองจุดสำคัญ: มีวิธีที่จะ จำกัด การหลามจากการมองขึ้นชื่อในขอบเขตล้อมรอบหากยังไม่ได้กำหนดไว้ในประเทศไม่ได้และทุกอย่าง (โมดูล ฟังก์ชั่นชั้นเรียน ฯลฯ ) เป็นวัตถุและอาศัยอยู่ในเนมสเปซเดียวกันกับ "ตัวแปร" อื่น ๆ ในตัวอย่างด้านบนของคุณprint_dataคือตัวแปรทั่วโลก ลองคิดดูสิ ...
bruno desthuilliers

2
ฉันสิ้นสุดที่เธรดนี้เนื่องจากฉันใช้ฟังก์ชันที่กำหนดไว้ในฟังก์ชันเพื่อให้ฟังก์ชันภายนอกอ่านได้ง่ายขึ้นโดยไม่ต้องยุ่งกับ namespace ทั่วโลกหรือใช้ไฟล์แยกกันอย่างหนัก ตัวอย่างนี้ที่นี่ใช้ไม่ได้กับกรณีทั่วไปนั้นของตัวแปรที่ไม่ใช่แบบโลคอลที่ไม่ใช่โลคัลกำลังถูกทำเงา
micseydel

2
ตกลง. ปัญหานี่คือ Python scoping การเข้าถึงวัตถุที่อยู่นอกขอบเขตปัจจุบันอย่างไม่ชัดเจนกำลังขอให้เกิดปัญหา ใครจะต้องการมัน! ความอัปยศเพราะอย่างอื่น Python เป็นภาษาที่คิดออกมาค่อนข้างดี (ไม่ทนต่อความกำกวมในการตั้งชื่อโมดูล)
CodeCabbie

24

วิธีแก้ปัญหาที่ดีในบางกรณีอาจเป็นการย้าย vars + code ไปยังฟังก์ชันอื่น:

def print_data(data):
    print data

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

main()

ใช่. ฉันคิดว่าอุดมคติที่ดีสามารถจัดการกับตัวแปรท้องถิ่นและตัวแปรระดับโลกได้โดยการปรับโครงสร้างใหม่ เคล็ดลับของคุณช่วยกำจัดความเสี่ยงที่อาจเกิดขึ้นกับ IDE ดั้งเดิมได้อย่าง
แท้จริง

5

ขึ้นอยู่กับระยะเวลาของฟังก์ชั่น ยิ่งฟังก์ชั่นทำงานนานขึ้นโอกาสที่ใครบางคนดัดแปลงมันในอนาคตจะเขียนdataความคิดว่ามันหมายถึงทั่วโลก ในความเป็นจริงมันหมายถึงท้องถิ่น แต่เนื่องจากฟังก์ชั่นนั้นยาวมากจึงไม่ชัดเจนว่ามีชื่อท้องถิ่นนั้นอยู่

สำหรับฟังก์ชั่นตัวอย่างของคุณฉันคิดว่าเงาของโลกนั้นไม่เลวเลย


5

ทำเช่นนี้:

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
ฉันคนหนึ่งไม่สับสน เห็นได้ชัดว่ามันเป็นพารามิเตอร์

2
@delnan คุณอาจไม่สับสนในตัวอย่างเล็ก ๆ น้อย ๆ นี้ แต่ถ้าฟังก์ชั่นอื่น ๆ ที่กำหนดไว้ใกล้เคียงใช้กับทั่วโลกdataลึกลงไปในโค้ดไม่กี่ร้อยบรรทัด?
John Colanduoni

13
@HevyLight ฉันไม่จำเป็นต้องดูฟังก์ชั่นอื่นในบริเวณใกล้เคียง ฉันดูที่ฟังก์ชั่นนี้เท่านั้นและสามารถเห็นได้ว่าdataเป็นชื่อท้องถิ่นในฟังก์ชั่นนี้ดังนั้นฉันจึงไม่ต้องกังวลในการตรวจสอบ / จดจำว่ามีชื่อเดียวกันทั่วโลกอยู่หรือไม่

4
ฉันไม่คิดว่าเหตุผลนี้ถูกต้องเพียงเพราะใช้ทั่วโลกคุณจะต้องกำหนด "ข้อมูลทั่วโลก" ภายในฟังก์ชั่น มิฉะนั้นโลกไม่สามารถเข้าถึงได้
CodyF

1
@CodyF False- ถ้าคุณไม่ได้กำหนด แต่ก็พยายามที่จะใช้dataมันดูผ่านขอบเขตจนกว่าจะพบหนึ่งดังนั้นจึงไม่dataพบว่าทั่วโลก data = [1, 2, 3]; def foo(): print(data); foo()
dwanderson

3

ฉันชอบที่จะเห็นเห็บสีเขียวที่มุมขวาบนของพิชมาร์ ฉันต่อท้ายชื่อตัวแปรด้วยการขีดเส้นใต้เพียงเพื่อล้างคำเตือนนี้เพื่อให้ฉันสามารถมุ่งเน้นไปที่คำเตือนที่สำคัญ

data = [4, 5, 6]

def print_data(data_): 
    print(data_)

print_data(data)

2

ดูเหมือนว่ารูปแบบรหัส pytest 100%

ดู:

https://docs.pytest.org/en/latest/fixture.html#conftest-py-sharing-fixture-functions

ฉันมีปัญหาเดียวกันกับนี่คือเหตุผลที่ฉันพบโพสต์นี้;)

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

และมันจะเตือนด้วย This inspection detects shadowing names defined in outer scopes.

ในการแก้ไขที่เพิ่งย้ายtwitterโคมของคุณเข้า./tests/conftest.py

# ./tests/conftest.py
import pytest

from syntropy import db


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

และลบการtwitterติดตั้งเหมือนใน./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

นี่จะทำให้ QA มีความสุข, Pycharm และทุกคน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.