Более Pythonic способ добавления атрибутов в класс?

Я работаю с наборами данных с двух разных веб-страниц, но для одного и того же человека - наборы данных являются юридической информацией. Некоторые данные доступны на первой странице, поэтому я инициализирую объект Ответчик с правильной информацией и задаю атрибуты, для которых у меня нет данных, для null, Это класс:

class Defendant(object):
    """holds data for each individual defendant"""
    def __init__(self,full_name,first_name,last_name,type_of_appeal,county,case_number,date_of_filing,
                 race,sex,dc_number,hair_color,eye_color,height,weight,birth_date,initial_receipt_date,current_facility,current_custody,current_release_date,link_to_page):
        self.full_name = full_name
        self.first_name = first_name
        self.last_name = last_name
        self.type_of_appeal = type_of_appeal
        self.county = county
        self.case_number = case_number
        self.date_of_filing = date_of_filing
        self.race = 'null'
        self.sex = 'null'
        self.dc_number = 'null'
        self.hair_color = 'null'
        self.eye_color = 'null'
        self.height = 'null'
        self.weight = 'null'
        self.birth_date = 'null'
        self.initial_receipt_date = 'null'
        self.current_facility = 'null'
        self.current_custody = 'null'
        self.current_release_date = 'null'
        self.link_to_page = link_to_page

И вот как это выглядит, когда я добавляю наполовину заполненный объект Ответчик в список ответчиков:

list_of_defendants.append(Defendant(name_final,'null','null',type_of_appeal_final,county_parsed_final,case_number,date_of_filing,'null','null','null','null','null','null','null','null','null','null','null','null',link_to_page))

затем, когда я получаю оставшиеся данные с другой страницы, я обновляю эти атрибуты, установив их в null, вот так:

        for defendant in list_of_defendants:
            defendant.sex = location_of_sex_on_page
            defendant.first_name = location_of_first_name_on_page
            ## Etc.

Мой вопрос: есть ли более питонический способ добавления атрибутов к классу или менее уродливый способ инициализации объекта класса, когда у меня есть только половина информации, которую я хочу сохранить в нем?

4 ответа

Решение

Во-первых, используйте значения по умолчанию для любых аргументов, для которых вы устанавливаете значение NULL. Таким образом, вам даже не нужно указывать эти аргументы при создании экземпляра объекта (и вы можете указать все, что вам нужно в любом порядке, используя имя аргумента). Вы должны использовать значение Python None а не строка "null" для них, если нет какой-либо конкретной причины для использования строки. В Python 2.x аргументы со значениями по умолчанию должны идти последними, поэтому link_to_page должен быть перемещен перед этим.

Затем вы можете установить свои атрибуты, обновив экземпляр __dict__ атрибут, в котором хранятся атрибуты, прикрепленные к экземпляру. Каждый аргумент будет установлен как атрибут экземпляра с тем же именем.

def __init__(self, full_name, first_name, last_name, type_of_appeal, county, case_number, 
             date_of_filing, link_to_page, race=None, sex=None, dc_number=None,
             hair_color=None, eye_color=None, height=None, weight=None, birth_date=None,
             initial_receipt_date=None, current_facility=None, current_custody=None, 
             current_release_date=None):

      # set all arguments as attributes of this instance
      code     = self.__init__.__func__.func_code
      argnames = code.co_varnames[1:code.co_argcount]
      locs     = locals()
      self.__dict__.update((name, locs[name]) for name in argnames)

Вы также можете рассмотреть возможность синтеза full_name из двух других аргументов имени. Тогда вам не нужно передавать избыточную информацию, и она никогда не сможет не совпадать. Вы можете сделать это на лету через свойство:

@property
def full_name(self):
    return self.first_name + " " + self.last_name

Для обновления я бы добавил метод для этого, но принял бы аргументы только для ключевых слов, используя **, Чтобы защитить целостность данных, мы изменим только те атрибуты, которые уже существуют и имеют значение None,

def update(self, **kwargs):
    self.__dict__.update((k, kwargs[k]) for k in kwargs
                          if self.__dict__.get(k, False) is None)

Тогда вы можете легко обновить все, что вы хотите с помощью одного вызова:

defendant.update(eye_color="Brown", hair_color="Black", sex="Male")

Чтобы убедиться, что экземпляр полностью заполнен, вы можете добавить метод или свойство, которое проверяет, что все атрибуты не являются None:

@property
def valid(self):
    return all(self.__dict__[k] is not None for k in self.__dict__)

Если вы можете передать каждый атрибут в виде пары имя-значение, вы можете использовать что-то вроде:

class Defendant(object):
    fields = ['full_name', 'first_name', 'last_name', 'type_of_appeal', 
              'county', 'case_number', 'date_of_filing', 'race', 'sex',
              'dc_number', 'hair_color', 'eye_color', 'height', 'weight', 
              'birth_date', 'initial_receipt_date', 'current_facility', 
              'current_custody', 'current_release_date', 'link_to_page']

    def __init__(self, **kwargs):
        self.update(**kwargs)

    def update(self, **kwargs):
        self.__dict__.update(kwargs)

    def blank_fields(self):
        return [field for field in self.fields if field not in self.__dict__]

    def verify(self):
        blanks = self.blank_fields()
        if blanks:
            print 'The fields {} have not been set.'.format(', '.join(blanks))
            return False
        return True

Использование будет выглядеть примерно так:

defendant = Defendant(full_name='John Doe', first_name='John', last_name='Doe')
defendant.update(county='Here', height='5-11', birth_date='1000 BC')
defendant.verify()
# The fields type_of_appeal, case_number, date_of_filing, race... have not been set.

Расширение этого для использования обязательных полей и дополнительных полей будет легко. Или вы можете добавить необходимые аргументы для инициализации. Или вы можете проверить, что каждая пара имя-значение имеет правильное имя. И так далее...

Я бы сказал, что самый питонический способ - это то, что выглядит следующим образом:

class Defendant(Model):
    full_name = None  # Some default value
    first_name = None
    last_name = None
    type_of_appeal = None
    county = None
    case_number = None
    date_of_filing = None
    race = None
    sex = None
    dc_number = None
    hair_color = None
    eye_color = None
    height = None
    weight = None
    birth_date = None
    initial_receipt_date = None
    current_facility = None
    current_custody = None
    current_release_date = None
    link_to_page = None

Чисто, все определяется только один раз и работает автоматически.

Об этом Model супер класс... Если вы используете какой-либо веб-фреймворк, такой как Django, во что бы то ни стало, унаследуйте его модель, и все готово Он имеет всю проводку, которая вам нужна.

В противном случае, простой способ реализовать что-то короткое и приятное, наследовать ваши Defendant класс от:

class Model(object):
    def __init__(self, **kwargs):
        for k, v in kwargs.items():
            setattr(self, k, v)

И создать экземпляр на основе доступных полей:

d1 = Defendant(height=1.75)
print d1.height

d2 = Defendant(full_name='Peter')
print d2.full_name

С помощью метапрограммирования можно добиться гораздо более интересных вещей, таких как проверка типов полей, проверка значений, дублированные объявления и т. Д. Если вы используете python 3, вы можете легко разрешить передачу значений в __init__ метод либо по аргументам (на основе порядка объявления), либо по kwargs.

Итак, более простой пример, чтобы проиллюстрировать, как вы могли бы сделать:

class Foo:
  def __init__(self, a, b, e, c=None, d=None):
    self.a = a
    self.b = b
    self.c = c
    self.d = d
    self.e = e

Но если у вас никогда нет c а также d когда вам нужно создать экземпляр, я бы порекомендовал это:

class Foo:
  def __init__(self, a, b, e):
    self.a = a
    self.b = b
    self.c = None
    self.d = None
    self.e = e

РЕДАКТИРОВАТЬ: Другой метод может быть:

class Defendant(object):
    __attrs = (
        'full_name',
        'first_name',
        'last_name',
        'type_of_appeal',
        'county',
        'case_number',
        'date_of_filing',
        'race',
        'sex',
        'dc_number',
        'hair_color',
        'eye_color',
        'height',
        'weight',
        'birth_date',
        'initial_receipt_date',
        'current_facility',
        'current_custody',
        'current_release_date',
        'link_to_page'
    )

    def __update(self, *args, **kwargs):
        self.__dict__.update(dict(zip(self.__attrs, args)))
        self.__dict__.update(kwargs)

    def __init__(self, *args, **kwargs):
        self.__dict__ = dict.fromkeys(Defendant.__attrs, None)
        self.__update(*args, **kwargs)

    update_from_data = __update


if __name__ == '__main__':
    test = Defendant('foo bar', 'foo', 'bar', height=180, weight=85)
    test.update_from_data('Superman', 'Clark', 'Kent', hair_color='red', county='SmallVille')
Другие вопросы по тегам