How to implement property() with dynamic name (in

2019-04-05 23:19发布

问题:

I am programming a simulations for single neurons. Therefore I have to handle a lot of Parameters. Now the Idea is that I have two classes, one for a SingleParameter and a Collection of parameters. I use property() to access the parameter value easy and to make the code more readable. This works perfect for a sinlge parameter but I don't know how to implement it for the collection as I want to name the property in Collection after the SingleParameter. Here an example:

class SingleParameter(object):
  def __init__(self, name, default_value=0, unit='not specified'):
    self.name = name
    self.default_value = default_value
    self.unit = unit
    self.set(default_value)
  def get(self):
    return self._v
  def set(self, value):
    self._v = value
  v = property(fget=get, fset=set, doc='value of parameter')

par1 = SingleParameter(name='par1', default_value=10, unit='mV')
par2 = SingleParameter(name='par2', default_value=20, unit='mA')

# par1 and par2 I can access perfectly via 'p1.v = ...'
# or get its value with 'p1.v'

class Collection(object):
  def __init__(self):
    self.dict = {}
  def __getitem__(self, name):
    return self.dict[name] # get the whole object
    # to get the value instead:
    # return self.dict[name].v
  def add(self, parameter):
    self.dict[parameter.name] = parameter
    # now comes the part that I don't know how to implement with property():
    # It shoule be something like
    # self.__dict__[parameter.name] = property(...) ?

col = Collection()
col.add(par1)
col.add(par2)
col['par1'] # gives the whole object

# Now here is what I would like to get:
# col.par1 -> should result like col['par1'].v
# col.par1 = 5 -> should result like col['par1'].v = 5

Other questions that I put to understand property():

  • Why do managed attributes just work for class attributes and not for instance attributes in python?
  • How can I assign a new class attribute via __dict__ in python?

回答1:

Using the same get/set functions for both classes forces you into an ugly hack with the argument list. Very sketchy, this is how I would do it:

In class SingleParameter, define get and set as usual:

def get(self):
  return self._s
def set(self, value):
  self._s = value

In class Collection, you cannot know the information until you create the property, so you define the metaset/metaget function and particularize them only later with a lambda function:

def metaget(self, par):
  return par.s
def metaset(self, value, par):
  par.s = value
def add(self, par):
  self[par.name] = par
  setattr(Collection, par.name,
    property(
      fget=lambda x : Collection.metaget(x, par),
      fset=lambda x, y : Collection.metaset(x,y, par))


回答2:

Look at built-in functions getattr and setattr. You'll probably be a lot happier.



回答3:

Properties are meant to dynamically evaluate attributes or to make them read-only. What you need is customizing attribute access. __getattr__ and __setattr__ do that really fine, and there's also __getattribute__ if __getattr__ is not enough.

See Python docs on customizing attribute access for details.



回答4:

Have you looked at the traits package? It seems that you are reinventing the wheel here with your parameter classes. Traits also have additional features that might be useful for your type of application (incidently I know a person that happily uses traits in neural simulations).



回答5:

Now I implemented a solution with set-/getattr:

class Collection(object):
...
  def __setattr__(self, name, value):
    if 'dict' in self.__dict__:
      if name in self.dict:
        self[name].v = value
    else:
      self.__dict__[name] = value
  def __getattr__(self, name):
    return self[name].v

There is one thing I quite don't like that much: The attributes are not in the __dict__. And if I have them there as well I would have a copy of the value - which can be dangerous...



回答6:

Finally I succeded to implement the classes with property(). Thanks a lot for the advice. It took me quite a bit to work it out - but I can promise you that this exercise helps you to understand better pythons OOP.

I implemented it also with __getattr__ and __setattr__ but still don't know the advantages and disadvantages to the property-solution. But this seems to be worth another question. The property-solutions seems to be quit clean.

So here is the code:

class SingleParameter(object):
  def __init__(self, name, default_value=0, unit='not specified'):
    self.name = name
    self.default_value = default_value
    self.unit = unit
    self.set(default_value)
  def get(*args):
    self = args[0]
    print "get(): "
    print args
    return self._v
  def set(*args):
    print "set(): "
    print args
    self = args[0]
    value = args[-1]
    self._v = value
  v = property(fget=get, fset=set, doc='value of parameter')

class Collection(dict):
  # inheriting from dict saves the methods: __getitem__ and __init__
  def add(self, par):
    self[par.name] = par
    # Now here comes the tricky part.
    # (Note: this property call the get() and set() methods with one
    # more argument than the property of SingleParameter)
    setattr(Collection, par.name,
      property(fget=par.get, fset=par.set))

# Applying the classes:
par1 = SingleParameter(name='par1', default_value=10, unit='mV')
par2 = SingleParameter(name='par2', default_value=20, unit='mA')
col = Collection()
col.add(par1)
col.add(par2)
# Setting parameter values:
par1.v = 13
col.par1 = 14
# Getting parameter values:
par1.v
col.par1
# checking identity:
par1.v is col.par1
# to access the whole object:
col['par1']

As I am new I am not sure how to move on: how to treat follow up questions (like this itself):

  • get() is seems to be called twice - why?
  • oop-design: property vs. "__getattr__ & __setattr__" - when should I use what?
  • is it rude to check the own answer to the own question as accepted?
  • is it recommended to rename the title in order to put correlated questions or questions elaborated with the same example into the same context?

Other questions that I put to understand property():

  • Why do managed attributes just work for class attributes and not for instance attributes in python?
  • How can I assign a new class attribute via __dict__ in python?


回答7:

I have a class that does something similar, but I did the following in the collection object:

setattr(self, par.name, par.v)