【问题标题】:Workaround for not having __set_name__ in Python versions before 3.6在 3.6 之前的 Python 版本中没有 __set_name__ 的解决方法
【发布时间】: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 中引入。

标签: python python-2.7


【解决方案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 显式子类化,这让我在起草此答案的第一个版本时感到困惑。

    【讨论】:

      猜你喜欢
      • 2019-10-16
      • 2019-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-11-24
      • 2018-12-04
      • 2018-06-16
      • 2015-04-29
      相关资源
      最近更新 更多