Pytanie Dlaczego puste klasy i funkcje Pythona działają jako arbitralne kontenery danych, ale nie inne obiekty?


Widziałem dwa różne obiekty Pythona służące do grupowania dowolnych danych: pustych klas i funkcji.

def struct():
    pass

record = struct
record.number = 3
record.name = "Zoe"


class Struct:
    pass

record = Struct()
record.number = 3
record.name = "Zoe"

Nawet jeśli klasa nie jest pusta, wydaje się działać tak długo, jak jest zdefiniowana w środowisku wykonawczym.

Ale kiedy zrobiłem się zarozumiały i próbowałem to zrobić z wbudowanymi funkcjami lub klasami, to nie zadziałało.

record = set()
record.number = 3
AttributeError: 'set' object has no attribute 'number'

record = pow
pow.number = 3
AttributeError: 'builtin_function_or_method' object has no attribute 'number'

Czy istnieje zasadnicza różnica między klasami wbudowanymi a "niestandardowymi" i funkcjami, które uwzględniają to zachowanie?


16
2017-07-11 13:54


pochodzenie


W twoim pierwszym przykładzie jest błąd: powinien record = struct withouth the (), Inaczej record będzie None a następna linia rzuci wyjątek. Należy również zauważyć, że używanie funkcji działa tylko dlatego, że zdefiniowana przez użytkownika funkcja jest po prostu kolejnym obiektem w pythonie, co oznacza, że ​​można przypisać dowolne atrybuty. Jeśli funkcja jest pusta lub nie ma z nią nic wspólnego. Ale chociaż prawdopodobnie możesz użyć tego dla jakiejś funky magii meta / dynamicznego programowania, nie mogę wymyślić powodu, dla którego używanie funkcji jako kontenera pamięci byłoby lepsze niż klasa ... - l4mpi
@ l4mpi Myślę, że jest to przydatne, jeśli chcesz emulować zamknięcia, które "piszą" do otaczającego ich zakresu w Pythonie 2.x, przed nonlocal. Używasz atrybutów ad-hoc lokalnego obiektu funkcji zamiast lokalnych zmiennych. - millimoose
Ups, masz rację co do pierwszego przykładu. - Eli Rose


Odpowiedzi:


Różnica polega na tym, że oba obiekty funkcyjne i twój obiekt Struct mają __dict__ atrybut, ale set instancje i wbudowane funkcje nie:

>>> def struct():
...     pass
...
>>> record = struct
>>> record.number = 2
>>> struct.__dict__
{'number': 2}
>>> class Struct:
...     pass
...
>>> record = Struct()
>>> record.number = 3
>>> record.__dict__
{'number': 3}
>>> record=set()
>>> record.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'set' object has no attribute '__dict__'
>>> pow.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'builtin_function_or_method' object has no attribute '__dict__'

Możesz emulować zachowanie używając slotów (chociaż tylko w klasach nowego stylu):

>>> class StructWithSlots(object):
...     __slots__ = []
...
>>> record = StructWithSlots()
>>> record.number = 3
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StructWithSlots' object has no attribute 'number'
>>> record.__dict__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'StructWithSlots' object has no attribute '__dict__'

7
2017-07-11 14:32





Wbudowane typy są napisane w C i nie można ich modyfikować w ten sposób. Ale po unifikacja typu / klasy wprowadzone w py2.2 możesz teraz dziedziczyć z wbudowanych typów i nadpisywać lub dodawać własne atrybuty do tej podklasy.

Możesz użyć forbiddenfood pakiet do dodawania atrybutów do wbudowanych typów:

Ten projekt ma ci pomóc znaleźć niebo w testach, ale to   może do tego doprowadzić piekło jeśli używasz go na kodzie produkcyjnym.

>>> from forbiddenfruit import curse 
>>> def words_of_wisdom(self):
 ...     return self * "blah "
>>> curse(int, "words_of_wisdom", words_of_wisdom)
>>> assert (2).words_of_wisdom() == "blah blah "

Oczywiście, jeśli jesteś wystarczająco zarozumiały, możesz tworzyć własne typy w C i dodawać do nich takie funkcje.


3
2017-07-11 13:59



Oczywiście można stworzyć typ w C, który może być użyty w taki sposób?
@delnan E.g. object rodzaj. Interesujące może być zagłębienie się w źródło Pythona, które jest szczególnym przypadkiem - pozwalając na rozwinięcie obiektów lub zapobieganie im. - millimoose
@delnan Ale nie nazwałbym tego typem wbudowanym, ponieważ piszesz swój własny typ w C (nie jest to ze standardowej biblioteki). Pytanie dotyczy klas wbudowanych i niestandardowych. - Ashwini Chaudhary
Nie sądzę, że to faktycznie odpowiada na pytanie w jakikolwiek przydatny sposób - istnieje odpowiedź na wyższym poziomie niż rozmowa na temat kodu C (który jest odpowiedni tylko dla CPython): obiekty, które można ustawić dowolne atrybuty na określenie __dict__, których nie możesz, nie rób tego. - RoadieRich
@AshwiniChaudhary Zastąp "Python core developers" dla "jednego". Innymi słowy, typy wbudowane nie odrzucają dodatkowych atrybutów, ponieważ są napisane w języku C, mogą być napisane w języku C i przyjmować dodatkowe atrybuty.


Niektóre wbudowane mogą być bardziej restrykcyjne. Ponadto zajęcia zaimplementowane na automatach nie będą akceptować dowolnych atrybutów.


2
2017-07-11 13:55





Jeśli chcesz mieć jakąś podobną ochronę w swojej klasie, możesz użyć __setattr__() metoda.

class TestClass(object):
    # Accept the attributes in this list
    __valid_attributes = ["myattr1", "myattr2"]

    def __setattr__(self, name, value):
        if not name in TestClass.__valid_attributes:
            raise AttributeError(
                "{0} has no attribute '{1}'".format(self.__class__.__name__, name))
        self.__dict__[name] = value

Teraz możesz zrobić coś takiego:

t = TestClass()
t.noattr = "test" # AttributeError: TestClass has no attribute 'noattr'

Ale "prawidłowe atrybuty" można jeszcze ustawić:

t = TestClass()
t.myattr1 = "test"
print(t.myattr1) # test

1
2017-07-11 14:23



Gratulacje, właśnie odkryłeś na nowo __slots__ koło. - RoadieRich
nie wiedziałem o tym automaty, Dzięki za komentarz - joente