开发者

How to implement a Required Property in Python

开发者 https://www.devze.com 2023-03-15 07:32 出处:网络
If I have a class such as below (only with many more properties), is there are clean way to note which fields are required before calling a particular method?

If I have a class such as below (only with many more properties), is there are clean way to note which fields are required before calling a particular method?

class Example():

    def __init__(self):
        pass

    @property
    """Have to use property methods to have docstrings..."""
    def prop1(self):
        return self._prop1
    @prop1.setter
    def task(self, value):
        # validation logic..
        self._prop1 = value

    def method(self):
        # check all required properties have been added

I could write an array by hand of all required propeties and loop through them in a method, but I was wondering if there is a cleaner way for example by implementing a @requiredProperty descriptor.

The class is used to generate a POST request for a web API. The request has 25+ parameters, some of which are required and some optional.

Rather than on the method calling the request having to loop through an array such as:

required_props = ['prop1','prop2',.开发者_运维问答...]

I was hoping there was a way in Python of adding a required decorator to properties so I wouldn't have to keep track by hand. E.g.

    @property, @required
    def prop1(self):
        return self._prop1


Would it not be best to make sure that all the attributes are supplied when an object is initialised? Then all your properties will be defined when you try to acces them.

For example,

class Example(object):
    def __init__(self, prop1, prop2):
        self.prop1 = prop1
        self.prop2 = prop2

Also, note from PEP8:

For simple public data attributes, it is best to expose just the attribute name, without complicated accessor/mutator methods.

So why use properties?


This should work the same way as in any OO language: A required property must be set during construction time. Calling the objects methods must never leave the object in a "bad" state, so that method can be called on any constructed object.

If the above doesn't hold true, you should think about refactoring your code.

Of course it is always possible to alter a python object to not be valid anymore by poking around in its guts. You don't do that unless you have a good reason. Don't bother checking for this, as your program should just blow up in your face whenever you do something stupid so you learn and stop.


It's hard to tell from your example what problem you are actually trying to solve, but I'm not convinced properties are the answer.

If you just want to check that an instance variable exists, you could use the special attribute __dict__, thus:

% cat ./test.py
#!/usr/bin/env python

class Example():

    def __init__(self):
        self.foo = None

    def method(self):
        assert 'foo' in self.__dict__
        assert 'bar' in self.__dict__

Example().method()


% ./test.py 
Traceback (most recent call last):
  File "./test.py", line 12, in <module>
    Example().method()
  File "./test.py", line 10, in method
    assert 'bar' in self.__dict__
AssertionError

But remember... EAFP: Easier to ask for forgiveness than permission.


As others have suggested, I suspect you are over-engineering. However, you could use a decorator to define 'required' attributes. Something along the lines of:

import functools

class MissingAttributeError(Exception):
    pass


def requires(*required_attrs):        
    def wrapper(method):

        @functools.wraps(method)
        def inner_wrapper(self, *args, **kargs):
            if not all(hasattr(self, attr) for attr in required_attrs):
                raise MissingAttributeError()
            return method(self, *args, **kargs)

        return inner_wrapper
    return wrapper


class Test(object):    
    def __init__(self, spam, eggs):
        self.spam = spam
        self.eggs = eggs

    @requires('spam', 'eggs', 'ham')
    def something(self):
        return 'Done'

t = Test('fu', 'bar')
t.something() ## fails
t.ham = 'nicer than spam'
t.something() ## succeeds

Although defining attribute dependencies this way has a certain neatness to it, I'm not sure I recommend it.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号