В python, почему мы можем создать новый атрибут из экземпляра, а не метода? - PullRequest
0 голосов
/ 31 марта 2020

В следующем коде:

# An example class with some variable and a method 
class ExampleClass(object):

    def __init__(self):
        self.var = 10

    def dummyPrint(self):
        print ('Hello World!')

# Creating instance and printing the init variable
inst_a = ExampleClass()
# This prints --> __init__ variable = 10
print ('__init__ variable = %d' %(inst_a.var))
# This prints --> Hello World!
inst_a.dummyPrint()

# Creating a new attribute and printing it. 
# This prints --> New variable = 20
inst_a.new_var = 20
print ('New variable = %d' %(inst_a.new_var))

# Trying to create new method, which will give error
inst_a.newDummyPrint()

Я могу создать новый атрибут (new_var) вне класса, используя экземпляр. И это работает. В идеале я ожидал, что это не сработает.

Аналогичным образом я попытался создать новый метод (newDummyPrint ()); который напечатает AttributeError: объект 'ExampleClass' не имеет атрибута 'newDummyPrint' , как я ожидал.

Мой вопрос:

  1. Почему создание нового атрибута сработало?
  2. Почему создание нового метода не сработало?

Ответы [ 2 ]

0 голосов
/ 31 марта 2020

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

inst_a.new_var = 20

перед прочтением его на следующей строке. Вы НЕ назначаете newDummyPrint где-либо, поэтому очевидно, что механизм разрешения атрибутов не может его найти и в конечном итоге вызывает AtributeError. Тот же самый результат вы получите, если попытаетесь получить доступ к любому другому несуществующему атрибуту, ie inst_a.whatever.

Обратите внимание, что поскольку в Python все является объектом (включая классы, функции и т. Д.) c), нет никакого реального различия между доступом к атрибуту «data» или методу - все они являются атрибутами (классом или экземпляром), и правила разрешения атрибута одинаковы. В случае методов (или любого другого вызываемого атрибута) операция вызова происходит после того, как атрибут был разрешен.

Для динамического создания нового «метода» у вас есть в основном два решения: создание в виде атрибута класса (который сделает его доступным для всех других экземпляров класса) или в качестве атрибута экземпляра (который будет - очевидно - сделайте его доступным только в этом конкретном экземпляре.

Первое решение настолько простое, насколько это возможно: определите свою функцию и привяжите ее к классу:

# nb: inheriting from `object` for py2 compat

class Foo(object):
     def __init__(self, var):
         self.var = var


def bar(self, x):
    return self.var * x

# testing before:
f = Foo(42)
try:
    print(f.bar(2))
except AttribteError as e:
    print(e)

# now binds the function to the class:
Foo.bar = bar

# and test it:
print(f.bar(2))

# and it's also available on other instances:
f2 = Foo(6)
print(f2.bar(7))

Создание Метод экземпляра (очень крошечный) немного сложнее - вы должны вручную получить метод из функции и связать этот метод с экземпляром:

def baaz(self):
    return "{}.var = {}".format(self, self.var)

# test before:
try:
    print(f.baaz())
except AttributeError as e:
    print(e)


# now binds the method to the instance
f.baaz = baaz.__get__(f, Foo)

# now `f` has a `baaz` method    
print(f.baaz())

# but other Foo instances dont
try:
    print(f2.baaz())
except AttributeError as e:
    print(e)

Как вы заметили, я говорил о функциях в первом case и методы во втором case. A python "method" - это на самом деле просто тонкая вызываемая оболочка вокруг функции, экземпляра и класса , и предоставляется типом function через протокол дескриптора - который вызывается автоматически, когда атрибут разрешается в самом классе (=> атрибут класса, реализуемый в дескрипторе r), но не при разрешении по экземпляру. Вот почему во втором случае мы должны вручную вызвать протокол дескриптора.

Также обратите внимание, что существуют ограничения на то, что здесь возможно: во-первых, __magic__ методы (все методы с именем с двумя начальными и двумя последними подчеркиваниями), которые ищутся только для самого класса, поэтому вы не можете определить их для каждого экземпляра. Тогда типы на основе слотов и некоторые встроенные или C -кодированные типы вообще не поддерживают динамические c атрибуты. Эти ограничения в основном существуют для оптимизации производительности.

0 голосов
/ 31 марта 2020

Вы можете создавать новые атрибуты на лету, когда используете пустое определение класса , эмулирующее Pascal "запись" или C "структура" . Иначе, то, что вы пытаетесь сделать, это не хороший способ или хороший шаблон для объектно-ориентированного программирования. Об этом можно прочитать много книг. Вообще говоря, вы должны четко указать в определении класса, что такое объект этого класса, как он ведет себя: изменение его поведения на лету (например, добавление новых методов) может привести к неизвестным результатам, которые делают вашу жизнь невозможной при чтении этого код месяц спустя и еще хуже, когда вы отлаживаете. Существует даже проблема анти-паттернов, называемая Неоднозначная точка зрения :

Отсутствие ясности с точки зрения моделирования приводит к проблемным неоднозначностям в объектных моделях.

В любом случае, если вы играете с Python и , вы клянетесь, что никогда не будете использовать этот код в производстве , вы можете написать новые атрибуты, которые хранят лямбда-функции, например,

c = ExampleClass()
c.newMethod = lambda s1, s2: str(s1) + ' and ' + str(s2)
print(c.newMethod('string1', 'string2'))
# output is: string1 and string2

но это очень уродливо, я бы никогда не сделал бы это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...