Preventing a class from being instantiated from Python?

2,151 views
Skip to first unread message

lu...@amacapital.net

unread,
Aug 29, 2013, 12:11:23 AM8/29/13
to cython...@googlegroups.com
Is there any way to prevent Python code from instantiating a class?  Adding an __init__ method that raises an exception makes it harder, but it's ugly and calling __new__ directly still works.

Ideally there'd be some simple annotation to remove the tp_new method while still allowing Cython code to instantiate the class.

--Andy

Stefan Behnel

unread,
Aug 29, 2013, 12:25:00 AM8/29/13
to cython...@googlegroups.com
lu...@amacapital.net, 29.08.2013 06:11:
> Is there any way to prevent Python code from instantiating a class? Adding
> an __init__ method that raises an exception makes it harder, but it's ugly
> and calling __new__ directly still works.

No, there isn't. You can declare it @cython.internal to remove it from the
module dict, and add a failing __init__ method if you like, but if you can
instantiate it, others can, too.


> Ideally there'd be some simple annotation to remove the tp_new method while
> still allowing Cython code to instantiate the class.

Is this a security concern or just for safety?

Stefan

lu...@amacapital.net

unread,
Aug 29, 2013, 4:44:12 PM8/29/13
to cython...@googlegroups.com, stef...@behnel.de

Just for safety.  The object starts out in a bad state until the Cython code gets it fully set up.

I suppose I can make __new__ take a typed parameter that's either @cython.internal or just not a Python type at all.

Stefan Behnel

unread,
Aug 30, 2013, 12:11:56 AM8/30/13
to cython...@googlegroups.com
lu...@amacapital.net, 29.08.2013 22:44:
> On Wednesday, August 28, 2013 9:25:00 PM UTC-7, Stefan Behnel wrote:
>> lu...@amacapital.net 29.08.2013 06:11:
>>> Is there any way to prevent Python code from instantiating a class?
>>> Adding
>>> an __init__ method that raises an exception makes it harder, but it's
>>> ugly and calling __new__ directly still works.
>>
>> No, there isn't. You can declare it @cython.internal to remove it from the
>> module dict, and add a failing __init__ method if you like, but if you can
>> instantiate it, others can, too.
>>
>>> Ideally there'd be some simple annotation to remove the tp_new method
>>> while still allowing Cython code to instantiate the class.
>>
>> Is this a security concern or just for safety?
>
> Just for safety. The object starts out in a bad state until the Cython
> code gets it fully set up.

Can't you use __cinit__() to set it up?

Stefan

Sturla Molden

unread,
Aug 30, 2013, 9:29:32 AM8/30/13
to cython...@googlegroups.com
On 8/30/13 6:11 AM, Stefan Behnel wrote:
> Can't you use __cinit__() to set it up?
>
Or use a Python package?


In Cython file foo/_bar.pyx:
=================

cdef class MyCythonClass:
def setup(self): pass


In Python file foo/bar.py:
================

from _bar import MyCythonClass
class MyPythonClass(MyCythonClass):
def __init__(self):
MyCythonClass.__init__(self)
self.setup()


In Python file foo/__init__.py:
==================

from bar import MyPythonClass
__all__ = ['MyPythonClass']


Of course there are numerous other options involving exporting factory
functions, overloading __new__, using metaclasses, or just utilizing
__cinit__.

However: The main thing is "we are all consenting adults". Python
security is not about making stupid decisions totally impossible, as is
common in C++, .NET or Java. In Python we just make dumb mistakes less
likely, and trust that he user is a consenting adult. If the user
nevertheless wants to be a stupid child, and use "MyCythonClass"
directly -- even though it wasn't exported by the package, and even
using it without calling .setup() first -- well that's their bad. Making
dumb ideas completely impossible is not Pythonic anyway.

Sturla










Andy Lutomirski

unread,
Aug 30, 2013, 10:36:21 AM8/30/13
to cython...@googlegroups.com
That was my initial thought, but I think it doesn't work. My class
wraps a C type, and I need to give it a C object to initialize it. As
far as I can tell, there's no easy way to pass a C object to __cinit__
because cdef __cinit__ is illegal.

--Andy

Sturla Molden

unread,
Aug 30, 2013, 11:51:56 AM8/30/13
to cython...@googlegroups.com
On 8/30/13 4:36 PM, Andy Lutomirski wrote:
> That was my initial thought, but I think it doesn't work. My class
> wraps a C type, and I need to give it a C object to initialize it. As
> far as I can tell, there's no easy way to pass a C object to __cinit__
> because cdef __cinit__ is illegal. --Andy

It sounds like you have a design problem. Migrate all the code you use
to allocate the C object to a class initializer. Don't pass a C object
to __cinit__. If your Cython code can get its hands on the C object, you
can migrate that Cython code to a class initializer.

Sturla

Robert Bradshaw

unread,
Aug 30, 2013, 12:44:51 PM8/30/13
to cython...@googlegroups.com
+1. That failing, do 

cdef unexposed_value = object()
cdef class MyType:
    def __cinit__(self, value):
        assert value == unexposed_value

cdef create_MyType(c_params):
     x = MyType(unexposed_value)
     [do setup]
     return x

- Robert


Andy Lutomirski

unread,
Sep 13, 2013, 9:30:27 AM9/13/13
to cython...@googlegroups.com
On Fri, Aug 30, 2013 at 9:44 AM, Robert Bradshaw <robe...@gmail.com> wrote:
> On Fri, Aug 30, 2013 at 8:51 AM, Sturla Molden <stu...@molden.no> wrote:
>>
>> On 8/30/13 4:36 PM, Andy Lutomirski wrote:
>>>
>>> That was my initial thought, but I think it doesn't work. My class wraps
>>> a C type, and I need to give it a C object to initialize it. As far as I can
>>> tell, there's no easy way to pass a C object to __cinit__ because cdef
>>> __cinit__ is illegal. --Andy
>>
>>
>> It sounds like you have a design problem. Migrate all the code you use to
>> allocate the C object to a class initializer. Don't pass a C object to
>> __cinit__. If your Cython code can get its hands on the C object, you can
>> migrate that Cython code to a class initializer.

The C object comes from outside, so I can't control where it gets
allocated. (The project is the Python bindings for Cap'n Proto, and
the class is a wrapper around a subobject of something else. In
principle, I suppose that __cinit__ could take a parent object and a
descriptor as parameters, but this would be a much more convoluted way
to do this for little gain.)

>
>
> +1. That failing, do
>
> cdef unexposed_value = object()
> cdef class MyType:
> def __cinit__(self, value):
> assert value == unexposed_value
>
> cdef create_MyType(c_params):
> x = MyType(unexposed_value)
> [do setup]
> return x

I think this is more effort and overhead than is really justified, so
I'll just leave it alone. Thanks, though!

--Andy
Reply all
Reply to author
Forward
0 new messages