Anh chị em nhập khẩu trọn gói


199

Tôi đã thử đọc qua các câu hỏi về anh chị em nhập khẩu và thậm chí cả tài liệu gói , nhưng tôi vẫn chưa tìm thấy câu trả lời.

Với cấu trúc như sau:

├── LICENSE.md
├── README.md
├── api
   ├── __init__.py
   ├── api.py
   └── api_key.py
├── examples
   ├── __init__.py
   ├── example_one.py
   └── example_two.py
└── tests
   ├── __init__.py
   └── test_one.py

Làm thế nào các tập lệnh trong examplesteststhư mục có thể nhập từ apimô-đun và được chạy từ dòng lệnh?

Ngoài ra, tôi muốn tránh sys.path.inserthack xấu xí cho mọi tệp. Chắc chắn điều này có thể được thực hiện trong Python, phải không?


7
Tôi khuyên bạn nên bỏ qua tất cả các bản sys.pathhack và đọc giải pháp thực tế duy nhất được đăng cho đến nay (sau 7 năm!).
Aran-Fey

1
Nhân tiện, vẫn còn chỗ cho một giải pháp tốt khác: Tách mã thực thi khỏi mã thư viện; hầu hết thời gian một tập lệnh bên trong một gói không nên được thực thi để bắt đầu.
Aran-Fey

Điều này rất hữu ích, cả câu hỏi và câu trả lời. Tôi chỉ tò mò, làm thế nào mà "Câu trả lời được chấp nhận" không giống như người được trao tiền thưởng trong trường hợp này?
Indominus 27/12/18

@ Aran-Fey Đó là một lời nhắc nhở được đánh giá thấp trong các câu hỏi và lỗi nhập tương đối này. Tôi đã tìm kiếm một bản hack toàn bộ thời gian này, nhưng sâu thẳm tôi biết có một cách đơn giản để thiết kế theo cách của tôi ra khỏi vấn đề. Không phải nói rằng đó là giải pháp cho mọi người ở đây đọc, nhưng đó là một lời nhắc tốt vì nó có thể dành cho nhiều người.
colorlace

Câu trả lời:


69

Bảy năm sau

Vì tôi đã viết câu trả lời dưới đây, sửa đổi sys.pathvẫn là một thủ thuật nhanh và bẩn, hoạt động tốt cho các tập lệnh riêng tư, nhưng đã có một số cải tiến

  • Cài đặt gói (trong virtualenv hoặc không) sẽ cung cấp cho bạn những gì bạn muốn, mặc dù tôi sẽ đề nghị sử dụng pip để thực hiện thay vì sử dụng trực tiếp setuptools (và sử dụng setup.cfgđể lưu trữ siêu dữ liệu)
  • Sử dụng -mcờ và chạy như một gói cũng hoạt động (nhưng sẽ hơi khó xử nếu bạn muốn chuyển đổi thư mục làm việc của mình thành một gói có thể cài đặt).
  • Đối với các thử nghiệm, cụ thể, pytest có thể tìm thấy gói api trong tình huống này và chăm sóc các bản sys.pathhack cho bạn

Vì vậy, nó thực sự phụ thuộc vào những gì bạn muốn làm. Tuy nhiên, trong trường hợp của bạn, vì dường như mục tiêu của bạn là tạo ra một gói phù hợp tại một số điểm, cài đặt thông qua pip -ecó lẽ là đặt cược tốt nhất của bạn, ngay cả khi nó chưa hoàn hảo.

Câu trả lời cũ

Như đã nói ở nơi khác, sự thật khủng khiếp là bạn phải thực hiện các bản hack xấu xí để cho phép nhập khẩu từ các mô đun anh chị em hoặc gói cha mẹ từ một __main__mô-đun. Vấn đề được nêu chi tiết trong PEP 366 . PEP 3122 đã cố gắng xử lý hàng nhập khẩu theo cách hợp lý hơn nhưng Guido đã từ chối nó một trong những tài khoản của

Trường hợp sử dụng duy nhất dường như đang chạy các tập lệnh xảy ra trong thư mục của mô-đun, thứ mà tôi luôn thấy là một phản mẫu.

( ở đây )

Mặc dù, tôi sử dụng mô hình này một cách thường xuyên với

# Ugly hack to allow absolute import from the root folder
# whatever its name is. Please forgive the heresy.
if __name__ == "__main__" and __package__ is None:
    from sys import path
    from os.path import dirname as dir

    path.append(dir(path[0]))
    __package__ = "examples"

import api

Đây path[0]là thư mục mẹ của tập lệnh đang chạy và dir(path[0])thư mục cấp cao nhất của bạn.

Mặc dù vậy, tôi vẫn chưa thể sử dụng nhập tương đối với điều này, nhưng nó cho phép nhập tuyệt đối từ cấp cao nhất (trong apithư mục mẹ của ví dụ của bạn ).


3
bạn không phải làm nếu bạn chạy từ một thư mục dự án bằng cách sử dụng -mbiểu mẫu hoặc nếu bạn cài đặt gói (pip và virtualenv làm cho nó dễ dàng)
jfs

2
Làm thế nào để pytest tìm thấy gói api cho bạn? Thật thú vị, tôi đã tìm thấy chủ đề này bởi vì tôi đang gặp phải vấn đề này đặc biệt với việc nhập gói pytest và anh chị em.
JuniorIncanter

1
Tôi có hai câu hỏi. 1. Mô hình của bạn dường như làm việc mà không có __package__ = "examples"tôi. tại sao bạn dùng nó? 2. Trong tình huống nào __name__ == "__main__"nhưng __package__không None?
fact_panda

@actual_panda Cài đặt __packages__giúp nếu bạn muốn đường dẫn tuyệt đối như examples.apihoạt động iirc (nhưng đã lâu rồi tôi mới làm điều đó) và kiểm tra gói đó không phải là Không an toàn cho các tình huống kỳ lạ và tương lai.
Evpok

165

Mệt mỏi vì hack sys.path?

Có rất nhiều cách sys.path.appendthức có sẵn, nhưng tôi đã tìm thấy một cách khác để giải quyết vấn đề trong tay.

Tóm lược

  • Gói mã vào một thư mục (ví dụ packaged_stuff)
  • Sử dụng tạo setup.pytập lệnh trong đó bạn sử dụng setuptools.setup () .
  • Pip cài đặt gói ở trạng thái có thể chỉnh sửa với pip install -e <myproject_folder>
  • Nhập khẩu bằng cách sử dụng from packaged_stuff.modulename import function_name

Thiết lập

Điểm bắt đầu là cấu trúc tệp bạn đã cung cấp, được bọc trong một thư mục có tên myproject.

.
└── myproject
    ├── api
       ├── api_key.py
       ├── api.py
       └── __init__.py
    ├── examples
       ├── example_one.py
       ├── example_two.py
       └── __init__.py
    ├── LICENCE.md
    ├── README.md
    └── tests
        ├── __init__.py
        └── test_one.py

Tôi sẽ gọi .thư mục gốc, và trong trường hợp ví dụ của tôi, nó được đặt tại C:\tmp\test_imports\.

api.py

Như một trường hợp thử nghiệm, hãy sử dụng ./api/api.py

def function_from_api():
    return 'I am the return value from api.api!'

test_one.py

from api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

Hãy thử chạy test_one:

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\myproject\tests\test_one.py", line 1, in <module>
    from api.api import function_from_api
ModuleNotFoundError: No module named 'api'

Cũng cố gắng nhập khẩu tương đối sẽ không làm việc:

Sử dụng from ..api.api import function_from_apisẽ dẫn đến

PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
Traceback (most recent call last):
  File ".\tests\test_one.py", line 1, in <module>
    from ..api.api import function_from_api
ValueError: attempted relative import beyond top-level package

Các bước

  1. Tạo một tập tin setup.py vào thư mục cấp gốc

Nội dung cho setup.pysẽ là *

from setuptools import setup, find_packages

setup(name='myproject', version='1.0', packages=find_packages())
  1. Sử dụng môi trường ảo

Nếu bạn quen thuộc với môi trường ảo, hãy kích hoạt một và bỏ qua bước tiếp theo. Việc sử dụng môi trường ảo không hoàn toàn bắt buộc, nhưng chúng sẽ thực sự giúp bạn trong thời gian dài (khi bạn có hơn 1 dự án đang diễn ra ..). Các bước cơ bản nhất là (chạy trong thư mục gốc)

  • Tạo env ảo
    • python -m venv venv
  • Kích hoạt env ảo
    • source ./venv/bin/activate(Linux, macOS) hoặc ./venv/Scripts/activate(Thắng)

Để tìm hiểu thêm về điều này, chỉ cần Google tìm ra "hướng dẫn env ảo python" hoặc tương tự. Bạn có thể không bao giờ cần bất kỳ lệnh nào khác ngoài việc tạo, kích hoạt và hủy kích hoạt.

Khi bạn đã tạo và kích hoạt một môi trường ảo, bảng điều khiển của bạn sẽ đặt tên của môi trường ảo trong ngoặc đơn

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>

và cây thư mục của bạn sẽ trông như thế này **

.
├── myproject
   ├── api
      ├── api_key.py
      ├── api.py
      └── __init__.py
   ├── examples
      ├── example_one.py
      ├── example_two.py
      └── __init__.py
   ├── LICENCE.md
   ├── README.md
   └── tests
       ├── __init__.py
       └── test_one.py
├── setup.py
└── venv
    ├── Include
    ├── Lib
    ├── pyvenv.cfg
    └── Scripts [87 entries exceeds filelimit, not opening dir]
  1. Pip cài đặt dự án của bạn trong trạng thái có thể chỉnh sửa

Cài đặt gói cấp cao nhất của bạn myprojectbằng cách sử dụng pip. Mẹo nhỏ là sử dụng -ecờ khi thực hiện cài đặt. Bằng cách này, nó được cài đặt ở trạng thái có thể chỉnh sửa và tất cả các chỉnh sửa được thực hiện cho các tệp .py sẽ được tự động đưa vào gói đã cài đặt.

Trong thư mục gốc, hãy chạy

pip install -e . (lưu ý dấu chấm, nó là viết tắt của "thư mục hiện tại")

Bạn cũng có thể thấy rằng nó được cài đặt bằng cách sử dụng pip freeze

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
  1. Thêm myproject.vào nhập khẩu của bạn

Lưu ý rằng bạn sẽ chỉ phải thêm myproject.vào các mục nhập không hoạt động khác. Nhập khẩu mà làm việc mà không có setup.py& pip installsẽ hoạt động tốt. Xem một ví dụ dưới đây.


Kiểm tra giải pháp

Bây giờ, hãy kiểm tra giải pháp bằng cách sử dụng api.pyđược xác định ở trên và test_one.pyđược xác định bên dưới.

test_one.py

from myproject.api.api import function_from_api

def test_function():
    print(function_from_api())

if __name__ == '__main__':
    test_function()

chạy thử

(venv) PS C:\tmp\test_imports> python .\myproject\tests\test_one.py
I am the return value from api.api!

* Xem tài liệu setuptools để biết thêm ví dụ về thiết lập chi tiết.

** Trong thực tế, bạn có thể đặt môi trường ảo của mình ở bất cứ đâu trên đĩa cứng.


13
Cảm ơn cho bài viết chi tiết. Đây là vấn đề của tôi. Nếu tôi làm mọi thứ bạn nói và tôi đóng băng pip, tôi nhận được một dòng -e git+https://username@bitbucket.org/folder/myproject.git@f65466656XXXXX#egg=myprojectBất kỳ ý tưởng nào về cách giải quyết?
Si Mon

2
Tại sao giải pháp nhập khẩu tương đối không hoạt động? Tôi tin bạn, nhưng tôi đang cố gắng hiểu hệ thống phức tạp của Python.
Jared Nielsen

8
Có ai có vấn đề liên quan đến a ModuleNotFoundError? Tôi đã cài đặt 'myproject' vào một virtualenv theo các bước sau và khi tôi vào một phiên dịch và chạy import myprojecttôi có được ModuleNotFoundError: No module named 'myproject'không? pip list installed | grep myprojectcho thấy rằng nó là ở đó, thư mục là đúng, và cả verison của pippythonđược xác nhận là đúng.
Những người đó

2
Xin chào @ np8, nó hoạt động, tôi vô tình cài đặt nó trong venv và trong os :) pip listhiển thị các gói, trong khi pip freezehiển thị các tên lạ nếu được cài đặt với cờ -e
Grzegorz Krug

3
Đã dành khoảng 2 giờ để cố gắng tìm ra cách làm cho hàng nhập khẩu tương đối hoạt động, và câu trả lời này là câu trả lời cuối cùng thực sự đã làm một điều gì đó hợp lý. 👍👍
Graham Lea

43

Đây là một cách khác mà tôi chèn vào đầu các tệp Python trong teststhư mục:

# Path hack.
import sys, os
sys.path.insert(0, os.path.abspath('..'))

1
+1 thực sự đơn giản và nó hoạt động hoàn hảo. Bạn cần thêm lớp cha vào nhập (ex api.api, example.example_two) nhưng tôi thích nó theo cách đó.
Evan Plaice

10
Tôi nghĩ rằng đáng để đề cập đến những người mới (như bản thân tôi) rằng ..ở đây có liên quan đến thư mục bạn đang thực hiện từ --- không phải thư mục chứa tệp thử nghiệm / ví dụ đó. Tôi đang thực hiện từ thư mục dự án, và tôi cần ./thay thế. Hy vọng điều này sẽ giúp người khác.
Joshua Detwiler

@JoshDetwiler, vâng tuyệt đối. Tôi đã không nhận thức được điều đó. Cảm ơn.
doak

1
Đây là một câu trả lời kém. Hack con đường không phải là thực hành tốt; thật tai tiếng khi nó được sử dụng trong thế giới trăn. Một trong những điểm chính của câu hỏi này là để xem cách nhập khẩu có thể được thực hiện trong khi tránh loại hack này.
jtc Bông63

sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))@JoshuaDetwiler
vldbnc

31

Bạn không cần và không nên hack sys.pathtrừ khi cần thiết và trong trường hợp này thì không. Sử dụng:

import api.api_key # in tests, examples

Chạy từ thư mục dự án : python -m tests.test_one.

Bạn có lẽ nên di chuyển tests(nếu họ là những người không quen biết) bên trong apivà chạy python -m api.testđể chạy tất cả các bài kiểm tra (giả sử là có __main__.py) hoặc python -m api.test.test_oneđể chạy test_onethay thế.

Bạn cũng có thể xóa __init__.pykhỏi examples(nó không phải là gói Python) và chạy các ví dụ trong virtualenv nơi apiđược cài đặt, ví dụ, pip install -e .trong virtualenv sẽ cài đặt apigói inplace nếu bạn có đúng setup.py.


@Alex câu trả lời không cho rằng các bài kiểm tra là các bài kiểm tra API ngoại trừ đoạn mà nó nói rõ ràng "nếu chúng là những điều không mong muốn của api" .
jfs

thật không may, sau đó bạn bị mắc kẹt với việc chạy từ thư mục gốc và PyCharm vẫn không tìm thấy tệp cho các chức năng hay của nó
mhstnsc

@mhstnsc: không đúng Bạn sẽ có thể chạy python -m api.test.test_onetừ bất cứ đâu khi virtualenv được kích hoạt. Nếu bạn không thể định cấu hình PyCharm để chạy thử nghiệm của mình, hãy thử đặt câu hỏi Stack Overflow mới (nếu bạn không thể tìm thấy câu hỏi hiện có về chủ đề này).
jfs

@jfs Tôi đã bỏ lỡ đường dẫn env ảo nhưng tôi không muốn sử dụng bất cứ thứ gì ngoài dòng shebang để chạy công cụ này từ bất kỳ thư mục nào. Nó không phải là về chạy với PyCharm. Các nhà phát triển với PyCharm cũng biết rằng họ đã hoàn thành và chuyển qua các chức năng mà tôi không thể làm cho nó hoạt động với bất kỳ giải pháp nào.
mhstnsc

@mhstnsc một shebang thích hợp là đủ trong nhiều trường hợp (trỏ nó vào tệp nhị phân ảo python. Bất kỳ IDE Python nào cũng nên hỗ trợ virtualenv.
jfs

9

Tôi chưa có sự hiểu biết về Pythonology cần thiết để xem cách chia sẻ mã dự định giữa các dự án không liên quan mà không có hack nhập khẩu tương đối. Cho đến ngày hôm đó, đây là giải pháp của tôi. Đối với exampleshoặc testsđể nhập nội dung từ ..\api, nó sẽ trông như:

import sys.path
import os.path
# Import from sibling directory ..\api
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
import api.api
import api.api_key

Điều này vẫn sẽ cung cấp cho bạn thư mục cha api và bạn sẽ không cần nối "/ .." sys.path.append (os.path.dirname (os.path.dirname (os.path.abspath ( tệp ))) )
Camilo Sanchez

4

Đối với nhập khẩu gói anh chị em, bạn có thể sử dụng phương thức chèn hoặc chắp thêm của mô-đun [sys.path] [2] :

if __name__ == '__main__' and if __package__ is None:
    import sys
    from os import path
    sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
    import api

Điều này sẽ hoạt động nếu bạn đang khởi chạy các tập lệnh của mình như sau:

python examples/example_one.py
python tests/test_one.py

Mặt khác, bạn cũng có thể sử dụng nhập tương đối:

if __name__ == '__main__' and if __package__ is not None:
    import ..api.api

Trong trường hợp này, bạn sẽ phải khởi chạy tập lệnh của mình với đối số '-m' (lưu ý rằng, trong trường hợp này, bạn không được đưa ra phần mở rộng '.py' ):

python -m packageName.examples.example_one
python -m packageName.tests.test_one

Tất nhiên, bạn có thể kết hợp hai cách tiếp cận, để tập lệnh của bạn hoạt động cho dù nó được gọi như thế nào:

if __name__ == '__main__':
    if __package__ is None:
        import sys
        from os import path
        sys.path.append( path.dirname( path.dirname( path.abspath(__file__) ) ) )
        import api
    else:
        import ..api.api

Tôi đang sử dụng khung Click không có __file__toàn cầu nên tôi phải sử dụng như sau: sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))))Nhưng nó hoạt động trong bất kỳ thư mục nào bây giờ
GammaGames

3

TLD

Phương pháp này không yêu cầu setuptools, hack đường dẫn, đối số dòng lệnh bổ sung hoặc chỉ định mức cao nhất của gói trong mỗi tệp duy nhất của dự án của bạn.

Chỉ cần tạo một tập lệnh trong thư mục mẹ của bất cứ thứ gì bạn đang gọi là của bạn __main__và chạy mọi thứ từ đó. Để giải thích thêm tiếp tục đọc.

Giải trình

Điều này có thể được thực hiện mà không cần hack một đường dẫn mới cùng nhau, thêm dòng lệnh lập luận hoặc thêm mã vào mỗi chương trình của bạn để nhận ra anh chị em của nó.

Lý do thất bại như tôi tin đã được đề cập trước đây là các chương trình được gọi có tên __name____main__. Khi điều này xảy ra, tập lệnh được gọi là chấp nhận chính nó ở cấp cao nhất của gói và từ chối nhận ra tập lệnh trong các thư mục anh chị em.

Tuy nhiên, mọi thứ ở cấp cao nhất của thư mục vẫn sẽ nhận ra BẤT K EL LÚC NÀO dưới cấp cao nhất. Điều này có nghĩa là điều DUY NHẤT bạn phải làm để có được các tệp trong các thư mục anh chị em để nhận biết / sử dụng lẫn nhau là gọi chúng từ một tập lệnh trong thư mục mẹ của chúng.

Bằng chứng về khái niệm Trong một thư mục có cấu trúc sau:

.
|__Main.py
|
|__Siblings
   |
   |___sib1
   |   |
   |   |__call.py
   |
   |___sib2
       |
       |__callsib.py

Main.py chứa mã sau đây:

import sib1.call as call


def main():
    call.Call()


if __name__ == '__main__':
    main()

sib1 / call.py chứa:

import sib2.callsib as callsib


def Call():
    callsib.CallSib()


if __name__ == '__main__':
    Call()

và sib2 / Callib.py chứa:

def CallSib():
    print("Got Called")

if __name__ == '__main__':
    CallSib()

Nếu bạn sao chép ví dụ này, bạn sẽ nhận thấy rằng việc gọi Main.pysẽ dẫn đến "Đã gọi" được in như được xác định sib2/callsib.py ngay cả khi sib2/callsib.pyđã được gọi qua sib1/call.py. Tuy nhiên, nếu người ta gọi trực tiếp sib1/call.py(sau khi thực hiện các thay đổi phù hợp đối với hàng nhập khẩu) thì nó sẽ ném ra một ngoại lệ. Mặc dù nó hoạt động khi được gọi bởi tập lệnh trong thư mục mẹ của nó, nó sẽ không hoạt động nếu nó tin rằng nó nằm ở cấp cao nhất của gói.


2

Tôi đã thực hiện một dự án mẫu để chứng minh cách tôi xử lý việc này, đây thực sự là một vụ hack sys.path khác như đã nêu ở trên. Ví dụ nhập khẩu anh chị em Python , dựa trên:

if __name__ == '__main__': import os import sys sys.path.append(os.getcwd())

Điều này có vẻ khá hiệu quả miễn là thư mục làm việc của bạn vẫn nằm trong thư mục gốc của dự án Python. Nếu bất cứ ai triển khai điều này trong một môi trường sản xuất thực sự, thật tuyệt khi nghe nó cũng hoạt động ở đó.


Điều này chỉ hoạt động nếu bạn đang chạy từ thư mục mẹ của tập lệnh
Evpok

1

Bạn cần xem để xem cách các câu lệnh nhập được viết trong mã liên quan. Nếu examples/example_one.pysử dụng câu lệnh nhập sau:

import api.api

... Sau đó, nó hy vọng thư mục gốc của dự án sẽ nằm trong đường dẫn hệ thống.

Cách dễ nhất để hỗ trợ điều này mà không có bất kỳ hack nào (như bạn nói) là chạy các ví dụ từ thư mục cấp cao nhất, như thế này:

PYTHONPATH=$PYTHONPATH:. python examples/example_one.py 

Với Python 2.7.1 tôi nhận được như sau : $ python examples/example.py Traceback (most recent call last): File "examples/example.py", line 3, in <module> from api.api import API ImportError: No module named api.api. Tôi cũng nhận được cùng import api.api.
zachwill

Cập nhật câu trả lời của tôi ... bạn làm phải thêm thư mục hiện hành để con đường nhập khẩu, không có cách nào xung quanh đó.
AJ.

1

Chỉ trong trường hợp ai đó sử dụng Pydev trên Eclipse kết thúc tại đây: bạn có thể thêm đường dẫn cha mẹ của anh chị em (và do đó là cha mẹ của mô-đun gọi) làm thư mục thư viện bên ngoài bằng Project-> Properties và đặt Thư viện bên ngoài trong menu bên trái Pydev-PYTHONPATH . Sau đó, bạn có thể nhập từ anh chị em của bạn, ví dụ from sibling import some_class.


-3

Trước tiên, bạn nên tránh để các tệp có cùng tên với chính mô-đun. Nó có thể phá vỡ nhập khẩu khác.

Khi bạn nhập một tệp, đầu tiên trình thông dịch sẽ kiểm tra thư mục hiện tại và sau đó tìm kiếm các thư mục toàn cầu.

Bên trong exampleshoặc testsbạn có thể gọi:

from ..api import api

Tôi nhận được những điều sau với Python 2.7.1:Traceback (most recent call last): File "example_one.py", line 3, in <module> from ..api import api ValueError: Attempted relative import in non-package
zachwill

2
Oh, sau đó bạn nên thêm một __init__.pytập tin vào thư mục cấp cao nhất.

8
Nó sẽ không hoạt động. Vấn đề không phải là thư mục mẹ không phải là một gói phần mềm, nó là vì các mô-đun của __name____main__thay vì package.module, Python không thể nhìn thấy gói mẹ của nó, vì vậy .điểm không có gì.
Evpok
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.