4

I'm having some difficulty with Python decorators, and I think it has to do with the fact I am passing a class as a parameter to a function decorator, when the function being decorated is a method of the class being passed.

I have no clearer explanation of the problem than that, so hopefully some code will help:

from typeChecking import *

class Vector:
    @accepts(Vector, float, float, float) #Vector hasn't been defined yet... In c++ I could forward declare...
    def __init__(self, x, y, z):
        self._x = float(x)
        self._y = float(y)
        self._z = float(z)
    ...

I don't think the definition of @accepts is important, but I'll leave it here just in case:

def accepts(*types):
    def check_accepts(f):
        assert len(types) == f.func_code.co_argcount
        def new_f(*args, **kwds):
            for (a, t) in zip(args, types):
                assert isinstance(a, t), \
                       "arg %r does not match %s" % (a,t)
            return f(*args, **kwds)
        new_f.func_name = f.func_name
        return new_f
    return check_accepts

I get the following error:

File "raytracerBase.py", line 41, in <module>
  class Vector:
File "raytracerBase.py", line 42, in Vector
  @accepts(Vector, float, float, float)
NameError: name 'Vector' is not defined

I think my comment explains what I think is happening: The class hasn't been defined yet (because, I'm in the process of defining it) and therefore I can't pass it.

Is there a neat workaround that isn't:

assert isinstance(self, Vector), "Something is wrong... very wrong..."

I am aware I'm type checking more than is deemed necessary, but I would like to know how to solve this sort of problem anyway.

Edit: I'm also aware that @accepts isn't actually valid Python. But, It's the outline of the code I intend to implement.

max
  • 49,282
  • 56
  • 208
  • 355
theoemms
  • 68
  • 7
  • 3
    I don't have a solution to this one, but I do want to point out that generally you don't want to have to do argument type checking in Python. Treat it like a duck, and catch any exceptions it might throw if you expect a quack and it barks. – Adam Smith Feb 08 '15 at 20:09
  • 1
    Indeed, I don't think you're going to get an answer you want. Most are going to focus on the fact that what you're trying to do is *incredibly* un-pythonic. Duck typing is a fundamental part of python. – aruisdante Feb 08 '15 at 20:11
  • That said, in this particular case you SHOULD be able to do `@accepts(object, float, float, float)` and be fine, since `self` is the argument you're type checking, which is ALWAYS the instance that's calling its method – Adam Smith Feb 08 '15 at 20:12
  • 2
    And if you wanted the type info for some kind of static typing just-in-time compilation for a speedup, then either do your own low-level work in `llvmpy` or `llvmlite`, or just use `numba` (which already has a type annotation decorator for this purpose). – ely Feb 08 '15 at 20:13
  • 2
    Alternatively, you can use string-based reflection to essentially defer evaluation of the type to runtime (when the class will exist). See here: http://stackoverflow.com/questions/17522706/how-to-pass-a-class-variable-to-a-decorator-inside-class-definition?rq=1 – aruisdante Feb 08 '15 at 20:14
  • The issue I have with that approach is that when the error will arise is unpredictable. For example, if I create two vectors: a = Vector("a", "b", "c") and b = Vector(1, 2, 3) then a.dot(b) would return "abbccc" and throw no errors until I use the result. This is too late for someone who doesn't understand how my module works. – theoemms Feb 08 '15 at 20:15
  • 1
    @theoemms unless the user is specifically trying to invoke that functionality. Never assume you know what the user wants to do -- just document what your class DOES and let them make new stuff from it. See the [itertools recipes](https://docs.python.org/2/library/itertools.html#recipes) page in the docs. – Adam Smith Feb 08 '15 at 20:19
  • @theoemms That's always a risk with a dynamic, duck-typed language. While you can handle obvious problems, at some point you have to rely on users using your module correctly. Remember, the method in the decorator is called *every time* the decorated method is called. That means that *every time* your type-checked methods are used, you're asking your user to pay an enormous run-time cost for functionality they may or may not want. – aruisdante Feb 08 '15 at 20:19
  • 2
    For instance, I'm fairly certain that no one intended to combine `iter`, `zip`, and multiplying one-element lists to be used as a `grouper` recipe, but someone realized it worked, was concise, well-understood, and was very flexible. – Adam Smith Feb 08 '15 at 20:21
  • In this instance I'm going to drop typechecking as suggested (Because it's clearly overkill). However, I'm still interested in possible solutions to problems of this type. Is string-based reflection (I'd upvote @ariusdante if I could work out how) the best way to solve problems of this type? – theoemms Feb 08 '15 at 20:30

1 Answers1

5

The short answer is No, you can't refer to your class like that before you've finished defining it.

Type-checking is a subject under active discussion among the Python development team at the moment, and to the extent that there's a consensus, it's that function annotations are the way forward: PEP 484 describes the direction Guido wants to take in implementing it.

The workaround for forward references proposed in that PEP is to just use a string instead:

When a type hint contains names that have not been defined yet, that definition may be expressed as a string, to be resolved later. For example, instead of writing:

def notify_by_email(employees: Set[Employee]): ...

one might write:

def notify_by_email(employees: 'Set[Employee]'): ...
Community
  • 1
  • 1
Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160