2

I was planning to use metaclass to validate the constructor argument in Python3, but it seems __new__method has no access to the variable val, because the class A() has not been instantiated yet.

Sow what's the correct way to do it?

class MyMeta(type):
    def __new__(cls, clsname, superclasses, attributedict):
        print("clsname: ", clsname)
        print("superclasses: ", superclasses)
        print("attributedict: ", attributedict)
        return type.__new__(cls, clsname, superclasses, attributedict)

class A(metaclass=MyMeta):
    def __init__(self, val):
        self.val = val

A(123)
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
user1187968
  • 7,154
  • 16
  • 81
  • 152

2 Answers2

4

... it seems __new__method has no access to the variable val, because the class A() has not been instantiated yet.

Exactly.

So what's the correct way to do it?

Not with a metaclass.

Metaclasses are for fiddling with the creation of the class object itself, and what you want to do is related to instances of the class.

Best practice: don't type-check the val at all. Pythonic code is duck-typed. Simply document that you expect a string-like argument, and users who put garbage in get garbage out.

wim
  • 338,267
  • 99
  • 616
  • 750
3

wim is absolutely correct that this isn't a good use of metaclasses, but it's certainly possible (and easy, too).

Consider how you would create a new instance of your class. You do this:

A(123)

In other words: You create an instance by calling the class. And python allows us to create custom callable objects by defining a __call__ method. So all we have to do is to implement a suitable __call__ method in our metaclass:

class MyMeta(type):
    def __call__(self, val):
        if not isinstance(val, str):
            raise TypeError('val must be a string')

        return super().__call__(val)

class A(metaclass=MyMeta):
    def __init__(self, val):
        self.val = val

And that's it. Simple, right?

>>> A('foo')
<__main__.A object at 0x007886B0>
>>> A(123)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "untitled.py", line 5, in __call__
    raise TypeError('val must be a string')
TypeError: val must be a string
Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
  • 1
    –1 this does not prevent later assignment of a non-string to the attribute val. – wim Mar 23 '18 at 18:45
  • this could work with dataclasses module which allows to "freeze" object after instantiation - still vim is right that's not the way python works :) – Bartek Maciejewski Feb 02 '22 at 13:55