KevinG
KevinG

Reputation: 956

How to trigger a setter when declaring a class property?

I have a class; let's call it Foo. It has a key_type class attribute, which contains a type:

class Key: pass

class Foo:
  key_type = Key

I would like to run some method on the key type when it's initialized(*) and whenever it changes.

So I made key_type a property in a metaclass:

class Key: pass
class OtherKey: pass

class MetaFoo(type):
  _key_type = None

  @property
  def key_type(cls):
    return cls._key_type

  @key_type.setter
  def key_type(cls, value):
    print(f'Setting key_type to {value}')
    cls._key_type = value

class Foo(metaclass=MetaFoo):
  key_type = Key

if __name__ == "__main__":
  print(f"Foo's key type: {Foo.key_type}")
  Foo.key_type = OtherKey
  print(f"Foo's key type: {Foo.key_type}")

Output:

Foo's key type: None
Setting key_type to <class '__main__.OtherKey'>
Foo's key type: <class '__main__.OtherKey'>

It seems like the definition of _key_type in the meta class overrode the definition of key_type in the main class. But most importantly, the setter was not called with the Key type.

Expected output:

Setting key_type to <class '__main__.Key'>
Foo's key type: <class '__main__.Key'>
Setting key_type to <class '__main__.OtherKey'>
Foo's key type: <class '__main__.OtherKey'>

(*) The reason I also want it to happen when it's initialized is that Foo can be inherited from. I want to know (either in MetaFoo or in Foo) if a child class uses a different key_type.

Upvotes: 1

Views: 182

Answers (1)

Philip Tzou
Philip Tzou

Reputation: 6458

The definition of key_type in class Foo actually added a key-value pair to the third parameter (which is a dict) for the initialization of MetaFoo, nothing else it will do.

Therefore, you can manipulate the initialization of MetaFoo to explicitly call your setter method. This can be done by overriding __init__ method of your metaclass:

class Key: pass
class OtherKey: pass

class MetaFoo(type):
  _key_type = None

  @property
  def key_type(cls):
    return cls._key_type

  @key_type.setter
  def key_type(cls, value):
    print(f'Setting key_type to {value}')
    cls._key_type = value

  def __init__(self, name, bases, kw):
    super(MetaFoo, self).__init__(name, bases, kw)
    for key, val in kw.items():
       setattr(self, key, val)


class Foo(metaclass=MetaFoo):
  key_type = Key

if __name__ == "__main__":
  print(f"Foo's key type: {Foo.key_type}")
  Foo.key_type = OtherKey
  print(f"Foo's key type: {Foo.key_type}")

The output:

Setting key_type to <class '__main__.Key'>
Foo's key type: <class '__main__.Key'>
Setting key_type to <class '__main__.OtherKey'>
Foo's key type: <class '__main__.OtherKey'>

Upvotes: 4

Related Questions