动态定义依赖于先前定义的属性的属性
Posted
技术标签:
【中文标题】动态定义依赖于先前定义的属性的属性【英文标题】:Dynamically define attributes that depend on earlier defined attributes 【发布时间】:2019-04-17 23:15:39 【问题描述】:我想要一个表示路径root
的对象和使用os.path.join(root)
构造的任意数量的子目录。我想以self.root
、self.path_a
、self.path_b
等形式访问这些路径...除了通过self.path_a
直接访问它们之外,我还希望能够遍历它们。不幸的是,下面的方法不允许通过 attr.astuple(paths)
迭代它们
下面的第一段代码是我想出的。它有效,但对我来说有点hacky。由于这是我第一次使用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
【问题讨论】:
这并不是attrs
的真正用途。你到底想完成什么?
@roeen30 我对代码示例进行了重大更改,希望更清楚地说明我试图解决的问题。
你想做什么更清楚,但不知道为什么。优秀的pathlib
提供了惯用的路径操作 - 如果您还没有,请查看它。由于attrs
的主要目标是创建快速记录类,它在这里并不能真正帮助您,因为您有一个具有一个预定属性的类(root
)。看起来像是试图通过一个圆孔安装一个方形钉。
附带说明,如果您无法避免使用__get/setattribute__()
,则使用getattr()
和setattr()
内置函数会更清楚。
【参考方案1】:
第一次尝试:您不能只是将随机数据附加到该类并希望 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')
【讨论】:
【参考方案2】:无法猜测您为什么要以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'])
输出
'root': '/home/runner', 'static': '/home/runner/static', 'bin': '/home/runner/bin', 'source': '/home/runner/source'
/home/runner/bin
您可以使这更复杂。只是一个例子。希望对您有所帮助。
【讨论】:
这样可以解决问题,但我有兴趣开始使用 attrs。以上是关于动态定义依赖于先前定义的属性的属性的主要内容,如果未能解决你的问题,请参考以下文章