Có phải là pythonic để nhập các chức năng bên trong?


126

PEP 8 cho biết:

  • Dữ liệu nhập luôn được đặt ở đầu tệp, ngay sau bất kỳ nhận xét và chuỗi tài liệu nào của mô-đun, và trước toàn cầu và hằng số mô-đun.

Khi xảy ra, tôi vi phạm PEP 8. Một số lần tôi nhập nội dung bên trong các hàm. Theo nguyên tắc chung, tôi thực hiện việc này nếu có một lần nhập chỉ được sử dụng trong một hàm duy nhất.

Có ý kiến ​​gì không?

CHỈNH SỬA (lý do tôi cảm thấy nhập các hàm có thể là một ý kiến ​​hay):

Lý do chính: Nó có thể làm cho mã rõ ràng hơn.

  • Khi nhìn vào mã của một hàm, tôi có thể tự hỏi: "Hàm / lớp xxx là gì?" (xxx đang được sử dụng bên trong hàm). Nếu tôi có tất cả các lần nhập của mình ở đầu mô-đun, tôi phải đến đó để xác định xxx là gì. Đây là một vấn đề nhiều hơn khi sử dụng from m import xxx. Xem m.xxxtrong chức năng có lẽ cho tôi biết thêm. Tùy thuộc vào điều gì m: Nó có phải là một mô-đun / gói ( import m) cấp cao nhất nổi tiếng không? Hay nó là một mô-đun con / gói ( from a.b.c import m)?
  • Trong một số trường hợp, có thêm thông tin ("xxx là gì?") Gần nơi xxx được sử dụng có thể làm cho hàm dễ hiểu hơn.

2
và bạn làm điều đó để thực hiện?
Macarse

4
Tôi cảm thấy nó làm cho mã rõ ràng hơn trong một số trường hợp. Tôi đoán hiệu suất thô sẽ giảm khi nhập vào một hàm (vì câu lệnh nhập sẽ thực thi mỗi khi hàm được gọi).
codeape

Bạn có thể trả lời "Hàm / lớp xxx là gì?" bằng cách sử dụng cú pháp nhập khẩu xyz chứ không phải là từ nhập khẩu xyz abc cú pháp
Tom Leys

1
Nếu sự rõ ràng là yếu tố duy nhất, thì U cũng có thể bao gồm một nhận xét có liên quan, để đạt được hiệu quả đó. ;)
Lakshman Prasad

5
@becomingGuru: Chắc chắn, nhưng những nhận xét không đồng bộ với thực tế ...
codeape

Câu trả lời:


88

Về lâu dài, tôi nghĩ bạn sẽ đánh giá cao việc có hầu hết các lần nhập của bạn ở đầu tệp, bằng cách đó bạn có thể biết ngay mô-đun của mình phức tạp như thế nào bằng những gì nó cần nhập.

Nếu tôi thêm mã mới vào tệp hiện có, tôi thường sẽ thực hiện việc nhập khi cần thiết và sau đó nếu mã vẫn còn, tôi sẽ làm cho mọi thứ lâu dài hơn bằng cách di chuyển dòng nhập lên đầu tệp.

Một điểm khác, tôi muốn có một ImportErrorngoại lệ trước khi chạy bất kỳ mã nào - như một kiểm tra sự tỉnh táo, vì vậy đó là một lý do khác để nhập ở trên cùng.

Tôi sử dụng pyCheckerđể kiểm tra các mô-đun không sử dụng.


47

Có hai trường hợp tôi vi phạm PEP 8 về vấn đề này:

  • Nhập vòng tròn: mô-đun A nhập mô-đun B, nhưng thứ gì đó trong mô-đun B cần mô-đun A (mặc dù đây thường là dấu hiệu cho thấy tôi cần cấu trúc lại các mô-đun để loại bỏ sự phụ thuộc vòng tròn)
  • Chèn điểm ngắt pdb: import pdb; pdb.set_trace()Điều này rất tiện lợi mà tôi không muốn đặt import pdbở đầu mỗi mô-đun mà tôi có thể muốn gỡ lỗi và dễ nhớ là xóa nhập khi tôi xóa điểm ngắt.

Ngoài hai trường hợp này, bạn nên đặt mọi thứ lên đầu. Nó làm cho các phụ thuộc rõ ràng hơn.


7
Tôi đồng ý rằng nó làm cho các phụ thuộc rõ ràng hơn liên quan đến toàn bộ mô-đun. Nhưng tôi tin rằng nó có thể làm cho mã kém rõ ràng hơn ở cấp hàm để nhập mọi thứ ở trên cùng. Khi bạn đang xem mã của một hàm, bạn có thể tự hỏi: "Hàm / lớp xxx là gì?" (xxx được sử dụng bên trong hàm). Và bạn phải nhìn vào đầu của tập tin để xem xxx đến từ đâu. Đây là một vấn đề nhiều hơn khi sử dụng từ m import xxx. Xem m.xxx cho bạn biết nhiều hơn - ít nhất là nếu không có nghi ngờ gì về m.
codeape

20

Dưới đây là bốn trường hợp sử dụng nhập mà chúng tôi sử dụng

  1. import(và from x import yimport x as y) ở trên cùng

  2. Lựa chọn nhập khẩu. Ở trên cùng.

    import settings
    if setting.something:
        import this as foo
    else:
        import that as foo
  3. Nhập khẩu có điều kiện. Được sử dụng với các thư viện JSON, XML và những thứ tương tự. Ở trên cùng.

    try:
        import this as foo
    except ImportError:
        import that as foo
  4. Nhập động. Cho đến nay, chúng ta chỉ có một ví dụ về điều này.

    import settings
    module_stuff = {}
    module= __import__( settings.some_module, module_stuff )
    x = module_stuff['x']

    Lưu ý rằng thao tác nhập động này không mang lại mã, nhưng mang lại cấu trúc dữ liệu phức tạp được viết bằng Python. Nó giống như một mẩu dữ liệu được ngâm, ngoại trừ việc chúng ta nhặt nó bằng tay.

    Điều này dù ít hay nhiều cũng nằm ở đầu mô-đun


Đây là những gì chúng tôi làm để làm cho mã rõ ràng hơn:

  • Giữ các mô-đun ngắn gọn.

  • Nếu tôi có tất cả các mục nhập của mình ở đầu mô-đun, tôi phải đi đến đó để xác định tên là gì. Nếu mô-đun ngắn, điều đó rất dễ thực hiện.

  • Trong một số trường hợp, có thông tin bổ sung gần với nơi sử dụng tên có thể làm cho hàm dễ hiểu hơn. Nếu mô-đun ngắn, điều đó rất dễ thực hiện.


Giữ các mô-đun ngắn tất nhiên là một ý kiến ​​rất hay. Nhưng để có được lợi ích của việc luôn có sẵn "thông tin nhập" cho các chức năng, độ dài mô-đun tối đa sẽ phải là một màn hình (có thể là tối đa 100 dòng). Và điều đó có lẽ sẽ quá ngắn để có thể thực tế trong hầu hết các trường hợp.
codeape

Tôi cho rằng bạn có thể đưa điều này đến một cực điểm logic. Tôi nghĩ rằng có thể có một điểm cân bằng mà mô-đun của bạn "đủ nhỏ" mà bạn không cần các kỹ thuật nhập ưa thích để quản lý sự phức tạp. Kích thước mô-đun trung bình của chúng tôi - thật trùng hợp - khoảng 100 dòng.
S.Lott

8

Một điều cần lưu ý: nhập khẩu không cần thiết có thể gây ra các vấn đề về hiệu suất. Vì vậy, nếu đây là một hàm sẽ được gọi thường xuyên, tốt hơn hết bạn nên đặt nhập ở đầu. Tất nhiên đây một sự tối ưu hóa, vì vậy nếu có một trường hợp hợp lệ được thực hiện rằng việc nhập vào bên trong một hàm rõ ràng hơn là nhập ở đầu tệp, điều đó sẽ vượt trội hơn hiệu suất trong hầu hết các trường hợp.

Nếu bạn đang thực hiện IronPython, tôi được khuyên rằng tốt hơn nên nhập các hàm bên trong (vì quá trình biên dịch mã trong IronPython có thể chậm). Do đó, bạn có thể có được cách nhập các hàm bên trong. Nhưng khác với điều đó, tôi cho rằng không đáng để chống lại quy ước.

Theo nguyên tắc chung, tôi thực hiện việc này nếu có một lần nhập chỉ được sử dụng trong một hàm duy nhất.

Một điểm khác mà tôi muốn đưa ra là đây có thể là một vấn đề về bảo trì tiềm ẩn. Điều gì sẽ xảy ra nếu bạn thêm một hàm sử dụng một mô-đun mà trước đó chỉ được sử dụng bởi một hàm? Bạn có nhớ thêm nhập vào đầu tệp không? Hay bạn sẽ quét từng chức năng để nhập?

FWIW, có những trường hợp nhập vào bên trong một hàm là hợp lý. Ví dụ: nếu bạn muốn đặt ngôn ngữ trong cx_Oracle, bạn cần đặt một _biến môi trường NLS LANG trước khi nó được nhập. Do đó, bạn có thể thấy mã như thế này:

import os

oracle = None

def InitializeOracle(lang):
    global oracle
    os.environ['NLS_LANG'] = lang
    import cx_Oracle
    oracle = cx_Oracle

2
Tôi đồng ý với vấn đề bảo trì của bạn. Cấu trúc lại mã có thể có một chút vấn đề. Nếu tôi thêm chức năng thứ hai sử dụng mô-đun trước đó chỉ được sử dụng bởi một chức năng - tôi sẽ di chuyển nhập lên trên cùng hoặc tôi phá vỡ quy tắc chung của riêng mình bằng cách nhập mô-đun trong hàm thứ hai.
codeape

2
Tôi nghĩ rằng lập luận về hiệu suất cũng có thể đi theo hướng khác. Việc nhập một mô-đun có thể tốn nhiều thời gian. Trên các hệ thống tệp phân tán như trên siêu máy tính, việc nhập một mô-đun lớn như numpy có thể mất vài giây. Nếu một mô-đun chỉ cần thiết cho một hàm duy nhất, hiếm khi được sử dụng, thì việc nhập vào hàm sẽ tăng tốc độ trường hợp chung một cách đáng kể.
amaurea

6

Tôi đã phá vỡ quy tắc này trước đây đối với các mô-đun tự kiểm tra. Đó là, chúng thường chỉ được sử dụng để hỗ trợ, nhưng tôi xác định một main cho chúng để nếu bạn tự chạy chúng, bạn có thể kiểm tra chức năng của chúng. Trong trường hợp đó, đôi khi tôi nhập getoptcmdchỉ nhập chính vì tôi muốn ai đó đọc mã hiểu rõ rằng các mô-đun này không liên quan gì đến hoạt động bình thường của mô-đun và chỉ được đưa vào để thử nghiệm.


5

Đến từ câu hỏi về việc tải mô-đun hai lần - Tại sao không phải cả hai?

Một lần nhập ở đầu tập lệnh sẽ chỉ ra các phần phụ thuộc và một lần nhập khác trong hàm với việc làm cho hàm này trở nên nguyên tử hơn, trong khi dường như không gây ra bất kỳ bất lợi nào về hiệu suất, vì một lần nhập liên tiếp rất rẻ.


3

Miễn là nó importvà không from x import *, bạn nên đặt chúng ở trên cùng. Nó chỉ thêm một tên vào không gian tên chung, và bạn gắn bó với PEP 8. Ngoài ra, nếu sau này bạn cần nó ở một nơi khác, bạn không phải di chuyển bất cứ thứ gì.

Nó không có vấn đề gì lớn, nhưng vì hầu như không có gì khác biệt, tôi khuyên bạn nên làm những gì PEP 8 nói.


3
Trên thực tế, việc đặt from x import *bên trong một hàm sẽ tạo ra một Cảnh báo cú pháp, ít nhất là trong 2.5.
Rick Copeland

3

Hãy xem cách tiếp cận thay thế được sử dụng trong sqlalchemy: tiêm phụ thuộc:

@util.dependencies("sqlalchemy.orm.query")
def merge_result(query, *args):
    #...
    query.Query(...)

Lưu ý cách thư viện đã nhập được khai báo trong trình trang trí và được chuyển làm đối số cho hàm !

Cách tiếp cận này làm cho mã sạch hơn và cũng hoạt động nhanh hơn 4,5 lần so với một importcâu lệnh!

Điểm chuẩn: https://gist.github.com/kolypto/589e84fbcfb6312532658df2fabdb796


2

Trong các mô-đun vừa là mô-đun 'bình thường' và có thể được thực thi (tức là có if __name__ == '__main__':-section), tôi thường nhập các mô-đun chỉ được sử dụng khi thực thi mô-đun bên trong phần chính.

Thí dụ:

def really_useful_function(data):
    ...


def main():
    from pathlib import Path
    from argparse import ArgumentParser
    from dataloader import load_data_from_directory

    parser = ArgumentParser()
    parser.add_argument('directory')
    args = parser.parse_args()
    data = load_data_from_directory(Path(args.directory))
    print(really_useful_function(data)


if __name__ == '__main__':
    main()

1

Có một trường hợp khác (có thể là "góc") có thể có lợi cho importbên trong các chức năng hiếm khi được sử dụng: rút ngắn thời gian khởi động.

Tôi đã chạm vào bức tường đó một lần với một chương trình khá phức tạp chạy trên một máy chủ IoT nhỏ chấp nhận các lệnh từ một dòng nối tiếp và thực hiện các hoạt động, có thể là các hoạt động rất phức tạp.

Đặt importcâu lệnh ở đầu tệp có nghĩa là tất cả các lần nhập được xử lý trước khi máy chủ khởi động; kể từ khi importdanh sách bao gồm jinja2, lxml, signxmlvà các "trọng lượng nặng" (và SoC là không phải là rất mạnh mẽ) có nghĩa này phút trước lệnh đầu tiên đã được thực thi.

OTOH đặt hầu hết các lần nhập vào các chức năng Tôi đã có thể có máy chủ "sống" trên dòng nối tiếp trong vài giây. Tất nhiên khi các mô-đun thực sự cần thiết, tôi phải trả giá (Lưu ý: điều này cũng có thể được giảm thiểu bằng cách tạo ra một nhiệm vụ nền thực hiện importtrong thời gian nhàn rỗi).

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.