开发者

Initializing object variables - a Java approach, a Python approach?

I have an object that needs to have some 4-5 values passed to it. To illustrate:

class Swoosh(开发者_JAVA技巧):
    spam = ''
    eggs = ''
    swallow = ''
    coconut = ''

    [... methods ...]

Right now, the way to use Swoosh is:

swoosh = Swoosh()
swoosh.set_spam('Spam!')
swoosh.set_eggs('Eggs!')
swoosh.set_swallow('Swallow!')
swoosh.set_coconut('Migrated.')

I'm having doubts whether this is Pythonic or is this too much Java influence. Or is it just necessary? Also, in case you're wondering why I'm using setters instead of simply accessing the object variables - some basic validation has to be done there, hence the set_ methods.

I reckon I could provide a different way to initialize a Swoosh - provide an __init__() that would accept a dict/list?

Anyway, I'm just looking for help/advice from someone more experienced with this stuff.

Thanks in advance for any input on this.


Firstly you're using old style classes. You really, really should be using new style classes that inherit from object:

class Swoosh(object):

Defining an __init__ method that takes arguments is definitely the Pythonic way of doing things:

def __init__(self,spam,eggs,swallow,coconut):
    self.spam = spam
    self.eggs = eggs
    self.swallow = swallow
    self.coconut = coconut

This would allow you to do:

s = Swoosh('Spam!','Eggs','Swallow','Migrated')

Like any other Python function the __init__ method can have default values for arguments, e.g.

def __init__(self,spam,eggs,swallow,coconut='Migrated.'):
        self.spam = spam
        self.eggs = eggs
        self.swallow = swallow
        self.coconut = coconut

If you want to validate attributes as they're set you should use standard property attributes rather than creating your own setters. If you do it this way you can use myobject.spam in your code like with an ordinary attribute but your setter method is still run.

For example:

@property
def spam(self):
    """I'm the 'spam' property."""
    return self._spam

@spam.setter
def spam(self, value):
    if not value.endswith("!"):
        raise ValueError("spam must end with !")
    # Store the value in "private" _spam attribute
    self._spam = value

@spam.deleter
def spam(self):
    del self._spam

Note: property will not work unless you're using new-style classes as described above.

In your example you have:

class Swoosh():
    spam = ''
    eggs = ''
    swallow = ''
    coconut = ''

This is often presented as a quick way of setting defaults for attributes for an object. It's fine but you need to understand what is actually happening. What's going on is that you're setting attributes on the class. If an object doesn't have an attribute Python will look to see if it's defined on the class and return the value from there (as this is what you want for methods).

If you want to set default values for object attributes you're much better off setting default values for the arguments in __init__ as described above rather than using class attributes.


First of all, this:

class Swoosh():
    spam = ''
    eggs = ''
    swallow = ''
    coconut = ''

sets class attributes spam, eggs, etc. Your set_spam method would then presumably go ahead and create object attributes of the same name that hide the class attributes. In other words, defining these attributes have no effect and just confuses things.

I would do it like this, if all the variables are optional:

class Swoosh():
    def __init__(self, spam="", eggs="", swallow="", coconut=""):
        self.spam = spam
        self.eggs = eggs
        self.swallow = swallow
        self.coconut = coconut

Then you can create objects like this:

o1 = Swoosh(eggs="Eggs!", coconut="Carried by the husk")

If, say, spam and eggs are mandatory, then substitute with

    def __init__(self, spam, eggs, swallow="", coconut=""):

And don't use setters and getters at all. If you need to later on, you can seamlessly replace a regular attribute with a property by adding code like this to your class:

    def get_spam(self):
        return self._spam
    def set_spam(self, value):
        self._spam = " ".join([str(value)] * 5)
    spam = property(get_spam, set_spam)

With the above change, this:

o2 = Swoosh(eggs="yes", spam="spam")
print o2.spam

prints

spam spam spam spam spam

Note: As Dave Webb points out in his answer, you need to subclass object for properties to work, unless you use python 3.0+, in which case classes implicitly subclass object. Oh, and do follow Sean Vieira's link to the Python is not Java article. It's a must-read.


Basic validation can be done by overriding the appropriate get and set methods on the class (if the validation needs to be done in all cases) or by altering the specific property with the property built-in. Remember, Python is not Java (Of course, Java is not Python, either).

As for how you want to do your class creation, you have a couple of choices:

1) Use the __init__ method with keyword args to let people provide as much or as little as they want to your constructor:

class Swoosh(object): # Get new-object goodness
    def __init__(self, spam='', eggs='', swallow='', coconut=''):
        # We are going to be using properties in this example
        self._spam = spam
        self._eggs = eggs
        self._swallow = swallow
        self._coconut = coconut

2) Continue doing things the way you are, but update the class attributes to be instance attributes:

class Swoosh(object): # Get new-object goodness
    def __init__(self):
        self.spam = ''
        self.eggs = ''
        self.swallow = ''
        self.coconut = ''

The way you are doing it currently, every instance of Swoosh shares the same values for these variables, and changing the value of spam on Swoosh will change it for every instance of Swoosh that hasn't set up an instance attribute with the same name (Which probably isn't what you want.)

Either way, you should look into removing the getters and setters and replacing them with properties (when you do this will of course depend on how public and widely used this API is):

# Inside class Swoosh's definition
@property
def spam(self):
    return self._spam
@spam.setter
def spam(self, value):
    # Validate that it's really SPAM, not WHAM!
    # And only then replace the underlying value
    self._spam = value
@spam.deleter
def spam(self):
    del self._spam
# Rinse and repeat for other properties that need access control


To initialize Swoosh values you have multiple choices :

First, the constructor args :

class Swoosh(object):
   def __init__(self, spam, eggs, swallow, coconut):
      self.spam = spam
      self.eggs = eggs
      self.swallow = swallow
      self.coconut = coconut

And then instanciate your obejct like this :

swoosh = Swoosh("spam", "eggs", "swallow", "coconut")

If you need more control on the way you get/set the values, you can the use property method.

class Swoosh():
   def __init__(self, spam, eggs, swallow, coconut):
      self.spam = spam
      self.eggs = eggs
      self.swallow = swallow
      self.coconut = coconut

  def set_spam(self, value):
     #do verification stuff
     self._spam = value

  def get_spam(self):
     #do what you want/need before get the value
     return self._spam

  spam = property(get_spam, set_spam)

And then, every call like swoosh.spam = spam_value will call the set_spam method, and foo = swoosh.spam will call the get_spam method.


The __init__() function is what you want to use to set values when the object is first created.

Eg.

class Swoosh:
    __init__(self, arg1, arg2, arg3):
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜