[ TL; DR? Sen edebilir bir kod örneğin sonuna atlamak .]
Aslında farklı bir deyim kullanmayı tercih ediyorum, ki bu bir kerelik kullanım için biraz dahil, ancak daha karmaşık bir kullanım durumunuz varsa güzel.
Önce biraz arka plan.
Özellikler, hem ayarı hem de değerleri programlı bir şekilde ele almamıza izin verir, ancak yine de özniteliklere öznitelik olarak erişilmesine izin verir. 'Gets'ı' hesaplamalara 'dönüştürebiliriz (esasen) ve' setleri '' olaylara 'dönüştürebiliriz. Diyelim ki Java benzeri alıcılar ve ayarlayıcılarla kodladığım şu sınıfımız var.
class Example(object):
def __init__(self, x=None, y=None):
self.x = x
self.y = y
def getX(self):
return self.x or self.defaultX()
def getY(self):
return self.y or self.defaultY()
def setX(self, x):
self.x = x
def setY(self, y):
self.y = y
def defaultX(self):
return someDefaultComputationForX()
def defaultY(self):
return someDefaultComputationForY()
Neden aramadım defaultX
ve defaultY
nesnenin __init__
yöntemini merak ediyor olabilirsiniz . Bunun nedeni, bizim durumumuz için someDefaultComputation
yöntemlerin zaman içinde değişen, bir zaman damgası söylediği ve x
(veya y
) her ayarlanmadığında (bu örneğin amacı için "ayarlanmadı") ayarlandığı anlamına geldiğini varsaymak istiyorum to None ") x
's (veya y
' s) varsayılan hesaplama değerini istiyorum .
Bu, yukarıda açıklanan bir dizi nedenden dolayı topal. Özellikleri kullanarak yeniden yazacağım:
class Example(object):
def __init__(self, x=None, y=None):
self._x = x
self._y = y
@property
def x(self):
return self.x or self.defaultX()
@x.setter
def x(self, value):
self._x = value
@property
def y(self):
return self.y or self.defaultY()
@y.setter
def y(self, value):
self._y = value
# default{XY} as before.
Ne kazandık? Her ne kadar sahnelerin arkasında yöntemler çalıştırıyor olsak da, bu niteliklere nitelik olarak gönderme yeteneği kazandık.
Elbette özelliklerin gerçek gücü, bu yöntemlerin sadece değerleri alıp ayarlamaya ek olarak bir şeyler yapmasını istememizdir (aksi takdirde özellikleri kullanmanın bir anlamı yoktur). Bunu alıcı örneğimde yaptım. Temel olarak, değer ayarlanmadığında varsayılanı almak için bir işlev gövdesi çalıştırıyoruz. Bu çok yaygın bir örüntüdür.
Ama ne kaybediyoruz ve ne yapamayız?
Benim görüşüme göre ana sıkıntı, bir alıcıyı tanımlarsanız (burada yaptığımız gibi) bir ayarlayıcı tanımlamanız gerektiğidir. [1] Bu, kodu karmaşıklaştıran ekstra gürültü.
Başka bir sıkıntı ise x
ve y
değerlerini hala başlatmamız gerektiğidir __init__
. (Elbette bunları kullanarak ekleyebiliriz, setattr()
ancak bu daha fazla kod.)
Üçüncüsü, Java benzeri örnekten farklı olarak, alıcılar diğer parametreleri kabul edemez. Şimdi söylediklerini duyabiliyorum, iyi, eğer parametre alıyorsa bir alıcı değil! Resmi anlamda, bu doğrudur. Ancak pratik anlamda, adlandırılmış bir özniteliği - gibi x
- parametrelendiremememiz ve değerini bazı belirli parametreler için ayarlamamamız için hiçbir neden yoktur .
Böyle bir şey yapabilirsek iyi olur:
e.x[a,b,c] = 10
e.x[d,e,f] = 20
Örneğin. Alabileceğimiz en yakın şey, bazı özel anlambilimi ima etmek için atamayı geçersiz kılmaktır:
e.x = [a,b,c,10]
e.x = [d,e,f,30]
ve elbette, ayarlayıcının ilk üç değeri bir sözlüğe anahtar olarak nasıl çıkaracağını ve değerini bir sayıya veya başka bir şeye nasıl ayarlayacağını bildiğinden emin olun.
Ancak bunu yapsak bile, onu özelliklerle destekleyemedik çünkü değeri elde etmenin bir yolu yok çünkü parametreleri alıcıya geçiremiyoruz. Bu yüzden her şeyi geri döndürmek zorunda kaldık, bir asimetri getirdik.
Java tarzı alıcı / ayarlayıcı bunu halletmemize izin veriyor, ancak alıcı / ayarlayıcılara ihtiyacımız var.
Aklımda gerçekten istediğimiz şey, aşağıdaki gereksinimleri yakalayan bir şey:
Kullanıcılar belirli bir öznitelik için yalnızca bir yöntem tanımlar ve burada özniteliğin salt okunur olup olmadığını belirtebilir. Öznitelik yazılabilirse, özellikler bu sınamada başarısız olur.
Kullanıcının işlevin altında yatan fazladan bir değişken tanımlamasına gerek yoktur, bu nedenle kodda __init__
veya setattr
kodda ihtiyacımız yoktur . Değişken, bu yeni stil niteliğini oluşturduğumuz gerçeğiyle var.
Öznitelik için herhangi bir varsayılan kod, yöntem gövdesinin kendisinde yürütülür.
Özniteliği öznitelik olarak ayarlayabilir ve öznitelik olarak başvurabiliriz.
Özelliği parametrelendirebiliriz.
Kod açısından şunu yazmanın bir yolunu istiyoruz:
def x(self, *args):
return defaultX()
ve şunları yapabilir:
print e.x -> The default at time T0
e.x = 1
print e.x -> 1
e.x = None
print e.x -> The default at time T1
ve benzerleri.
Ayrıca, parametrelendirilebilir bir özelliğin özel durumu için bunu yapmanın bir yolunu istiyoruz, ancak yine de varsayılan atama vakasının çalışmasına izin veriyoruz. Bunu nasıl ele aldığımı aşağıda göreceksiniz.
Şimdi noktaya (yay! Nokta!). Bunun için bulduğum çözüm şöyledir.
Bir özellik kavramını değiştirmek için yeni bir nesne yaratırız. Nesnenin kendisine ayarlanmış bir değişkenin değerini saklaması amaçlanmıştır, ancak aynı zamanda varsayılanın nasıl hesaplanacağını bilen kod üzerinde bir tanıtıcı tutar. Görevi, kümeyi saklamak value
veya method
bu değer ayarlanmamışsa çalıştırmaktır.
Buna bir diyelim UberProperty
.
class UberProperty(object):
def __init__(self, method):
self.method = method
self.value = None
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def clearValue(self):
self.value = None
self.isSet = False
method
Burada bir sınıf yöntemi olduğunu varsayalım value
, değeri UberProperty
, ve ben ekledim isSet
çünkü None
gerçek bir değer olabilir ve bu bize gerçekten "değer" olduğunu bildirmek için temiz bir yol sağlar. Başka bir yol, bir çeşit nöbetçi.
Bu temelde bize istediğimizi yapabilen bir nesne veriyor, ama aslında onu sınıfımıza nasıl koyabiliriz? Özellikler, dekoratörler kullanır; neden yapamıyoruz? Nasıl göründüğüne bakalım (buradan sonra sadece tek bir 'özellik' kullanmaya devam edeceğim x
).
class Example(object):
@uberProperty
def x(self):
return defaultX()
Aslında bu henüz işe yaramıyor. uberProperty
Hem alır hem de ayarlar işlediğinden emin olmalıyız .
Hadi başlayalım.
İlk denemem sadece yeni bir UberProperty nesnesi oluşturmak ve onu döndürmekti:
def uberProperty(f):
return UberProperty(f)
Elbette, bunun işe yaramadığını çabucak keşfettim: Python, çağrılabilir nesneyi asla nesneye bağlamaz ve işlevi çağırmak için nesneye ihtiyacım var. Sınıfta dekoratör oluşturmak bile işe yaramaz, çünkü şimdi sınıfa sahip olmamıza rağmen, hala üzerinde çalışacak bir nesnemiz yok.
Yani burada daha fazlasını yapabilmemiz gerekecek. Bir yöntemin yalnızca bir kez temsil edilmesi gerektiğini biliyoruz, bu yüzden dekoratörümüzü koruyalım, ancak UberProperty
yalnızca method
referansı saklamak için değiştirelim :
class UberProperty(object):
def __init__(self, method):
self.method = method
Ayrıca çağrılabilir değil, şu anda hiçbir şey çalışmıyor.
Resmi nasıl tamamlarız? Yeni dekoratörümüzü kullanarak örnek sınıf oluşturduğumuzda neyle sonuçlanırız:
class Example(object):
@uberProperty
def x(self):
return defaultX()
print Example.x <__main__.UberProperty object at 0x10e1fb8d0>
print Example().x <__main__.UberProperty object at 0x10e1fb8d0>
her iki durumda UberProperty
da elbette hangisinin çağrılabilir olmadığını geri alırız , bu yüzden bu pek işe yaramaz.
İhtiyacımız olan şey UberProperty
, sınıf oluşturulduktan sonra dekoratör tarafından oluşturulan örneği, o nesne kullanım için o kullanıcıya geri gönderilmeden önce sınıfın bir nesnesine dinamik olarak bağlamanın bir yoludur . Um, evet, bu bir __init__
telefon ahbap.
Bulma sonucumuzun önce olmasını istediğimizi yazalım. Biz bir bağlayıcı ediyoruz UberProperty
dönüşüne açık bir şey BoundUberProperty olacağını bu yüzden, bir örneğine. Bu, x
öznitelik için durumu gerçekten koruyacağımız yerdir .
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
Şimdi temsil ediyoruz; bunları bir nesneye nasıl taşıyabilirim? Birkaç yaklaşım vardır, ancak açıklanması en kolay olanı sadece __init__
bu eşlemeyi yapmak için yöntemi kullanır . Zamana göre __init__
dekoratörlerimiz çalıştırıldı, bu yüzden sadece nesnenin içine bakmak __dict__
ve özniteliğin değerinin tür olduğu herhangi bir özniteliği güncellemek gerekir UberProperty
.
Şimdi, uber-özellikleri harika ve muhtemelen onları çok fazla kullanmak isteyeceğiz, bu yüzden bunu tüm alt sınıflar için yapan bir temel sınıf oluşturmak mantıklı. Bence baz sınıfın adı ne olacak.
class UberObject(object):
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
Bunu ekliyoruz, miras almak için örneğimizi değiştiriyoruz UberObject
ve ...
e = Example()
print e.x -> <__main__.BoundUberProperty object at 0x104604c90>
Değişiklik yaptıktan sonra x
:
@uberProperty
def x(self):
return *datetime.datetime.now()*
Basit bir test yapabiliriz:
print e.x.getValue()
print e.x.getValue()
e.x.setValue(datetime.date(2013, 5, 31))
print e.x.getValue()
e.x.clearValue()
print e.x.getValue()
Ve istediğimiz çıktıyı elde ediyoruz:
2013-05-31 00:05:13.985813
2013-05-31 00:05:13.986290
2013-05-31
2013-05-31 00:05:13.986310
(Gee, geç saatlere kadar çalışıyorum.)
Ben kullandım o Not getValue
, setValue
ve clearValue
burada. Bunun nedeni, bunların otomatik olarak geri döndürülmesi için henüz bağlantı kurmamış olmamdır.
Ama bence burası şimdilik durmak için iyi bir yer, çünkü yoruluyorum. İstediğimiz temel işlevin yerinde olduğunu da görebilirsiniz; gerisi pencere giydirme. Önemli kullanılabilirlik penceresi giyinme, ancak postayı güncellemek için bir değişiklik yapana kadar bekleyebilir.
Bir sonraki gönderideki örneği şu şeylere değinerek bitireceğim:
UberObject'lerin __init__
her zaman alt sınıflar tarafından çağrıldığından emin olmalıyız .
- Bu yüzden ya bir yere çağrılmaya zorlanıyoruz ya da uygulanmasını engelliyoruz.
- Bunu bir metasınıfla nasıl yapacağımızı göreceğiz.
Birisinin bir işlevi başka bir şeye `` takma '' ettiği ortak durumu ele almamız gerektiğinden emin olmalıyız:
class Example(object):
@uberProperty
def x(self):
...
y = x
Varsayılan e.x
olarak geri dönmemiz gerekiyor e.x.getValue()
.
- Aslında göreceğimiz şey, bu modelin başarısız olduğu bir alandır.
- Değeri elde etmek için her zaman bir işlev çağrısı kullanmamız gerektiği ortaya çıkıyor.
- Ancak normal bir işlev çağrısı gibi görünmesini sağlayabilir ve kullanmaktan kaçınabiliriz
e.x.getValue()
. (Bunu düzeltmediyseniz, bunu yapmak açıktır.)
Ayarları e.x directly
olduğu gibi desteklememiz gerekir e.x = <newvalue>
. Bunu üst sınıfta da yapabiliriz, ancak bununla __init__
başa çıkmak için kodumuzu güncellememiz gerekir.
Son olarak, parametreli özellikler ekleyeceğiz. Bunu da nasıl yapacağımız oldukça açık olmalı.
Şimdiye kadar var olan kod İşte:
import datetime
class UberObject(object):
def uberSetter(self, value):
print 'setting'
def uberGetter(self):
return self
def __init__(self):
for k in dir(self):
v = getattr(self, k)
if isinstance(v, UberProperty):
v = BoundUberProperty(self, v)
setattr(self, k, v)
class UberProperty(object):
def __init__(self, method):
self.method = method
class BoundUberProperty(object):
def __init__(self, obj, uberProperty):
self.obj = obj
self.uberProperty = uberProperty
self.isSet = False
def setValue(self, value):
self.value = value
self.isSet = True
def getValue(self):
return self.value if self.isSet else self.uberProperty.method(self.obj)
def clearValue(self):
del self.value
self.isSet = False
def uberProperty(f):
return UberProperty(f)
class Example(UberObject):
@uberProperty
def x(self):
return datetime.datetime.now()
[1] Bunun hala böyle olup olmadığı konusunda geride kalabilirim.