Python ile metaprogramlamaya bir bakış

Decorator’lar

Neredeyse her Python programcısı eninde sonunda bu yapıyı kullanır, o yüzden bu yapının nasıl çalıştığı konusunda bir içgörü edinmek oldukça önemli. Eğer decorator nedir bilmiyorsanız, ya da kafanızda canlanmadıysa şu örneğe bakabilirsiniz:

@app.route('/')
def home():
return render_template('home.html')
>>> int()
0
>>> 5()
<stdin>:1: SyntaxWarning: 'int' object is not callable; perhaps you missed a comma?
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
def deco(func):
print("Decorated %s" % func)
return func
def deco(func):
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)

return inner
def echo(number):
return number

echo = deco(echo)
@deco
def echo(number):
return number
def deco0(func):
print("Decorating %s" % func)
return func


def deco(func):
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)

return inner


def echo(number):
return number


echo_1 = deco(echo)
echo_2 = deco0(echo)

echo_1 is echo # output: False
echo_2 is echo # output: True
import functools

def deco(func):
@functools.wraps(func)
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)

return inner
def deco(func):
def inner(*args, **kwargs):
print("Calling %s" % func)
return func(*args, **kwargs)

return functools.wraps(func)(inner)

type

Eğer bir nesnenin type’ini merak edersek bunu tek argüman olarak bu arkadaşa verebiliriz; bunu hepimiz biliyoruz.

class Person:
def __init__(self, name):
self.name = name

ismet = Person(name="İsmet")
  1. İsmi ne?
  2. Hangi base classları almış, yani neyi inherit etmiş?
  3. Class attribute’leri neler, yani class gövdesinde hangi tanımlar yapılmış?
def __init__(self, name):
self.name = name

Person = type("Person", (), {"__init__": __init__})
ismet = Person(name="İsmet")
  1. Her class aslında type’ın bir subtype’ıdır.
  2. Type’ları oluşturmanın tek yolu, class syntax’ı değil.
  3. Type’ların oluşturulmasını (ve diğer davranışlarını) değiştiren veya kontrol eden özel type’lar var.

Metaclass kavramı

Metaclass’ların ne olduğunu yukarıda öğrenmiştik. Şimdi kendimizi metaclass yazmak için motive etmeye çalışalım. Mesela bir class’ın oluşum sürecine neden müdahale etmek isteyelim ki?

class PersonMeta(type):
def __new__(mcs, name, bases, classdict):
cls = super().__new__(mcs, name, bases, classdict)
cls = super_useful_decorator(cls)
return cls


class Person(metaclass=PersonMeta):
def __init__(self, name):
self.name = name
  1. Metaclass type’in subclass’ı, zira type da bir metaclass. Metaclass işlevselliği için bu subtyping’a ihtiyacımız var (zira arkada C ile dönen bir implementation var; oraya kadar inemiyoruz).
  2. Buradaki __new__ sıradan __new__ ile aynı imzaya sahip değil, normalde __new__, __init__’den önce çalışır ve instance’yi oluşturmakla görevlidir. Oysa burda class’ın kendisini oluşturmakla görevli.
  3. __new__’in imzası type()’nın 3 argüman alan imzasıyla aynı, yani yukarıda bahsettiğimiz üç karakteristik.
  4. Bir class’ın metaclass’ını belirtmek için, class tanımında metaclass keyword argümanı kullanılır.
  5. __new__ içindeki oluşturma mekanizması için yine type’a başvuruyoruz (super yoluyla; burada açık açık cls = type(name, bases, classdict)da diyebilirdik, ama yaygın kullanım super’i çağırmaktır). Demek ki type oluşumuna müdahele etmek aslında o kadar karmaşık bir işlem değil. Ya bu üç karakteristiği değiştireceğiz ya da yeni oluşturulan type nesnesi üzerinde birtakım işlemler yapacağız.
registry = []


class Person:
def __init__(self, name):
self.name = name

def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
registry.append(cls)
class Person:
def __init__(self, name):
self.name = name

def __init_subclass__(cls, location, **kwargs):
super().__init_subclass__(**kwargs)
cls.location = location
registry.append(cls)


class RichPerson(Person, location="Los Angeles"):
pass

Descriptor’lar

Descriptor’lar kısaca class’ların attribute erişimini özelleştirmemize yarıyor, bir başka tabirle “nokta operatörünü özelleştirmek”. Mesela şu örnek üzerinden gidelim:

class Person:
name = None

person = Person()
person.name = "İsmet"
  1. Eğer Person’a name atanacak ise, bu name en az 3 karakter olsun, değilse hata versin.
  2. Eğer Person’un name’si belirlenmemiş ise, person.name diye çağırdığımız zaman “isim belirilmemiş” diye hata versin.
  3. Person’a name atanınca Person’un eski isimleri bir listede tutulsun ve person.old_names şeklinde erişebilelim.
class Name:
names = []
current_name = None

def
__set__(self, instance, value):
if len(value) < 3:
raise ValueError(
"Please provide a name that"
" is at least 3 characters."
)

self.names.append(value)
instance.old_names = self.names[:-1]
self.current_name = value

def __get__(self, instance, owner):
if self.current_name is None:
raise ValueError("Name is not set.")

return self.current_name


class Person:
name = Name()
  1. Descriptor’lar class gövdesinde initialize edilir. Her descriptor kendi başına bir class’dır.
  2. __set__ metodu attribute değişiminde çağrılır, yani özetle erişimi bir metot ile sarmış oluyoruz. Benzer bir şekilde bu durum __get__ için de geçerli, bu metot da erişim zamanında çağrılır.
  3. Eğer bir class __get__, __set__ ya da __delete__ tanımı yapıyorsa, bir descriptor olur.
class Person(models.Model):
name = models.CharField()
SELECT name FROM users WHERE user.id = 1
class Name:
names = []
current_name = None

def
__set_name__(self, owner, name):
self.field_name = name

...
class DirectorySize:
def __get__(self, instance, owner):
return len(os.listdir(instance.dirname))

class Directory:
size = DirectorySize()

def __init__(self, dirname):
self.dirname = dirname
  1. Bir class’ın attribute erişimini ve değişimini kontrol eden mekanizmalara descriptor diyoruz ve bu işlemler sırayla __get__ ve __set__ metotlarına denk geliyor.
  2. Descriptor’lar özellikle data validation veya dinamik data erişimi için işimize yarıyor.
  3. Descriptor’lar argüman alabiliyorlar ve __set_name__ metoduyla bir descriptor’un nasıl isimlendirildiğini öğrenebiliyoruz.
  4. Descriptor’lar sadece class variable olarak tanımlandıkları zaman çalışırlar.

--

--

Developing software…

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store