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)
7
  • 1
    Does it have to be a metaclass? Could it be a parent class instead? Or maybe a decorator? Commented Mar 23, 2018 at 17:56
  • Just make sure only string value are allowed. Commented Mar 23, 2018 at 17:58
  • @Aran-Fey I know I can do it with a parent class or decorator, but I want to try to use it with a metaclass. Commented Mar 23, 2018 at 17:58
  • You want A().val to only allow strings? Better tool for the job is descriptors, not metaclasses. Commented Mar 23, 2018 at 17:59
  • @wim Agree with you, but I want to see can I do it with a metaclass? Commented Mar 23, 2018 at 17:59

2 Answers 2

4

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
Sign up to request clarification or add additional context in comments.

2 Comments

–1 this does not prevent later assignment of a non-string to the attribute val.
this could work with dataclasses module which allows to "freeze" object after instantiation - still vim is right that's not the way python works :)
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.