在 3.6 之前的 Python 版本中没有 __set_name__ 的解决方法
Posted
技术标签:
【中文标题】在 3.6 之前的 Python 版本中没有 __set_name__ 的解决方法【英文标题】:Workaround for not having __set_name__ in Python versions before 3.6 【发布时间】:2019-04-13 23:06:37 【问题描述】:在 Python 3.6 中,我可以使用 __set_name__
挂钩来获取描述符的类属性名称。如何在 python 2.x 中实现这一点?
这是在 Python 3.6 中运行良好的代码:
class IntField:
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError('expecting integer')
instance.__dict__[self.name] = value
def __set_name__(self, owner, name):
self.name = name
class Example:
a = IntField()
【问题讨论】:
我稍微编辑了你的问题。__set_name__
在 3.6 中引入。
【参考方案1】:
您可能正在寻找元类,使用它您可以在创建类时处理类属性。
class FooDescriptor(object):
def __get__(self, obj, objtype):
print('calling getter')
class FooMeta(type):
def __init__(cls, name, bases, attrs):
for k, v in attrs.iteritems():
if issubclass(type(v), FooDescriptor):
print('FooMeta.__init__, attribute name is ""'.format(k))
class Foo(object):
__metaclass__ = FooMeta
foo = FooDescriptor()
f = Foo()
f.foo
输出:
FooMeta.__init__, attribute name is "foo"
calling getter
如果您需要在创建类之前更改它,您需要在您的元类中覆盖 __new__
而不是 __init__
。有关此主题的更多信息,请参阅此答案:Is there any reason to choose __new__ over __init__ when defining a metaclass?
【讨论】:
【参考方案2】:有各种不同程度的hackish的解决方案。我一直喜欢为此使用类装饰器。
class IntField(object):
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__[self.name]
def __set__(self, instance, value):
if not isinstance(value, int):
raise ValueError('expecting integer')
instance.__dict__[self.name] = value
def with_intfields(*names):
def with_concrete_intfields(cls):
for name in names:
field = IntField()
field.name = name
setattr(cls, name, field)
return cls
return with_concrete_intfields
你可以这样使用它:
@with_intfields('a', 'b')
class Example(object):
pass
e = Example()
演示:
$ python2.7 -i clsdec.py
>>> [x for x in vars(Example) if not x.startswith('_')]
['a', 'b']
>>> Example.a.name
'a'
>>> e.a = 3
>>> e.b = 'test'
[...]
ValueError: expecting integer
确保从 Python 2.7 中的 object
显式子类化,这让我在起草此答案的第一个版本时感到困惑。
【讨论】:
以上是关于在 3.6 之前的 Python 版本中没有 __set_name__ 的解决方法的主要内容,如果未能解决你的问题,请参考以下文章
使用 pytest 测试 __init__.py 中可选依赖项的导入:Python 3.5 /3.6 的行为不同
无法在 Python 3.6 中使用 _pickle 加载 Python 2.x .dat 文件 [重复]
python 3.6 cx_Oracle.DatabaseError: DPI-1050