1

I can create class definition dynamically, like there:

class_name = 'Human'
base_classes = (object,)
attributes = {'name':'',
              'books':list(),
              'say_hello':lambda self: sys.stdout.write('Hello!')}

Human = type(class_name, base_classes, attributes)

uzumaxy = Human()
uzumaxy.name = 'Maxim'
uzumaxy.books.append('Programming via .NET')
print(uzumaxy.name)  # Out: "Maxim"
print(uzumaxy.books) # Out: "['Programming via .NET']"

grandrey = Human()
grandrey.name = 'Andrey'
grandrey.books.append('Programming via python')

print(grandrey.name)  # Out: "Andrey"
print(uzumaxy.name)  # Out: "Maxim"

print(grandrey.books)  # Out: "['Programming via .NET', 'Programming via python']"
print(uzumaxy.books)  # Out: "['Programming via .NET', 'Programming via python']", but i'm expecting: "['Programming via .NET']"

Seems, attribute "name" is instance-level, but why attribute "books" is class-level? How I can dynamically create definition of type with instance-level attributes? Thx for help.

Marcus Wölk
  • 512
  • 1
  • 6
  • 26
eterey
  • 390
  • 1
  • 4
  • 16

2 Answers2

3

Actually, both name and books are class-level. It's just that strings are immutable, so when you use uzumaxy.name = "Maxim", you're adding a new attribute called name hiding the class name, while for uzumaxy.books.append("Programming via .NET"), you're accessing the existing (class) books and modifying it. Your code is equivalent to this:

class Human(object):
    name = ''
    books = []

    def say_hello(self):
        sys.stdout.write("Hello!")

Note the same behavior. Traditionally, we'd fix that by writing Human like this:

class Human(object):
    def __init__(self):
        self.name = ''
        self.books = []

    def say_hello(self):
        sys.stdout.write("Hello!")

Now each instance has its own name and books. To do this with a dynamically-created type, you do essentially the same thing, giving it an __init__:

def init_human(self):
    self.name = ''
    self.books = []

attributes = { '__init__': init_human,
               'say_hello': lambda self: sys.stdout.write("Hello!") }
icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • What I must do, when I have dynamic collection (will change on runtime) of attributes and can't write predefined function "init_human"? – eterey Jan 19 '14 at 01:30
  • 1
    @uzumaxy: You'll need to have a function to do it, but it needn't explicitly reference attributes like `self.name = ''`. Instead, you might loop through a list and use something similar to `setattr(self, 'name', '')`. – icktoofay Jan 19 '14 at 01:31
  • Nope, it's not work: http://pastebin.com/HfAgghFF – eterey Jan 19 '14 at 02:23
  • 1
    @uzumaxy: That's because `class_builder.instance_attributes['books']` is set to one list. `init_function` will give each instance that same list. If you want each instance to have a distinct list, you need to make sure your implementation does that. One possible solution: make `instance_attributes` a dictionary from attribute names to nullary functions that return the attribute value. Then you can create a new list for each instance. – icktoofay Jan 19 '14 at 02:27
  • 1
    @uzumaxy: For context, what you're doing now is equivalent to this: `_name = ''; _books = []; class Human(object): def __init__(self): self.name = _name; self.books = _books` – icktoofay Jan 19 '14 at 02:29
2

They're both class-level. name is simply immutable, so it doesn't look class-level at first glance. Most attempts to modify it will create a new instance-level attribute with the same name.

Just like when writing a class the normal way, you need to create instance attributes in the constructor:

def __init__(self):
    self.name = ''
    self.books = []

def say_hello(self):
    # This prints a newline. The original didn't.
    print 'Hello!'

Human = type('Human', (object,), {
    '__init__': __init__,
    'say_hello': say_hello,
})
user2357112
  • 260,549
  • 28
  • 431
  • 505