Programowanie i projektowanie obiektowe
Transkrypt
Programowanie i projektowanie obiektowe
Programowanie i projektowanie obiektowe
Python od środka
Paweł Daniluk
Wydział Fizyki
Jesień 2013
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
1 / 40
Zasięgi nazw (ang. scopes)
Przestrzeń nazw
Mapowanie nazw (zmiennych) do wartości (obiektów). Jest związane z:
klasami
obiektami
modułami
Dodatkowo występują przestrzenie:
lokalna (locals())
globalna (globals())
wbudowana (__builtin__)
Przestrzenie nazw są niezależne.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
2 / 40
Zasięgi nazw (ang. scopes)
Zasięgi
Zasięgi są związane z funkcjami.
Zmienne występujące w funkcji są widoczne w jej leksykalnym obrębie
i należą do lokalnej przestrzeni nazw.
Przypisanie zawsze dotyczy najsilniej zagnieżdżonego zasięgu.
Zasięg, w którym funkcja została zdefiniowana istnieje wraz z nią.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
3 / 40
Zasięgi – przykłady
def f ( ) :
print (x)
x = ’ global ’
f ()
def f ( ) :
x = ’f ’
print (x)
x = ’ global ’
print (x)
f ()
print (x)
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
4 / 40
Zasięgi – przykłady
def f ( ) :
print (x)
x = ’f ’
x = ’ global ’
f ()
def f ( ) :
print (x)
def g ( ) :
global x
print (x)
x = ’g ’
x = ’ global ’
f ()
g ()
f ()
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
5 / 40
Zasięgi – przykłady
x = ’ global ’
def f ( ) :
def g ( ) :
global x
print (x)
x = ’f ’
g ()
f ()
def f ( ) :
def g ( ) :
nonlocal x
x = ’g ’
x = ’f ’
g ()
print (x)
x = ’ global ’
f ()
print (x)
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
6 / 40
Implementacja przestrzeni nazw
Przestrzenie nazw są zazwyczaj implementowane przy pomocy słowników.
Często słownik dostępny jest przez atrybut __dict__.
Funkcje
locals()
globals()
dir([object]) – zwraca zmienne w bieżącym zasięgu lub atrybuty
dostępne w obiekcie (niekoniecznie własne)
Można zrobić klasę, której obiekty nie mają atrybutu __dict__. Służy do
tego atrybut __slots__.
c l a s s A( o b j e c t ) :
__slots__=[ ’ a1 ’ , ’ a2 ’ ]
Klasa A ma tylko dwa atrybuty a1 i a2.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
7 / 40
Typy i obiekty
Każda wartość w Pythonie jest obiektem (nawet liczba).
Każdy obiekt ma unikalny identyfikator (id())
Każdy obiekt ma typ (type())
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
8 / 40
Typy wbudowane w Pythona
None
NotImplemented
Ellipsis
numbers.Number
I
I
I
numbers.Integral
numbers.Real
numbers.Complex
Sequences
I
immutable
F
F
F
I
strings
unicode
tuples
mutable
F
F
lists
byte arrays
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
9 / 40
Typy wbudowane w Pythona
sets
I
I
set
frozenset
mappings
callable types
I
I
I
I
I
I
I
I
user-defined functions
user-defined methods
generator functions
built-in functions
built-in methods
class types (new-style)
classic classes
class instances (czasem)
modules
classes
class instances
files
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
10 / 40
Funkcje są obiektami
Funkcje mogą być traktowane jak wartości:
przypisywane na zmienną,
przekazywane jako parametry.
Funkcje mogą mieć atrybuty.
>>> def f():
...
pass
...
>>> f.atr=1
>>> f.atr
1
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
11 / 40
Funkcje są obiektami
Funkcje nazwane
def f ( x ) :
r e t u r n x+1
Funkcje anonimowe
f=lambda x : x+1
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
12 / 40
Czym się różni funkcja od metody?
>>> class A():
...
def f(self):
...
pass
...
>>> A.__dict__
’__module__’: ’__main__’, ’__doc__’: None, ’f’: <function f at 0x10e3615f0>
>>> A.f
<unbound method A.f>
>>> A().f
<bound method A.f of <__main__.A instance at 0x10e1fdb90>>
Funkcje są atrybutami klas.
Pobranie atrybutu, który jest funkcją, skutkuje zwróceniem metody.
Metoda może być przywiązana lub nie.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
13 / 40
Czym się różni funkcja od metody? c.d.
Metody mają atrybuty:
im_class – klasa
im_func – funkcja (atrybut klasy)
im_self – obiekt (określony tylko w metodzie przywiązanej)
Metoda przywiązana
Obiekt, który można wywołać (ma metodę __call__. Wywołanie
powoduje przekazanie do funkcji parametru self oraz pozostałych
podanych w wywołaniu metody.
Metoda nieprzywiązana
Metoda nieprzywiązana różni się od funkcji tym, że weryfikuje, czy pierwszy
parametr jest instancją klasy, z której metoda pochodzi.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
14 / 40
Nowe i stare klasy (new-style classes, classic classes
New-style classes
Classic classes
Włączone w system typów.
Niezależne od systemu
typów.
Typ instancji jest równy jej
klasie.
Instancje klas są typu
<type ’instance’>.
Dziedziczą z klasy object.
Możliwości klas w nowym stylu
metody statyczne i klasowe
cechy i deskryptory
lepsze wielodziedziczenie
metaklasy
__slots__
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
15 / 40
Wielodziedziczenie (ang. multiple inheritance
Czasami występuje potrzeba opisu klas, które łączą w sobie cechy kilku klas
nadrzędnych (np. latająca łódź).
Wielodziedziczenie może powodować straszne problemu (Deadly Diamond
of Death). Niezwykle istotne są reguły wyszukiwania metod (Method
Resolution Order – MRO).
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
16 / 40
Wielodziedziczenie (ang. multiple inheritance
Czasami występuje potrzeba opisu klas, które łączą w sobie cechy kilku klas
nadrzędnych (np. latająca łódź).
Wielodziedziczenie może powodować straszne problemu (Deadly Diamond
of Death). Niezwykle istotne są reguły wyszukiwania metod (Method
Resolution Order – MRO).
Stare i nowe klasy różnią się MRO. W nowych klasach zawsze występuje
diament (bo dziedziczą z object).
Linearyzacja
Kolejność klas w MRO zachowuje porządek wynikający z dziedziczenia
(nadklasa zawsze po podklasie) i kolejności występowania nadklas w
definicji klasy.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
16 / 40
Wielodziedziczenie c.d.
Niektóre hierarchie nie pozwalają na linearyzację
c l a s s A( o b j e c t ) :
d e f meth ( s e l f ) : r e t u r n "A"
c l a s s B( o b j e c t ) :
d e f meth ( s e l f ) : r e t u r n "B"
c l a s s X(A , B ) : p a s s
c l a s s Y(B , A ) : p a s s
c l a s s Z (X , Y ) : p a s s
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
17 / 40
Jak dostać się do nadklasy?
Metoda rozszerzająca zazwyczaj wywołuje metodę z nadklasy.
Problem
c l a s s A( o b j e c t ) :
d e f m( s e l f ) : " s a v e ␣A ’ s ␣ d a t a "
c l a s s B(A ) :
d e f m( s e l f ) :
" s a v e ␣B ’ s ␣ d a t a "
A .m( s e l f )
c l a s s C(A ) :
d e f m( s e l f ) :
" s a v e ␣C ’ s ␣ d a t a "
A .m( s e l f )
c l a s s D(B , C ) :
d e f m( s e l f ) :
" s a v e ␣D ’ s ␣ d a t a "
B .m( s e l f ) ; C .m( s e l f )
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
18 / 40
Jak dostać się do nadklasy?
Metoda rozszerzająca zazwyczaj wywołuje metodę z nadklasy.
Problem
c l a s s A( o b j e c t ) :
d e f m( s e l f ) : " s a v e ␣A ’ s ␣ d a t a "
c l a s s B(A ) :
d e f m( s e l f ) :
" s a v e ␣B ’ s ␣ d a t a "
A .m( s e l f )
c l a s s C(A ) :
d e f m( s e l f ) :
" s a v e ␣C ’ s ␣ d a t a "
A .m( s e l f )
c l a s s D(B , C ) :
d e f m( s e l f ) :
" s a v e ␣D ’ s ␣ d a t a "
B .m( s e l f ) ; C .m( s e l f )
Wywołanie D.m() spowoduje dwukrotne wykonanie A.m().
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
18 / 40
Jak dostać się do nadklasy?
c l a s s A( o b j e c t ) :
d e f m( s e l f ) : " s a v e ␣A ’ s ␣ d a t a "
c l a s s B(A ) :
d e f _m( s e l f ) : " s a v e ␣B ’ s ␣ d a t a "
d e f m( s e l f ) :
s e l f ._m( ) ; A .m( s e l f )
c l a s s C(A ) :
d e f _m( s e l f ) : " s a v e ␣C ’ s ␣ d a t a "
d e f m( s e l f ) :
s e l f ._m( ) ; A .m( s e l f )
c l a s s D(B , C ) :
d e f _m( s e l f ) : " s a v e ␣D ’ s ␣ d a t a "
d e f m( s e l f ) :
s e l f ._m( ) ; B ._m( s e l f ) ; C ._m( s e l f ) ; A .m( s e l f )
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
19 / 40
Jak dostać się do nadklasy?
c l a s s A( o b j e c t ) :
d e f m( s e l f ) : " s a v e ␣A ’ s ␣ d a t a "
c l a s s B(A ) :
d e f _m( s e l f ) : " s a v e ␣B ’ s ␣ d a t a "
d e f m( s e l f ) :
s e l f ._m( ) ; A .m( s e l f )
c l a s s C(A ) :
d e f _m( s e l f ) : " s a v e ␣C ’ s ␣ d a t a "
d e f m( s e l f ) :
s e l f ._m( ) ; A .m( s e l f )
c l a s s D(B , C ) :
d e f _m( s e l f ) : " s a v e ␣D ’ s ␣ d a t a "
d e f m( s e l f ) :
s e l f ._m( ) ; B ._m( s e l f ) ; C ._m( s e l f ) ; A .m( s e l f )
To rozwiązanie jest poprawne, ale implementacja D musi uwzględnić
istnienie klasy A.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
19 / 40
Strategia następnej metody
c l a s s A( o b j e c t
d e f m( s e l f
c l a s s B(A ) :
d e f m( s e l f
c l a s s C(A ) :
d e f m( s e l f
c l a s s D(B , C ) :
d e f m( s e l f
A .__mro__
B .__mro__
C .__mro__
D .__mro__
==
==
==
==
):
) : " s a v e ␣A ’ s ␣ d a t a "
) : " s a v e ␣B ’ s ␣ d a t a " ; s u p e r (B , s e l f ) .m( ) # C
) : " s a v e ␣C ’ s ␣ d a t a " ; s u p e r (C , s e l f ) .m( ) # A
) : " s a v e ␣D ’ s ␣ d a t a " ; s u p e r (D, s e l f ) .m( ) # B
(A ,
(B ,
(C ,
(D,
object )
A, o b j e c t )
A, o b j e c t )
B, C, A, o b j e c t )
super(class, obj)
nie musi być nadklasą class
jest nadklasą obj.__class__
obj musi być instancją pewnej podklasy class
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
20 / 40
Jak powstają metody?
>>> class A(object):
...
def f(self): pass
...
>>> A.__dict__[’f’]
<function f at 0x10be7f140>
>>> A.f
<unbound method A.f>
>>> A().f
<bound method A.f of <__main__.A object at 0x10bea10d0>>
Funkcja ma metodę __get__(self, obj, objtype=None).
>>> def g(self):pass
...
>>> g.__get__(None, A)
<unbound method A.g>
>>> g.__get__(A(), A)
<bound method A.g of <__main__.A object at 0x10bea1250>>
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
21 / 40
Jak powstają metody?
Zamiast funkcji możemy mieć dowolny obiekt, który ma metodę __get__.
c l a s s RandFun ( o b j e c t ) :
d e f __init__ ( s e l f , ∗ a r g s ) :
s e l f . f l i s t =a r g s
d e f __get__( s e l f , o b j , o b j t y p e=None ) :
r e t u r n random . c h o i c e ( s e l f . f l i s t ) . __get__( o b j , o b j t y p e )
c l a s s A( o b j e c t ) :
def f1 ( s e l f ) :
p r i n t " Jestem ␣ f 1 "
def f2 ( s e l f ) :
p r i n t " Jestem ␣ f 2 "
def g ( s e l f , v a l ) :
p r i n t " J e s t e m ␣ g : ␣ "+s t r ( v a l )
r f =RandFun ( f 1 , f 2 )
r f g=RandFun ( f 1 , f 2 , f g )
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
22 / 40
Deskryptory
Obiekt implementujący co najmniej jedną z metod __get__(), __set()__,
__delete()__ nazywa się deskryptorem.
class RevealAccess ( object ) :
d e f __init__ ( s e l f , i n i t v a l =None , name= ’ v a r ’ ) :
s e l f . val = i n i t v a l
s e l f . name = name
d e f __get__( s e l f , o b j , o b j t y p e ) :
p r i n t ’ R e t r i e v i n g ’ , s e l f . name
return s e l f . val
d e f __set__( s e l f , o b j , v a l ) :
p r i n t ’ U p d a t i n g ’ , s e l f . name
s e l f . val = val
c l a s s B( o b j e c t ) :
x = R e v e a l A c c e s s (10 , ’ var ␣"x" ’ )
y = 5
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
23 / 40
Deskryptory c.d.
Deskryptory pozwalają na łatwą realizację kapsułkowania.
property(fget=None, fset=None, fdel=None, doc=None)
c l a s s C( o b j e c t ) :
def getx ( s e l f ) :
r e t u r n s e l f . __x
def setx ( s e l f , value ) :
s e l f . __x = v a l u e
def delx ( s e l f ) :
d e l s e l f . __x
x = p r o p e r t y ( g e t x , s e t x , d e l x , " I ’m␣ t h e ␣ ’ x ’ ␣ p r o p e r t y . " )
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
24 / 40
Metody statyczne
Przy pomocy deskryptorów można zrealizować metody statyczne.
class staticmethod ( object ):
d e f __init__ ( s e l f , f ) :
self . f = f
d e f __get__( s e l f , o b j , o b j t y p e=None ) :
return s e l f . f
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
25 / 40
... i klasowe
c l a s s classmethod ( object ) :
d e f __init__ ( s e l f , f ) :
self . f = f
d e f __get__( s e l f , o b j , k l a s s=None ) :
i f k l a s s i s None :
k l a s s = type ( obj )
d e f ne wfun c ( ∗ a r g s ) :
return s e l f . f ( klass , ∗ args )
r e t u r n ne wfunc
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
26 / 40
@dekoratory
Metody statyczne (i klasowe) można tworzyć korzystając z klas
staticmethod i classmethod.
c l a s s A( o b j e c t ) :
def f ( ) :
pass
s t a t i c =s t a t i c m e t h o d ( f )
def g( c l s ) :
pass
g=c l a s s m e t h o d ( g )
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
27 / 40
@dekoratory c.d.
Ale zazwyczaj stosuje się zapożyczoną z Javy składnię.
c l a s s A( o b j e c t ) :
@staticmethod
def f ( ) :
pass
@classmethod
def g ( c l s ) :
pass
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
28 / 40
@dekoratory c.d.
Napis:
@dekorator
def f ( arg ) :
...
oznacza:
def f ( arg ) :
...
f=d e k o r a t o r ( f )
Dekorator funkcji jest funkcją, która jako argument bierze funkcję
dekorowaną i zwraca zmodyfikowaną funkcję.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
29 / 40
@dekoratory c.d.
Przykład
def bread ( func ) :
def wrapper ( ) :
p r i n t " </ ’ ’ ’ ’ ’ ’\ > "
func ()
p r i n t "<\______/>"
r e t u r n wrapper
def i n g r e d i e n t s ( func ) :
def wrapper ( ) :
p r i n t "#t o m a t o e s#"
func ()
p r i n t "~ s a l a d ~"
r e t u r n wrapper
d e f s a n d w i c h ( f o o d="−−ham−−" ) :
p r i n t food
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
30 / 40
@dekoratory c.d.
Przykład
sandwich ()
#o u t p u t s : −−ham−−
sandwich = bread ( i n g r e d i e n t s ( sandwich ))
sandwich ()
#o u t p u t s :
#</ ’ ’ ’ ’ ’ ’\ >
# #t o m a t o e s#
# −−ham−−
# ~salad~
#<\______/>
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
31 / 40
@dekoratory c.d.
@bread
@ingredients
d e f s a n d w i c h ( f o o d="−−ham−−" ) :
p r i n t food
sandwich ()
#o u t p u t s :
#</ ’ ’ ’ ’ ’ ’\ >
# #t o m a t o e s#
# −−ham−−
# ~salad~
#<\______/>
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
32 / 40
@dekoratory c.d.
Kolejność ma znaczenie
@ingredients
@bread
d e f s t r a n g e _ s a n d w i c h ( f o o d="−−ham−−" ) :
p r i n t food
strange_sandwich ()
#o u t p u t s :
##t o m a t o e s#
#</ ’ ’ ’ ’ ’ ’\ >
# −−ham−−
#<\______/>
# ~salad~
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
33 / 40
@dekoratory – przekazywanie argumentów
def a_decorator_passing_arguments ( function_to_decorate ) :
d e f a _ w r a p p e r _ a c c e p t i n g _ a r g u m e n t s ( arg1 , a r g 2 ) :
p r i n t " I ␣ g o t ␣ a r g s ! ␣ Look : " , arg1 , a r g 2
f u n c t i o n _ t o _ d e c o r a t e ( arg1 , a r g 2 )
r e t u r n a_wrapper_accepting_arguments
@a_decorator_passing_arguments
d e f p r i n t _ f u l l _ n a m e ( f i r s t _ n a m e , last_name ) :
p r i n t "My␣name␣ i s " , f i r s t _ n a m e , last_name
p r i n t _ f u l l _ n a m e ( " P e t e r " , "Venkman" )
# outputs :
#I g o t a r g s ! Look : P e t e r Venkman
#My name i s P e t e r Venkman
Przy dekoratorach stosowanych do dowolnych funkcji używa się
*args, **kwargs.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
34 / 40
@dekoratory – przekazywanie argumentów do dekoratora
d e f wrap_in_tag ( t a g ) :
def f a c t o r y ( func ) :
d e f d e c o r a t o r ( ∗ a r g s , ∗∗ k w a r g s ) :
r e t u r n ’<%(t a g ) s>%(r v ) s </%(t a g ) s> ’ %
( { ’ t a g ’ : tag , ’ r v ’ : f u n c ( ∗ a r g s , ∗∗ k w a r g s ) } )
return decorator
return factory
@wrap_in_tag ( ’ b ’ )
@wrap_in_tag ( ’ i ’ )
def say ( v a l ) :
return val
p r i n t say ( ’ h e l l o ’ )
Funkcja wrap_in_tag zwraca dekorator, który dokłada do wyniku
dekorowanej funkcji zadany tag XML.
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
35 / 40
Klasy również można dekorować
d e f addID ( o r i g i n a l _ c l a s s ) :
o r i g _ i n i t = o r i g i n a l _ c l a s s . __init__
d e f __init__ ( s e l f , i d , ∗ a r g s , ∗∗ kws ) :
s e l f . __id = i d
s e l f . getId = getId
o r i g _ i n i t ( s e l f , ∗ a r g s , ∗∗ kws )
o r i g i n a l _ c l a s s . __init__ = __init__
return original_class
@addID
c l a s s Foo :
pass
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
36 / 40
Metaklasy
(Lasciate ogni speranza)
Klasy są obiektami:
klasy type – new-style classes
klasy types.ClassType – classic classes
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
37 / 40
Metaklasy
(Lasciate ogni speranza)
Klasy są obiektami:
klasy type – new-style classes
klasy types.ClassType – classic classes
W zasadzie nic nie stoi na przeszkodzie, aby klasy były obiektami dowolnej
klasy (metaklasy).
Jaki jest z tego pożytek?
sianie zamętu i zniechęcenia
kontrola tworzenia klasy (metody __new__ i __init__)
kontrola tworzenia obiektów klasy (metoda __call__)
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
37 / 40
Metaklasy c.d.
Metaklasa danej klasy jest określona przez:
atrybut __metaclass__
metaklasa jednej z klas bazowych
zmienna globalna __metaclass__
types.ClassType
Tworzenie klasy skutkuje wywołaniem metaklasy:
M( name , b a s e s , d i c t )
gdzie:
name – nazwa klasy
bases – klasy bazowe
dict – elementy klasy
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
38 / 40
Metaklasy c.d.
Przykład – automatyczne tworzenie properties
c l a s s autoprop ( type ) :
d e f __init__ ( c l s , name , b a s e s , d i c t ) :
s u p e r ( a u t o p r o p , c l s ) . __init__ ( name ,
p r o p s = {}
f o r member i n d i c t . k e y s ( ) :
i f member . s t a r t s w i t h ( "_get_" ) o r
member . s t a r t s w i t h ( "_set_" ) :
p r o p s [ member [ 5 : ] ] = 1
f o r prop i n props . keys ( ) :
f g e t = g e t a t t r ( c l s , "_get_%s " %
f s e t = g e t a t t r ( c l s , "_set_%s " %
s e t a t t r ( c l s , prop , p r o p e r t y ( f g e t
P. Daniluk(Wydział Fizyki)
PO w. XII
bases , d i c t )
prop , None )
prop , None )
, fset ))
Jesień 2013
39 / 40
Metaklasy c.d.
Zastosowanie
a = InvertedX ()
a s s e r t not h a s a t t r ( a , " x " )
a . x = 12
a s s e r t a . x == 12
a s s e r t a . _InvertedX__x == −12
P. Daniluk(Wydział Fizyki)
PO w. XII
Jesień 2013
40 / 40