[英]What is the best way to validate attributes of a class that have many attributes in Python?
[英]What is the best way to set the attributes of a class at object creation time in Python?
当我定义一个类时,我经常想在创建对象时为该类设置一组属性。 到目前为止,我是通过将属性作为参数传递给init方法来实现的。 但是,我对此类代码的重复性感到不满:
class Repository(OrderedDict,UserOwnedObject,Describable):
def __init__(self,user,name,gitOriginURI=None,gitCommitHash=None,temporary=False,sourceDir=None):
self.name = name
self.gitOriginURI = gitOriginURI
self.gitCommitHash = gitCommitHash
self.temporary = temporary
self.sourceDir = sourceDir
...
在这个例子中,我必须输入 3 次name
3 次gitOriginURI
3 次gitCommitHash
3 次temporary
和 3 次sourceDir
。 只是为了设置这些属性。 这是非常无聊的代码编写。
我考虑过将这样的类更改为:
class Foo():
def __init__(self):
self.a = None
self.b = None
self.c = None
并初始化他们的对象,如:
f = Foo()
f.a = whatever
f.b = something_else
f.c = cheese
但是从文档的角度来看,这似乎更糟,因为类的用户需要知道需要设置哪些属性,而不是简单地查看类初始化程序的自动生成的help()
字符串。
有没有更好的方法来做到这一点?
我认为可能是一个有趣的解决方案的一件事是,如果有一个store_args_to_self()
方法,它将存储传递给 init 的每个参数作为 self 的属性。 有这样的方法吗?
让我对这种寻求更好方法感到悲观的一件事是,例如查看 cPython 源代码中date
对象的源代码,我看到了相同的重复代码:
def __new__(cls, year, month=None, day=None):
...
self._year = year
self._month = month
self._day = day
https://github.com/python/cpython/blob/master/Lib/datetime.py#L705
而 urwid,虽然被 setter 的使用稍微混淆了,但也有这样的“接受一个参数并将其设置为 self 的一个属性”的热土豆代码:
def __init__(self, caption=u"", edit_text=u"", multiline=False,
align=LEFT, wrap=SPACE, allow_tab=False,
edit_pos=None, layout=None, mask=None):
...
self.__super.__init__("", align, wrap, layout)
self.multiline = multiline
self.allow_tab = allow_tab
self._edit_pos = 0
self.set_caption(caption)
self.set_edit_text(edit_text)
if edit_pos is None:
edit_pos = len(edit_text)
self.set_edit_pos(edit_pos)
self.set_mask(mask)
https://github.com/urwid/urwid/blob/master/urwid/widget.py#L1158
您可以使用dataclasses
项目让它负责为您生成__init__
方法; 它还将处理表示、散列和相等性测试(以及可选的丰富比较和不变性):
from dataclasses import dataclass
from typing import Optional
@dataclass
class Repository(OrderedDict, UserOwnedObject, Describable):
name: str
gitOriginURI: Optional[str] = None
gitCommitHash: Optional[str] = None
temporary: bool = False
sourceDir: Optional[str] = None
dataclasses
在PEP 557 - Data Classes中定义,它已被接受包含在 Python 3.7 中。 该库将在 Python 3.6 及更高版本上运行(因为它依赖于 3.6 中引入的新变量注释语法)。
该项目的灵感来自attrs
项目,它仍然提供了更多的灵活性和选项,以及与 Python 2.7 和 Python 3.4 及更高版本的兼容性。
好吧,你可以这样做:
class Foo:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
foo = Foo(a=1, b='two', c='iii')
print(foo.a, foo.b, foo.c)
输出
1 two iii
但是如果你这样做了,在将kwargs
中的键转储到你的实例__dict__
之前检查它们是否正常可能是一个好主意。 ;)
这是一个稍微高级的示例,它对传入的参数进行了一些检查。
class Foo:
attrs = ['a', 'b', 'c']
''' Some stuff about a, b, & c '''
def __init__(self, **kwargs):
valid = {key: kwargs.get(key) for key in self.attrs}
self.__dict__.update(valid)
def __repr__(self):
args = ', '.join(['{}={}'.format(key, getattr(self, key)) for key in self.attrs])
return 'Foo({})'.format(args)
foo = Foo(a=1, c='iii', d='four')
print(foo)
输出
Foo(a=1, b=None, c=iii)
对于 Python 2.7,我的解决方案是从 namedtuple 继承并使用 namedtuple 本身作为 init 的唯一参数。 为了避免每次我们可以使用装饰器时都重载 new。 优点是我们有明确的init签名,没有 *args、**kwargs 以及很好的 IDE 建议
def nt_child(c):
def __new__(cls, p): return super(c, cls).__new__(cls, *p)
c.__new__ = staticmethod(__new__)
return c
ClassA_P = namedtuple('ClassA_P', 'a, b, foo, bar')
@nt_child
class ClassA(ClassA_P):
def __init__(self, p):
super(ClassA, self).__init__(*p)
self.something_more = sum(p)
a = ClassA(ClassA_P(1,2,3,4)) # a = ClassA(ClassA_P( <== suggestion a, b, foo, bar
print a.something_more # print a. <== suggesion a, b, foo, bar, something_more
我会在这里留下另一个食谱。 attrs
很有用,但也有缺点,其中主要是缺乏__init__
类的 IDE 建议。
拥有初始化链也很有趣,我们使用父类的实例作为__init__
第一个参数,而不是一个一个地提供它的所有属性。
所以我建议使用简单的装饰器。 它分析__init__
签名并基于它自动添加类属性(因此方法与 attrs 的方法相反)。 这为我们提供了__init__
很好的 IDE 建议(但缺乏对属性本身的建议)。
用法:
@data_class
class A:
def __init__(self, foo, bar): pass
@data_class
class B(A):
# noinspection PyMissingConstructor
def __init__(self, a, red, fox):
self.red_plus_fox = red + fox
# do not call parent constructor, decorator will do it for you
a = A(1, 2)
print a.__attrs__ # {'foo': 1, 'bar': 2}
b = B(a, 3, 4) # {'fox': 4, 'foo': 1, 'bar': 2, 'red': 3, 'red_plus_fox': 7}
print b.__attrs__
来源:
from collections import OrderedDict
def make_call_dict(f, is_class_method, *args, **kwargs):
vnames = f.__code__.co_varnames[int(is_class_method):f.__code__.co_argcount]
defs = f.__defaults__ or []
d = OrderedDict(zip(vnames, [None] * len(vnames)))
d.update({vn: d for vn, d in zip(vnames[-len(defs):], defs)})
d.update(kwargs)
d.update({vn: v for vn, v in zip(vnames, args)})
return d
def data_class(cls):
inherited = hasattr(cls, '_fields')
if not inherited: setattr(cls, '_fields', None)
__init__old__ = cls.__init__
def __init__(self, *args, **kwargs):
d = make_call_dict(__init__old__, True, *args, **kwargs)
if inherited:
# tricky call of parent __init__
O = cls.__bases__[0] # put parent dataclass first in inheritance list
o = d.values()[0] # first arg in my __init__ is parent class object
d = OrderedDict(d.items()[1:])
isg = o._fields[O] # parent __init__ signature, [0] shows is he expect data object as first arg
O.__init__(self, *(([o] if isg[0] else []) + [getattr(o, f) for f in isg[1:]]))
else:
self._fields = {}
self.__dict__.update(d)
self._fields.update({cls: [inherited] + d.keys()})
__init__old__(self, *args, **kwargs)
cls.__attrs__ = property(lambda self: {k: v for k, v in self.__dict__.items()
if not k.startswith('_')})
cls.__init__ = __init__
return cls
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.