Динамически определять атрибуты, которые зависят от ранее определенных атрибутов - PullRequest
0 голосов
/ 15 ноября 2018

Я хочу, чтобы объект представлял путь root и произвольное количество подкаталогов, созданных с помощью os.path.join(root). Я хочу получить доступ к этим путям в форме self.root, self.path_a, self.path_b и т. Д. ... В дополнение к прямому доступу к ним через self.path_a, я хочу иметь возможность перебирать их. К сожалению, приведенный ниже подход не позволяет перебирать их через attr.astuple(paths)

Первый фрагмент кода ниже - это то, что я придумал. Это работает, но чувствует себя немного взломанным для меня. Так как это мое первое использование attrs , мне интересно, есть ли более интуитивный / идиоматический способ приблизиться к этому. Мне потребовалось много времени, чтобы понять, как написать довольно простой класс ниже, поэтому я подумал, что могу упустить что-то очевидное.

Мой подход

@attr.s
class Paths(object):
    subdirs = attr.ib()
    root = attr.ib(default=os.getcwd())
    def __attrs_post_init__(self):
        for name in self.subdirs:
            subdir = os.path.join(self.root, name)
            object.__setattr__(self, name, subdir)

    def mkdirs(self):
        """Create `root` and `subdirs` if they don't already exist."""
        if not os.path.isdir(self.root):
            os.mkdir(self.root)
        for subdir in self.subdirs:
            path = self.__getattribute__(subdir)
            if not os.path.isdir(path):
                os.mkdir(path)

выход

>>> p = Paths(subdirs=['a', 'b', 'c'], root='/tmp')
>>> p
Paths(subdirs=['a', 'b', 'c'], root='/tmp')
>>> p.a
'/tmp/a'
>>> p.b
'/tmp/b'
>>> p.c
'/tmp/c'

Следующая была моя первая попытка, которая не работает.

Неудачная попытка

@attr.s
class Paths(object):
    root = attr.ib(default=os.getcwd())
    subdir_1= attr.ib(os.path.join(root, 'a'))
    subdir_2= attr.ib(os.path.join(root, 'b'))

выход

------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-31-71f19d55e4c3> in <module>()
    1 @attr.s
----> 2 class Paths(object):
    3     root = attr.ib(default=os.getcwd())
    4     subdir_1= attr.ib(os.path.join(root, 'a'))
    5     subdir_2= attr.ib(os.path.join(root, 'b'))

<ipython-input-31-71f19d55e4c3> in Paths()
    2 class Paths(object):
    3     root = attr.ib(default=os.getcwd())
--> 4     subdir_1= attr.ib(os.path.join(root, 'a'))
    5     subdir_2= attr.ib(os.path.join(root, 'b'))
    6

~/miniconda3/lib/python3.6/posixpath.py in join(a, *p)
    76     will be discarded.  An empty last part will result in a path that
    77     ends with a separator."""
--> 78     a = os.fspath(a)
    79     sep = _get_sep(a)
    80     path = a

TypeError: expected str, bytes or os.PathLike object, not _CountingAttr

Ответы [ 2 ]

0 голосов
/ 16 ноября 2018

Первая попытка: вы не можете просто прикрепить случайные данные к классу и надеяться, что attrs (в данном случае astuple) их заберет.attrs специально пытается избежать магии и догадок, что означает, что вы действительно должны определить свои атрибуты в классе.

Вторая попытка: вы не можете использовать имя атрибута в области видимости класса (то есть в пределах class Paths:, но внеметод, потому что - как говорит вам Python - на данный момент они все еще являются внутренними данными, которые используются @attr.s.

. Самый элегантный подход, который я могу придумать, - это универсальная фабрика, которая принимает путь в качестве аргумента.и строит полный путь:

In [1]: import attr

In [2]: def make_path_factory(path):
   ...:     def path_factory(self):
   ...:         return os.path.join(self.root, path)
   ...:     return attr.Factory(path_factory, takes_self=True)

, который вы можете использовать следующим образом:

In [7]: @attr.s
   ...: class C(object):
   ...:     root = attr.ib()
   ...:     a = attr.ib(make_path_factory("a"))
   ...:     b = attr.ib(make_path_factory("b"))

In [10]: C("/tmp")
Out[10]: C(root='/tmp', a='/tmp/a', b='/tmp/b')

In [11]: attr.astuple(C("/tmp"))
Out[11]: ('/tmp', '/tmp/a', '/tmp/b')

attrs - это attrs, вы, конечно, можете пойти дальше и определить свою собственную оболочку attr.ib:

In [12]: def path(p):
    ...:     return attr.ib(make_path_factory(p))

In [13]: @attr.s
    ...: class D(object):
    ...:     root = attr.ib()
    ...:     a = path("a")
    ...:     b = path("b")
    ...:

In [14]: D("/tmp")
Out[14]: D(root='/tmp', a='/tmp/a', b='/tmp/b')
0 голосов
/ 15 ноября 2018

Не могу догадаться, почему вы хотите получить доступ как self.paths.path.Но вот что я бы сделал:

class D(object):
    root = os.getcwd()
    paths = dict()

    def __init__(self, paths=[]):
        self.paths.update({'root': self.root})
        for path in paths:
            self.paths.update({path: os.path.join(self.root, path)})

    def __str__(self):
        return str(self.paths)    

d = D(paths=['static', 'bin', 'source'])
print(d)
print(d.paths['bin'])

output

{'root': '/home/runner', 'static': '/home/runner/static', 'bin': '/home/runner/bin', 'source': '/home/runner/source'}
/home/runner/bin

Вы можете сделать это более сложным.Просто пример.Надеюсь, это поможет.

...