Python işlevi nitelikleri - kullanır ve kötüye kullanır [kapalı]


196

Pek çoğu bu özelliğin farkında değildir, ancak Python'un işlevlerinin (ve yöntemlerinin) öznitelikleri olabilir . Seyretmek:

>>> def foo(x):
...     pass
...     
>>> foo.score = 10
>>> dir(foo)
['__call__', '__class__', '__delattr__', '__dict__', '__doc__', '__get__', '__getattribute__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name', 'score']
>>> foo.score
10
>>> foo.score += 1
>>> foo.score
11

Python'da bu özelliğin olası kullanımları ve kötüye kullanımları nelerdir? Bildiğim biri iyi kullanımıdır kat bir yöntem bir sözdizimi kuralı ilişkilendirmek için docstringe arasında 'ın kullanımı. Peki ya özel özellikler? Bunları kullanmak için iyi nedenler var mı?


3
PEP 232'yi kontrol edin .
user140352

2
Bu çok şaşırtıcı mı? Genel olarak, Python nesneleri geçici nitelikleri destekler. Tabii ki, bazıları özellikle yerleşik tipte olanlar yapmaz. Bana göre, bunu desteklemeyenler kural değil istisnalar gibi görünüyor.
allyourcode


2
@GrijeshChauhan Bu dokümanı gördükten sonra bu soruya geldim!
Alexander Suraphel

5
Bu kapalı olduğu için, arama kodunda yakalarken kolay erişim sağlamak için, işlevin yükseltebileceği özel istisnaları ekleyebileceğinizi eklemek istedim. Açıklayıcı bir örnek verirdim, ama en iyisi bir cevapta yapılır.
Hardy

Yanıtlar:


154

Ek açıklamalarda genellikle depolama alanı olarak işlev niteliklerini kullanırım. C # tarzında yazmak istediğimi varsayalım (belirli bir yöntemin web hizmeti arayüzünün bir parçası olması gerektiğini gösterir)

class Foo(WebService):
    @webmethod
    def bar(self, arg1, arg2):
         ...

o zaman tanımlayabilirim

def webmethod(func):
    func.is_webmethod = True
    return func

Daha sonra, bir webservis çağrısı geldiğinde, yönteme bakarım, temel fonksiyonun is_webmethod özniteliğine sahip olup olmadığını kontrol edin (gerçek değer alakasızdır) ve yöntem web üzerinden çağrılmayacak veya çağrılmayacaksa hizmeti reddeder.


2
Sizce bunun aşağı tarafı var mı? Örneğin, iki kütüphane aynı geçici özelliği yazmaya çalışırsa ne olur?
allyourcode

18
Tam olarak bunu yapmayı düşünüyordum. Sonra kendimi durdurdum. "Bu kötü bir fikir mi?" Merak ettim. Sonra SO'ya doğru dolaştım. Etrafta dolaşan bu soru / cevabı buldum. Bunun iyi bir fikir olup olmadığından hala emin değilim.
allyourcode

7
Bu kesinlikle tüm cevapların işlev niteliklerinin en yasal kullanımıdır (Kasım 2012 itibariyle). Diğer yanıtların çoğu (hepsi değilse de), global değişkenlerin yerine işlev niteliklerini kullanır; ancak, küresel değişkenlerle ilgili sorun olan küresel devletten kurtulmazlar. Bu farklıdır, çünkü değer bir kez ayarlandığında değişmez; sabittir. Bunun güzel bir sonucu, global değişkenlerin doğasında olan senkronizasyon sorunlarıyla karşılaşmamanızdır. Evet, kendi senkronizasyonunuzu sağlayabilirsiniz, ancak önemli olan nokta: otomatik olarak güvenli değildir.
allyourcode

Gerçekten, diyorum ki, özellik söz konusu işlevin davranışını değiştirmediği sürece, iyidir. Karşılaştırma.__doc__
Dima Tisnek

Bu yaklaşım, python 2'de eksik olan dekore edilmiş işleve çıktı açıklaması eklemek için de kullanılabilir. *
Juh_

126

Bunları bir işlev için statik değişkenler olarak kullandım. Örneğin, aşağıdaki C kodu verildiğinde:

int fn(int i)
{
    static f = 1;
    f += i;
    return f;
}

Bu işlevi Python'da benzer şekilde uygulayabilirim:

def fn(i):
    fn.f += i
    return fn.f
fn.f = 1

Bu kesinlikle spektrumun "suistimalleri" sonuna düşecektir.


2
İlginç. Python'da statik değişkenler uygulamanın başka yolları var mı?
Eli Bendersky

4
-1, bu piton bir jeneratör ile uygulanacaktır.

124
Bu, C ve Python arasında bir benzetme gösteren ve bu özel işlevi yazmanın mümkün olan en iyi yolunu savunmayan bu cevabı aşağılamak için oldukça zayıf bir neden.
Robert Rossney

3
@RobertRossney Ancak jeneratörler gidilecek yolsa, bu işlev niteliklerinin zayıf kullanımıdır. Eğer öyleyse, bu bir kötüye kullanımdır. Yine de suistimalleri onaylayıp onaylamayacağından emin değilim, çünkü soru da bunları soruyor: P
allyourcode

1
Kesinlikle bir kötüye kullanım gibi görünüyor PEP 232, teşekkürler @ user140352, ve hop'ın yorum ve bu SO cevap
hobs

53

Nesneleri JavaScript yolu ile yapabilirsiniz ... Mantıklı değil ama işe yarıyor;)

>>> def FakeObject():
...   def test():
...     print "foo"
...   FakeObject.test = test
...   return FakeObject
>>> x = FakeObject()
>>> x.test()
foo

36
+1 Sorunun istediği şeylerden biri olan bu özelliğin kötüye kullanımına güzel bir örnek.
Michael Dunn

1
Bunun mipadi'nin cevabından farkı nedir? Aynı şey gibi görünüyor, int yerine öznitelik değeri bir işlevdir.
allyourcode

olduğunu def test()gerçekten gerekli?
Keerthana Prabhakaran

15

Onları idareli kullanıyorum, ancak oldukça uygun olabilirler:

def log(msg):
   log.logfile.write(msg)

Şimdi modülüm logboyunca kullanabilir ve çıkışı ayarlayarak yönlendirebilirim log.logfile. Bunu başarmak için birçok yol var, ama bu hafif ve kir basit. İlk yaptığımda komik kokuyor olsa da, küresel bir logfiledeğişkene sahip olmaktan daha iyi koktuğuna inanmaya başladım .


7
yeniden koku: Bu küresel günlük dosyasından kurtulmak değil. Sadece başka bir küresel, günlük fonksiyonunda sincaplar.
allyourcode

2
@allyourcode: Aynı modülde farklı işlevler için bir grup global günlük dosyası olması gerekiyorsa, ad çakışmalarını önlemeye yardımcı olabilir.
firegurafiku

11

İşlev öznitelikleri, kodu ve ilişkili verileri bir araya getiren hafif kapaklar yazmak için kullanılabilir:

#!/usr/bin/env python

SW_DELTA = 0
SW_MARK  = 1
SW_BASE  = 2

def stopwatch():
   import time

   def _sw( action = SW_DELTA ):

      if action == SW_DELTA:
         return time.time() - _sw._time

      elif action == SW_MARK:
         _sw._time = time.time()
         return _sw._time

      elif action == SW_BASE:
         return _sw._time

      else:
         raise NotImplementedError

   _sw._time = time.time() # time of creation

   return _sw

# test code
sw=stopwatch()
sw2=stopwatch()
import os
os.system("sleep 1")
print sw() # defaults to "SW_DELTA"
sw( SW_MARK )
os.system("sleep 2")
print sw()
print sw2()

1,00934004784

2,00644397736

3,01593494415


3
Sınıflarımız elimizde olduğunda neden fonksiyonları zorlarsınız? Ve sınıfların bir işlevi taklit edebileceğini unutmayalım.
muhuk

1
ayrıca time.sleep(1)daha iyidiros.system('sleep 1')
Boris Gorelik

3
@bgbg Doğru, ancak bu örnek uyku ile ilgili değildir.
allyourcode

Bu kesinlikle bir kötüye kullanımdır; burada işlevlerin kullanımı tamamen ücretsizdir. muhuk tam olarak haklıdır: sınıflar daha iyi bir çözümdür.
allyourcode

1
Ayrıca, "Bunun bir sınıfa göre avantajı nedir?" bunun birçok dezavantajına karşı birçok Python programcısı için açık değildir.
cjs

6

İşlev yardımcılarını kolayca ayarlamak için bu yardımcı dekoratörü oluşturdum:

def with_attrs(**func_attrs):
    """Set attributes in the decorated function, at definition time.
    Only accepts keyword arguments.
    E.g.:
        @with_attrs(counter=0, something='boing')
        def count_it():
            count_it.counter += 1
        print count_it.counter
        print count_it.something
        # Out:
        # >>> 0
        # >>> 'boing'
    """
    def attr_decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kwargs):
            return fn(*args, **kwargs)

        for attr, value in func_attrs.iteritems():
            setattr(wrapper, attr, value)

        return wrapper

    return attr_decorator

Kullanım durumu, bir fabrika koleksiyonu oluşturmak ve işlev meta düzeyinde oluşturabilecekleri veri türünü sorgulamaktır.
Örneğin (çok aptal olan):

@with_attrs(datatype=list)
def factory1():
    return [1, 2, 3]

@with_attrs(datatype=SomeClass)
def factory2():
    return SomeClass()

factories = [factory1, factory2]

def create(datatype):
    for f in factories:
        if f.datatype == datatype:
            return f()
    return None

Dekoratör nasıl yardımcı olur? Neden sadece factory1.datatype=listeski tarz dekoratörler gibi bildirimin altında değil ?
Ceasar Bautista

2
2 ana fark: stil, çoklu özelliklerin ayarlanması daha kolaydır. Kesinlikle bir özellik olarak ayarlayabilirsiniz, ancak bence birden fazla özellik ile ayrıntılı olur ve ayrıca dekoratörü daha fazla işlem yapmak için genişletme fırsatı elde edersiniz (işlevi kullanan tüm yerler yerine varsayılanların tek bir yerde tanımlanması gibi veya öznitelikler ayarlandıktan sonra fazladan bir işlev çağırın). Tüm bu sonuçları elde etmenin başka yolları da var, bunu daha temiz buluyorum, ama fikrimi değiştirmekten mutluyum;)
DiogoNeves

Hızlı güncelleme: Python 3 ile items()bunun yerine kullanmanız gerekir iteritems().
Scott

4

Bazen zaten hesaplanmış değerleri önbelleğe almak için bir işlev özniteliği kullanın. Bu yaklaşımı genelleştiren genel bir dekoratör de olabilir. Eşzamanlılık sorunlarının ve bu işlevlerin yan etkilerinin farkında olun!


Bu fikri seviyorum! Hesaplanan değerleri önbelleğe almanın daha yaygın bir hilesi, arayanın asla sağlamak istemediği bir özniteliğin varsayılan değeri olarak bir diksiyon kullanmaktır. etrafında. İşlev özniteliklerini kullanmak daha az belirgin olsa da, bana çok daha az acayip geliyor.
Soren Bjornstad

1

Her zaman bunun mümkün olmasının tek nedeni, bir doc-string veya benzeri şeyler koymak için mantıklı bir yer olduğu varsayımıydım. Herhangi bir üretim kodu için kullandıysam, okuyanların çoğunun kafasını karıştıracağını biliyorum.


1
Bu büyük olasılıkla kafa karıştırıcı olma konusundaki ana görüşünüze katılıyorum, ancak docstrings: Evet, ancak işlevlerin neden AD-HOC öznitelikleri var? Öğretiyi tutmak için bir dizi sabit özellik olabilir.
allyourcode

@allyourcode Dilde tasarlanmış özel geçici durumlar yerine genel duruma sahip olmak işleri kolaylaştırır ve Python'un eski sürümleriyle uyumluluğu artırır. (Örneğin, docstrings'i ayarlayan / işleyen kod, özelliğin bulunmadığı durumu ele aldığı sürece
docstrings kullanmayan bir
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.