Импортировать все функции и классы внутри модуля в класс Python - PullRequest
5 голосов
/ 28 октября 2019

Я пытаюсь импортировать все объекты из подпапки в класс в Python 3.8, и я пытаюсь найти способ сделать это. Я не хочу вручную импортировать все объекты, поскольку в списке слишком много файлов:

class Foo:
    from bar import {
        one,
        two,
        three,
        # ...
    }

И когда я использую символ звезды для импорта всех функций и классов (например, from bar import *) Я получаю следующую ошибку:

SyntaxError: import * only allowed at module level

Кроме того, я не хотел бы помещать все в подобласть (например, import bar.package и помещать все файлы в подпакет package bar), потому что функции в bar полагаются на передачу self при запуске и означают, что придется изменить все ссылки self на self.package Я создаю только другую папку со всеми методами в классе, поэтомучто нет ни одного чрезвычайно длинного файла.

Итак, у меня есть три вопроса: почему нельзя импортировать все в классе, как я могу обойти это, и есть ли лучший способ разделить несколькометоды в разные файлы?

РЕДАКТИРОВАТЬ: я видел этот пост , и это то, что у меня есть в настоящее время, но только вместо того, чтобы импортировать все вручную, я хочу использовать вместо *.

РЕДАКТИРОВАТЬ 2: Возможно, создание подметодов класса в виде пакета и импорт его как self будет работать (например, import bar.package as self), но это может переопределить self по умолчанию. Например, скажем, у меня есть два файла foo.py и bar.py:

# foo.py
def print_bar(self):
    print("bar")
# bar.py
class Test:
    import foo as self
    def __init__(self):
        print("hi")
        self.one = 1

    def print_one(self):
        print(self.one)

if __name__ == "__main__":
    test_class = Test()
    test_class.print_one()
    test_class.self.print_bar(test_class)

Обратите внимание на ужасный вызов в bar.py, который выполняется test_class.self.print_bar(test_class). В этом случае python автоматически не передает класс как метод. Если я избавлюсь от аргумента test_class, переданного в функцию, он не запустится и выдаст TypeError. Я хочу избежать передачи self в метод любой ценой.

Ответы [ 2 ]

13 голосов
/ 02 ноября 2019

Прежде всего: Не делайте этого .

Возможно, есть более эффективные способы решения вашей конкретной проблемы. Если вам нужно определить большое количество связанных методов для класса, и вы хотите использовать модули для организации этих методов, то дайте каждому модулю базовый класс, определите методы для этих классов, а затем объедините классы из модулей в один. используя подклассы. См. Ниже.

Почему нельзя импортировать все в классе

Поскольку оператор класса представляет собой пространство имен с областью действия, где Python, во время компиляции Необходимо знать, на какие имена ссылаются как глобальные, и отличать их от локальных имен. Это особенно важно в функциях, где локальное пространство имен высоко оптимизировано, зная, какие имена используются во время компиляции . Для оператора класса «локальные» имена становятся атрибутами класса. Оператор класса является более гибким, когда речь идет о динамических локальных именах, чем функции, но импорт произвольного динамического списка имен в определение класса никогда не рассматривался как вариант поддержки, достойный поддержки.

как я могу обойти это

Вы можете проанализировать модуль и получить все те же имена, которые импортирует Python, и установить их в классе динамически, используя setattr() или путем присвоения словарю locals() (оператор класса является единственным местом, где последний действительно работает). В документации import указано, что импортируется:

  • Если модуль определяет __all__, то он используется в качестве списка имен для импорта
  • В противном случае импортируются все публичные имена (все глобальные имена с именами, которые не начинаются с _).

Таким образом, следующее будет иметь тот же эффект, что и использование from bar import * внутри классаоператор для Foo:

import bar

def _import_all(module, class):
    namespace = vars(module)
    public = (name for name in namespace if name[:1] != "_")
    for name in getattr(module, "__all__", public):
        setattr(class, name, namespace[name])

class Foo:
    pass

_import_all(bar, Foo)

или, и это определенно более «хак-у» и в зависимости от внутренних деталей реализации Python:

import bar

class Foo
    locals().update(
        (n, getattr(bar, n))
        for n in getattr(
            bar, "__all__",
            (n for n in dir(bar) if n[:1] != "_")
        )
    )

есть лилучший способ разделить несколько методов на разные файлы?

Да, используйте классы. Поместите все эти методы в отдельные модули, каждый в классе , затем просто импортируйте эти классы из каждого модуля и используйте его в качестве базового класса:

import bar, spam

class Foo(bar.Base, spam.Base):
    pass

, где bar.py будет определятькласс Base:

class Base:
    def print_bar(self):
        print("bar")

и т. п. spam и т. д. При необходимости вы можете добавлять и удалять методы для этих базовых классов смешивания, а операторы import для объединения базовых классовне меняется.

0 голосов
/ 01 ноября 2019

Цикл for для атрибутов bar с getattr и setattr может имитировать результат import-all, и вызов метода __get__ функции вернет ту же функцию, привязанную к экземпляру ( см. Этот ответ ):

# bar.py
def frog(self):
    print(f"frog: {self}")

def horse(self):
    print(f"horse: {self}")

# foo.py
class Foo:
    def __init__(self):
        import bar
        for object_name in dir(bar):
            if not object_name.startswith("__"):
                bound_method = getattr(bar, object_name).__get__(
                    self, 
                    self.__class__
                )
                setattr(self, object_name, bound_method)

>>> from foo import Foo
>>> f = Foo()
>>> f.frog
<bound method frog of <foo.Foo object at 0x10c961a50>>
>>> f.horse
<bound method horse of <foo.Foo object at 0x10c961a50>>
>>> f.frog()
frog: <foo.Foo object at 0x10c961a50>
>>> f.horse()
horse: <foo.Foo object at 0x10c961a50>
>>>

Удалите оператор if, чтобы включить дополнительные объекты Python по умолчанию (* 1015)*, __spec__ и т. Д.) Или расширьте его, если вы имеете в виду другие фильтры.

Обратите внимание, что для этого потребуется создание экземпляра класса перед доступом к этим объектам через него.

...