Chúng ta có thể sử dụng chức năng nhập trong Python không?

Python là một ngôn ngữ khá thông thường; . Hầu hết thời gian nếu bạn có thể làm điều gì đó trong một hàm, thì bạn có thể làm điều đó trong phạm vi toàn cầu hoặc trong định nghĩa lớp hoặc ngược lại. Có một ngoại lệ cho from .. import *, hoạt động ở phạm vi toàn cục nhưng không hoạt động bên trong một hàm. Lý do hóa ra lại cho chúng ta biết điều gì đó thú vị về nội bộ Python

Bắt đầu với một số ví dụ. bạn có thể thực thi mã tùy ý trong khi xác định một lớp

class Foo:
    print["defining foo"]
 
    def method[self]:
        return 1

Tương tự như vậy, bạn có thể định nghĩa các lớp hoặc nhập các mô-đun từ bên trong một hàm

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]

Loại điều này đôi khi hữu ích, và đôi khi khó hiểu. Nhưng dù tốt hay xấu, đó là một phần triết lý của Python. Khi các tính năng mới được thêm vào Python, chúng có xu hướng duy trì kiểu đều đặn này

Điều khiến mọi thứ trở nên kỳ lạ hơn là có [ít nhất] một điều bạn không thể làm trong phạm vi chức năng mà bạn có thể làm trong phạm vi toàn cầu. sử dụng import *

def do_sqrt[x]:
    from math import *
    return sqrt[x]

đưa ra lỗi

SyntaxError: import * only allowed at module level

Lý do tại sao hóa ra lại thú vị

Tra cứu trong phạm vi toàn cầu

Tôi đã giải thích ở đâu đó rằng phạm vi toàn cầu là một lệnh và rằng

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
0 bổ sung cho lệnh đó. Khi bạn nhập một mô-đun ở phạm vi toàn cầu, Python sẽ tạo một mục nhập mới trong chính tả toàn cầu với tên của mô-đun bạn vừa nhập [hoặc bí danh của nó nếu bạn đã thực hiện
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
1]

Khi bạn làm

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
2, điều gì đó hơi phức tạp hơn sẽ xảy ra. Python tìm trong mô-đun đã nhập và thêm tất cả các thành viên toàn cục của mô-đun đó làm thành viên của mô-đun toàn cầu hiện tại. Điều quan trọng là nó chỉ thêm những thành viên tồn tại vào thời điểm quá trình nhập được thực hiện. Nội dung của một mô-đun thay đổi khi mô-đun được xử lý, bằng cách nhập các mô-đun con và định nghĩa các lớp và hàm. Quá trình import * đóng băng chế độ xem của mô-đun tại thời điểm quá trình nhập diễn ra. Đây là nguồn gây nhầm lẫn thường xuyên cho những người mới sử dụng quá mức import * và sau đó giới thiệu các phụ thuộc vòng tròn

Để nhắc lại một điểm tôi đã thực hiện ở nơi khác. điều này có nghĩa là khi bạn sử dụng một tên [biến/lớp/mô-đun/hàm] trong phạm vi toàn cầu, bạn đang thực hiện tra cứu trong một lệnh. Python lấy tên biến, coi nó như một chuỗi và tra cứu giá trị trong các giá trị toàn cục dict

Điều này là tốt đẹp và đơn giản và thường xuyên. Nhưng nó cũng chậm

Tra cứu trong phạm vi chức năng

Để xem những gì diễn ra bên trong một chức năng, chúng ta sẽ cần thực hiện một số thao tác tháo gỡ bytecode. Xem tại đây nếu bạn cần một số thông tin cơ bản về Python bytecode

Thực hiện một chức năng đơn giản như thế này

def add_multiply[a, b, c]:
    return [a + b] * c

Và tháo rời nó

________số 8_______

Nếu bạn đã quen thuộc với những kiến ​​thức cơ bản về bytecode thì điều này sẽ trông khá quen thuộc. Tải

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
5 và
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
6 vào ngăn xếp, sau đó thêm hai phần tử ngăn xếp trên cùng. Tải
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
7 vào ngăn xếp, sau đó nhân hai phần tử trên cùng của ngăn xếp. Sau đó trả lại đỉnh của ngăn xếp

Nhìn kỹ hơn vào tải, mặc dù. Hướng dẫn là

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
8, và theo các tài liệu thì hướng dẫn này

Đẩy giá trị được liên kết với

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
9 vào ngăn xếp

def do_sqrt[x]:
    from math import *
    return sqrt[x]
0 là đối số nguyên được cung cấp cho hướng dẫn. trong hướng dẫn

            2 LOAD_FAST                1 [b]

2 là phần bù từ khi bắt đầu hàm. 1 là đối số được đưa ra cho lệnh

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
8.
def do_sqrt[x]:
    from math import *
    return sqrt[x]
2 không thực sự tồn tại trong mã, nó chỉ là thứ mà trình dịch ngược in ra để giúp người đọc

Vì vậy, trong trường hợp này, hướng dẫn “đẩy giá trị được liên kết với

def do_sqrt[x]:
    from math import *
    return sqrt[x]
3 vào ngăn xếp”

Hãy nhìn vào

def do_sqrt[x]:
    from math import *
    return sqrt[x]
4

>>> print[add_multiply.__code__.co_varnames]
['a', 'b', 'c']

Vì vậy, chúng tôi đang tải "giá trị được liên kết với

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
6", nghe giống như những gì chúng tôi muốn. Nhưng nó đặt ra câu hỏi “giá trị gắn liền với” ở đây nghĩa là gì

Tôi nghĩ cách nghĩ về nó là xem một chức năng như thế này

Hàm chúng tôi đã xác định có ba nhóm,

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
5,
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
6 và
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
7. Chúng là một phần của chức năng giống như mã, và chúng được xác định khi chức năng được biên dịch [và không thể thêm hoặc xóa sau đó]. Khi chúng ta có đoạn mã có nội dung như “thêm
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
5 vào
def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
6“, Python có thể biến nó thành “thêm nội dung của nhóm thứ nhất vào nội dung của nhóm thứ hai”. Vì các nhóm này luôn ở cùng một vị trí và chúng được gọi trực tiếp theo số, nên việc tra cứu chúng sẽ nhanh hơn nhiều so với việc tìm thành viên trong từ điển

Một hàm có thể có bất kỳ số lượng nào trong số các nhóm này, nhưng số lượng này được xác định khi hàm được biên dịch và không bao giờ thay đổi

Điều này hoạt động vì mã của hàm được biên dịch cùng một lúc và không thể thêm các biến mới sau khi được biên dịch. Hãy suy nghĩ về nó. bạn không thể đề cập đến một biến không được đề cập trong mã ở đâu đó tại thời điểm bạn biên dịch mã

Quay lại import *

Về vấn đề này, chúng ta có thể hiểu tại sao chúng ta không thể sử dụng import * ở phạm vi chức năng. Nếu một import * được thực thi, Python sẽ phải tạo một nhóm cho từng thành viên của mô-đun đã nhập để mã đó có thể sử dụng

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
8 để nhận các giá trị này. Nhưng tại thời điểm chức năng được biên dịch, không có cách nào để biết những gì sẽ có trong mô-đun đã nhập, vì vậy không có cách nào để phân bổ trước các bộ chứa cho các thành viên. Mô-đun thậm chí có thể không được tải khi chức năng được biên dịch;

Về mặt lý thuyết có thể giải quyết vấn đề này và làm cho import * hoạt động ở phạm vi chức năng không? . Python có thể tạo ra một

def foo[]:
    import math
    print[math.sqrt[16]]
 
    class Foo:
        def method[self]:
            return 1
 
    return Foo[]
8 cho các tên biến mà nó nhận ra, và một
SyntaxError: import * only allowed at module level
7 chậm hơn cho tất cả các biến khác. Nhưng điều này có nghĩa là tạo một lệnh mới trên mỗi lệnh gọi hàm [đó là một phạm vi mới mỗi khi hàm được gọi, không chỉ khi một hàm mới được xác định], điều này gây lãng phí một cách không cần thiết cho một tính năng không hữu ích lắm. Python đưa ra quyết định thực dụng không hỗ trợ điều này, ngay cả khi mất tính thường xuyên

Tại sao tôi không thể nhập chức năng trong Python?

Lỗi này thường xảy ra khi không thể nhập một lớp do một trong những lý do sau. Lớp đã nhập nằm trong phần phụ thuộc vòng tròn . Lớp đã nhập không khả dụng hoặc chưa được tạo. Tên lớp đã nhập bị sai chính tả.

Là nhập khẩu một chức năng?

Cú pháp import[], thường được gọi là nhập động, là một biểu thức giống hàm cho phép tải mô-đun ECMAScript một cách linh hoạt và không đồng bộ vào .

Bạn có nên nhập các mô-đun bên trong các định nghĩa hàm không?

Chỉ di chuyển quá trình nhập vào phạm vi cục bộ, chẳng hạn như bên trong định nghĩa hàm, nếu điều đó là cần thiết để giải quyết vấn đề chẳng hạn như tránh nhập tuần hoàn hoặc đang cố gắng giảm thời gian khởi tạo . .

Chủ Đề