Raymond Hettinger
<python at rcn dot com>
목차
ëì¤í¬ë¦½í°ë ê°ì²´ê° ì´í¸ë¦¬ë·°í¸ ì¡°í, ì ì¥ ë° ìì 를 ì¬ì©ì ì ì í ì ìëë¡ í©ëë¤.
ì´ ì§ì¹¨ìë ë¤ ê°ì 주ì ì¹ì ì¼ë¡ 구ì±ë©ëë¤:
âì 문âì ê°ë¨í ìì ìì ë¶ëë½ê² ì´ëíì¬ í ë²ì íëì 기ë¥ì ì¶ê°íë 기본 ê°ì를 ì ê³µí©ëë¤. ëì¤í¬ë¦½í°ë¥¼ ì²ì ì¬ì©íë©´ ì¬ê¸°ìì ììíì¸ì.
ë ë²ì§¸ ì¹ì ì ìì íê³ ì¤ì©ì ì¸ ëì¤í¬ë¦½í° ìì 를 ë³´ì¬ì¤ëë¤. ì´ë¯¸ 기본 ì¬íì ìê³ ìë¤ë©´, ì¬ê¸°ìì ììíììì¤.
ì¸ ë²ì§¸ ì¹ì ììë ëì¤í¬ë¦½í°ê° ìëíë ë°©ìì ëí ìì¸í ë©ì»¤ëì¦ì ê´í´ ì¤ëª íë ë 기ì ì ì¸ ììµì를 ì ê³µí©ëë¤. ëë¶ë¶ì ì¬ëì ì´ë¬í ìì¤ì ì¸ë¶ ì ë³´ê° íìíì§ ììµëë¤.
ë§ì§ë§ ì¹ì
ìë Cë¡ ìì±ë ë´ì¥ ëì¤í¬ë¦½í°ì ëí ììí íì´ì¬ ë±ê°ë¬¼ì´ ììµëë¤. í¨ìê° ì°ê²°ë ë©ìëë¡ ë°ëë ë°©ë²ì´ë classmethod()
, staticmethod()
, property()
ë° __slots__ì ê°ì ì¼ë°ì ì¸ ë구ì 구íì ëí´ ê¶ê¸íë©´ ì´ ë¬¸ì를 ì½ì¼ììì¤.
ì´ ì 문ìììë, ê°ë¥í ê°ì¥ 기본ì ì¸ ìì ë¡ ììí ë¤ì ìë¡ì´ 기ë¥ì íëì© ì¶ê°í ê²ì ëë¤.
ê°ë¨í ì: ìì를 ë°ííë ëì¤í¬ë¦½í°Â¶The Ten
class is a descriptor whose __get__()
method always returns the constant 10
:
class Ten: def __get__(self, obj, objtype=None): return 10
ëì¤í¬ë¦½í°ë¥¼ ì¬ì©íë ¤ë©´, ë¤ë¥¸ í´ëì¤ì í´ëì¤ ë³ìë¡ ì ì¥í´ì¼ í©ëë¤:
class A: x = 5 # Regular class attribute y = Ten() # Descriptor instance
ëí í ì¸ì ì ì¼ë° ì´í¸ë¦¬ë·°í¸ ì¡°íì ëì¤í¬ë¦½í° ì¡°íì ì°¨ì´ì ì ë³´ì¬ì¤ëë¤:
>>> a = A() # Make an instance of class A >>> a.x # Normal attribute lookup 5 >>> a.y # Descriptor lookup 10
In the a.x
attribute lookup, the dot operator finds 'x': 5
in the class dictionary. In the a.y
lookup, the dot operator finds a descriptor instance, recognized by its __get__
method. Calling that method returns 10
.
10
ê°ì´ í´ëì¤ ëì
ë리ë ì¸ì¤í´ì¤ ëì
ë리ì ì ì¥ëì§ ììì ì ìíììì¤. ëì , 10
ê°ì ìì² ì ê³ì°ë©ëë¤.
ì´ ìë ê°ë¨í ëì¤í¬ë¦½í°ê° ì´ë»ê² ìëíëì§ ë³´ì¬ ì£¼ì§ë§, ê·¸ë¤ì§ ì ì©íì§ë ììµëë¤. ìì를 꺼ë´ë ¤ë©´, ì¼ë° ì´í¸ë¦¬ë·°í¸ ì¡°íê° ë ì¢ìµëë¤.
ë¤ì ì¹ì ììë, ì¢ ë ì ì©í ëì ì¡°í를 ë§ë¤ ê²ì ëë¤.
ëì ì¡°í¶í¥ë¯¸ë¡ì´ ëì¤í¬ë¦½í°ë ë³´íµ ìì를 ë°ííë ëì ê³ì°ì ì¤íí©ëë¤:
import os class DirectorySize: def __get__(self, obj, objtype=None): return len(os.listdir(obj.dirname)) class Directory: size = DirectorySize() # Descriptor instance def __init__(self, dirname): self.dirname = dirname # Regular instance attribute
ëí í ì¸ì ì ì¡°íê° ëì ìì ë³´ì¬ì¤ëë¤ - ë§¤ë² ë¤ë¥¸ ê°±ì ë ëµë³ì ê³ì°í©ëë¤:
>>> s = Directory('songs') >>> g = Directory('games') >>> s.size # The songs directory has twenty files 20 >>> g.size # The games directory has three files 3 >>> os.remove('games/chess') # Delete a game >>> g.size # File count is automatically updated 2
Besides showing how descriptors can run computations, this example also reveals the purpose of the parameters to __get__()
. The self parameter is size, an instance of DirectorySize. The obj parameter is either g or s, an instance of Directory. It is the obj parameter that lets the __get__()
method learn the target directory. The objtype parameter is the class Directory.
A popular use for descriptors is managing access to instance data. The descriptor is assigned to a public attribute in the class dictionary while the actual data is stored as a private attribute in the instance dictionary. The descriptorâs __get__()
and __set__()
methods are triggered when the public attribute is accessed.
ë¤ì ììì, ageë ê³µì© ì´í¸ë¦¬ë·°í¸ì´ê³ _ageë ê°ì¸ ì´í¸ë¦¬ë·°í¸ì ëë¤. ê³µì© ì´í¸ë¦¬ë·°í¸ì ì¡ì¸ì¤íë©´, ëì¤í¬ë¦½í°ë ì¡°íë ê°±ì ì ë¡ê·¸ í©ëë¤:
import logging logging.basicConfig(level=logging.INFO) class LoggedAgeAccess: def __get__(self, obj, objtype=None): value = obj._age logging.info('Accessing %r giving %r', 'age', value) return value def __set__(self, obj, value): logging.info('Updating %r to %r', 'age', value) obj._age = value class Person: age = LoggedAgeAccess() # Descriptor instance def __init__(self, name, age): self.name = name # Regular instance attribute self.age = age # Calls __set__() def birthday(self): self.age += 1 # Calls both __get__() and __set__()
ëí í ì¸ì ì ê´ë¦¬ëë ì´í¸ë¦¬ë·°í¸ ageì ëí 모ë ì¡ì¸ì¤ê° ë¡ê·¸ ëì§ë§, ì¼ë° ì´í¸ë¦¬ë·°í¸ nameì ë¡ê·¸ ëì§ ììì ë³´ì¬ì¤ëë¤:
>>> mary = Person('Mary M', 30) # The initial age update is logged INFO:root:Updating 'age' to 30 >>> dave = Person('David D', 40) INFO:root:Updating 'age' to 40 >>> vars(mary) # The actual data is in a private attribute {'name': 'Mary M', '_age': 30} >>> vars(dave) {'name': 'David D', '_age': 40} >>> mary.age # Access the data and log the lookup INFO:root:Accessing 'age' giving 30 30 >>> mary.birthday() # Updates are logged as well INFO:root:Accessing 'age' giving 30 INFO:root:Updating 'age' to 31 >>> dave.name # Regular attribute lookup isn't logged 'David D' >>> dave.age # Only the managed attribute is logged INFO:root:Accessing 'age' giving 40 40
ì´ ìì ì í ê°ì§ 주ì 문ì ë ê°ì¸ ì´ë¦ _ageê° LoggedAgeAccess í´ëì¤ì ê³ ì ëì´ ìë¤ë ê²ì ëë¤. ì¦, ê° ì¸ì¤í´ì¤ë íëì ë¡ê·¸ ëë ì´í¸ë¦¬ë·°í¸ ë§ ê°ì§ ì ìì¼ë©° í´ë¹ ì´ë¦ì ë³ê²½í ì ììµëë¤. ë¤ì ìììë, ì´ ë¬¸ì 를 ìì í©ëë¤.
ì¬ì©ì ì ì ì´ë¦Â¶í´ëì¤ê° ëì¤í¬ë¦½í°ë¥¼ ì¬ì©í ë, ì´ë¤ ë³ì ì´ë¦ì´ ì¬ì©ëìëì§ ê° ëì¤í¬ë¦½í°ì ì릴 ì ììµëë¤.
In this example, the Person
class has two descriptor instances, name and age. When the Person
class is defined, it makes a callback to __set_name__()
in LoggedAccess so that the field names can be recorded, giving each descriptor its own public_name and private_name:
import logging logging.basicConfig(level=logging.INFO) class LoggedAccess: def __set_name__(self, owner, name): self.public_name = name self.private_name = '_' + name def __get__(self, obj, objtype=None): value = getattr(obj, self.private_name) logging.info('Accessing %r giving %r', self.public_name, value) return value def __set__(self, obj, value): logging.info('Updating %r to %r', self.public_name, value) setattr(obj, self.private_name, value) class Person: name = LoggedAccess() # First descriptor instance age = LoggedAccess() # Second descriptor instance def __init__(self, name, age): self.name = name # Calls the first descriptor self.age = age # Calls the second descriptor def birthday(self): self.age += 1
An interactive session shows that the Person
class has called __set_name__()
so that the field names would be recorded. Here we call vars()
to look up the descriptor without triggering it:
>>> vars(vars(Person)['name']) {'public_name': 'name', 'private_name': '_name'} >>> vars(vars(Person)['age']) {'public_name': 'age', 'private_name': '_age'}
ì´ì ì í´ëì¤ë nameê³¼ age 모ëì ëí ì¡ì¸ì¤ë¥¼ ë¡ê·¸ í©ëë¤:
>>> pete = Person('Peter P', 10) INFO:root:Updating 'name' to 'Peter P' INFO:root:Updating 'age' to 10 >>> kate = Person('Catherine C', 20) INFO:root:Updating 'name' to 'Catherine C' INFO:root:Updating 'age' to 20
ë ê°ì Person ì¸ì¤í´ì¤ìë ê°ì¸ ì´ë¦ë§ í¬í¨ë©ëë¤:
>>> vars(pete) {'_name': 'Peter P', '_age': 10} >>> vars(kate) {'_name': 'Catherine C', '_age': 20}ë§ë¬´ë¦¬ ìê°Â¶
A descriptor is what we call any object that defines __get__()
, __set__()
, or __delete__()
.
Optionally, descriptors can have a __set_name__()
method. This is only used in cases where a descriptor needs to know either the class where it was created or the name of class variable it was assigned to. (This method, if present, is called even if the class is not a descriptor.)
Descriptors get invoked by the dot operator during attribute lookup. If a descriptor is accessed indirectly with vars(some_class)[descriptor_name]
, the descriptor instance is returned without invoking it.
ëì¤í¬ë¦½í°ë í´ëì¤ ë³ìë¡ ì¬ì©ë ëë§ ìëí©ëë¤. ì¸ì¤í´ì¤ì ë£ì¼ë©´, í¨ê³¼ê° ììµëë¤.
ëì¤í¬ë¦½í°ì 주ì ë기ë í´ëì¤ ë³ìì ì ì¥ë ê°ì²´ê° ì´í¸ë¦¬ë·°í¸ ì¡°í ì¤ì ë°ìíë ì¼ì ì ì´ í ì ìëë¡ íë í ì ì ê³µíë ê²ì ëë¤.
ì íµì ì¼ë¡, í¸ì¶íë í´ëì¤ê° ì¡°í ì¤ì ì´ë¤ ì¼ì´ ì¼ì´ë ì§ ì ì´í©ëë¤. ëì¤í¬ë¦½í°ë ê·¸ ê´ê³ë¥¼ ìì ìí¤ê³ ì¡°í ì¤ì¸ ë°ì´í°ê° ë°ì¸ê¶ì ê°ëë¡ í©ëë¤.
ëì¤í¬ë¦½í°ë ì¸ì´ ì ì²´ìì ì¬ì©ë©ëë¤. í¨ìê° ì°ê²°ë ë©ìëë¡ ë°ëë ë°©ë²ì
ëë¤. classmethod()
, staticmethod()
, property()
ë° functools.cached_property()
ì ê°ì ì¼ë°ì ì¸ ë구ë 모ë ëì¤í¬ë¦½í°ë¡ 구íë©ëë¤.
ì´ ìììë, ì°¾ê¸°ê° ë§¤ì° ì´ë ¤ì´ ê²ì¼ë¡ ì ëª ëì ë°ì´í° ìì ë²ê·¸ì ìì¹ë¥¼ 찾기 ìí ì¤ì©ì ì´ê³ ê°ë ¥í ë구를 ë§ëëë¤.
ì í¨ì± ê²ì¬ê¸° í´ëì¤Â¶ì í¨ì± ê²ì¬ê¸°ë ê´ë¦¬ëë ì´í¸ë¦¬ë·°í¸ ì¡ì¸ì¤ë¥¼ ìí ëì¤í¬ë¦½í°ì ëë¤. ë°ì´í°ë¥¼ ì ì¥í기 ì ì, ì ê°ì´ ë¤ìí íê³¼ ë²ì ì íì 충족íëì§ íì¸í©ëë¤. ì´ë¬í ì í ì¬íì´ ì¶©ì¡±ëì§ ìì¼ë©´, ë°ì´í° ììì ë°©ì§í기 ìí´ ìì²ìì ìì¸ê° ë°ìí©ëë¤.
This Validator
class is both an abstract base class and a managed attribute descriptor:
from abc import ABC, abstractmethod class Validator(ABC): def __set_name__(self, owner, name): self.private_name = '_' + name def __get__(self, obj, objtype=None): return getattr(obj, self.private_name) def __set__(self, obj, value): self.validate(value) setattr(obj, self.private_name, value) @abstractmethod def validate(self, value): pass
Custom validators need to inherit from Validator
and must supply a validate()
method to test various restrictions as needed.
ë¤ìì ì¸ ê°ì§ ì¤ì©ì ì¸ ë°ì´í° ì í¨ì± ê²ì¬ ì í¸ë¦¬í°ì ëë¤:
OneOf
verifies that a value is one of a restricted set of options.
Number
verifies that a value is either an int
or float
. Optionally, it verifies that a value is between a given minimum or maximum.
String
verifies that a value is a str
. Optionally, it validates a given minimum or maximum length. It can validate a user-defined predicate as well.
class OneOf(Validator): def __init__(self, *options): self.options = set(options) def validate(self, value): if value not in self.options: raise ValueError( f'Expected {value!r} to be one of {self.options!r}' ) class Number(Validator): def __init__(self, minvalue=None, maxvalue=None): self.minvalue = minvalue self.maxvalue = maxvalue def validate(self, value): if not isinstance(value, (int, float)): raise TypeError(f'Expected {value!r} to be an int or float') if self.minvalue is not None and value < self.minvalue: raise ValueError( f'Expected {value!r} to be at least {self.minvalue!r}' ) if self.maxvalue is not None and value > self.maxvalue: raise ValueError( f'Expected {value!r} to be no more than {self.maxvalue!r}' ) class String(Validator): def __init__(self, minsize=None, maxsize=None, predicate=None): self.minsize = minsize self.maxsize = maxsize self.predicate = predicate def validate(self, value): if not isinstance(value, str): raise TypeError(f'Expected {value!r} to be an str') if self.minsize is not None and len(value) < self.minsize: raise ValueError( f'Expected {value!r} to be no smaller than {self.minsize!r}' ) if self.maxsize is not None and len(value) > self.maxsize: raise ValueError( f'Expected {value!r} to be no bigger than {self.maxsize!r}' ) if self.predicate is not None and not self.predicate(value): raise ValueError( f'Expected {self.predicate} to be true for {value!r}' )ì¤ì©ì ì¸ ìì©Â¶
ì¤ì í´ëì¤ìì ë°ì´í° ì í¨ì± ê²ì¬ê¸°ë¥¼ ì¬ì©íë ë°©ë²ì ë¤ìê³¼ ê°ìµëë¤:
class Component: name = String(minsize=3, maxsize=10, predicate=str.isupper) kind = OneOf('wood', 'metal', 'plastic') quantity = Number(minvalue=0) def __init__(self, name, kind, quantity): self.name = name self.kind = kind self.quantity = quantity
ëì¤í¬ë¦½í°ë ì못ë ì¸ì¤í´ì¤ê° ë§ë¤ì´ì§ë ê²ì ë°©ì§í©ëë¤:
>>> Component('Widget', 'metal', 5) # Blocked: 'Widget' is not all uppercase Traceback (most recent call last): ... ValueError: Expected <method 'isupper' of 'str' objects> to be true for 'Widget' >>> Component('WIDGET', 'metle', 5) # Blocked: 'metle' is misspelled Traceback (most recent call last): ... ValueError: Expected 'metle' to be one of {'metal', 'plastic', 'wood'} >>> Component('WIDGET', 'metal', -5) # Blocked: -5 is negative Traceback (most recent call last): ... ValueError: Expected -5 to be at least 0 >>> Component('WIDGET', 'metal', 'V') # Blocked: 'V' isn't a number Traceback (most recent call last): ... TypeError: Expected 'V' to be an int or float >>> c = Component('WIDGET', 'metal', 5) # Allowed: The inputs are valid기ì ì ì¸ ììµì¶
ë¤ìì ëì¤í¬ë¦½í°ì ìë ì리ì ì¸ë¶ ì¬íì ëí ë 기ì ì ì¸ ììµìì ëë¤.
ìì½Â¶ëì¤í¬ë¦½í°ë¥¼ ì ìíê³ , íë¡í ì½ì ìì½íë©° ëì¤í¬ë¦½í°ë¥¼ í¸ì¶íë ë°©ë²ì ë³´ì¬ì¤ëë¤. ê°ì²´ ê´ê³í 매í(object relational mappings)ì´ ìëíë ë°©ìì ë³´ì¬ì£¼ë ì를 ì ê³µí©ëë¤.
ëì¤í¬ë¦½í°ì ëí íìµì ë í° ë구 ì§í©ì ëí ì¡ì¸ì¤ë¥¼ ì ê³µí ë¿ë§ ìëë¼, íì´ì¬ì ìë ë°©ìì ëí ì¬ì¸µì ì¸ ì´í´ë¥¼ ë§ëëë¤.
ì ìì ìê°Â¶In general, a descriptor is an attribute value that has one of the methods in the descriptor protocol. Those methods are __get__()
, __set__()
, and __delete__()
. If any of those methods are defined for an attribute, it is said to be a descriptor.
ì´í¸ë¦¬ë·°í¸ ì¡ì¸ì¤ì 기본 ëìì ê°ì²´ì ëì
ë리ìì ì´í¸ë¦¬ë·°í¸ë¥¼ ê°ì ¸ì¤ê±°ë(get) ì¤ì íê±°ë(set) ìì íë(delete) ê²ì
ëë¤. ì를 ë¤ì´, a.x
ë a.__dict__['x']
ë¡ ììí ë¤ì type(a).__dict__['x']
를 ê±°ì³, type(a)
ì ë©ìë ê²°ì ììë¡ ê³ìëë ì¡°í ì²´ì¸ì ê°ìµëë¤. ì¡°íë ê°ì´ ëì¤í¬ë¦½í° ë©ìë ì¤ íë를 ì ìíë ê°ì²´ì´ë©´, íì´ì¬ì 기본 ëìì ëì²´íê³ ëì ëì¤í¬ë¦½í° ë©ìë를 í¸ì¶ í ì ììµëë¤. ì°ì ìì ì²´ì¸ìì ì´ê²ì´ ì´ë쯤 ë±ì¥íëì§ë ì´ë¤ ëì¤í¬ë¦½í° ë©ìëê° ì ìëìëì§ì ë°ë¼ ë¤ë¦
ëë¤.
Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and super()
. They are used throughout Python itself. Descriptors simplify the underlying C code and offer a flexible set of new tools for everyday Python programs.
descr.__get__(self, obj, type=None)
descr.__set__(self, obj, value)
descr.__delete__(self, obj)
ì´ê²ì´ ì ë¶ì ëë¤. ì´ë¬í ë©ìë ì¤ íë를 ì ìíììì¤, ê·¸ë¬ë©´ ê°ì²´ë¥¼ ëì¤í¬ë¦½í°ë¡ ê°ì£¼íê³ ì´í¸ë¦¬ë·°í¸ë¡ ì¡°íë ë 기본 ëìì ì¬ì ìí ì ììµëë¤.
If an object defines __set__()
or __delete__()
, it is considered a data descriptor. Descriptors that only define __get__()
are called non-data descriptors (they are often used for methods but other uses are possible).
ë°ì´í°ì ë¹ ë°ì´í° ëì¤í¬ë¦½í°ë ì¸ì¤í´ì¤ ëì ë리ì í목과 ê´ë ¨íì¬ ì¬ì ìê° ê³ì°ëë ë°©ìì´ ë¤ë¦ ëë¤. ì¸ì¤í´ì¤ ëì ë리ì ë°ì´í° ëì¤í¬ë¦½í°ì ì´ë¦ì´ ê°ì íëª©ì´ ìì¼ë©´, ë°ì´í° ëì¤í¬ë¦½í°ê° ì°ì í©ëë¤. ì¸ì¤í´ì¤ì ëì ë리ì ë¹ ë°ì´í° ëì¤í¬ë¦½í°ì ì´ë¦ì´ ê°ì íëª©ì´ ìì¼ë©´, ëì ë리 íëª©ì´ ì°ì í©ëë¤.
To make a read-only data descriptor, define both __get__()
and __set__()
with the __set__()
raising an AttributeError
when called. Defining the __set__()
method with an exception raising placeholder is enough to make it a data descriptor.
ëì¤í¬ë¦½í°ë desc.__get__(obj)
ë desc.__get__(None, cls)
ë¡ ì§ì í¸ì¶ í ì ììµëë¤.
íì§ë§ ì´í¸ë¦¬ë·°í¸ ì¡ì¸ì¤ ì ëì¤í¬ë¦½í°ê° ìëì¼ë¡ í¸ì¶ëë ê²ì´ ë ì¼ë°ì ì ëë¤.
The expression obj.x
looks up the attribute x
in the chain of namespaces for obj
. If the search finds a descriptor outside of the instance __dict__
, its __get__()
method is invoked according to the precedence rules listed below.
í¸ì¶ ì¸ë¶ ì¬íì obj
ê° ê°ì²´, í´ëì¤ í¹ì superì ì¸ì¤í´ì¤ì¸ì§ì ë°ë¼ ë¤ë¦
ëë¤.
Instance lookup scans through a chain of namespaces giving data descriptors the highest priority, followed by instance variables, then non-data descriptors, then class variables, and lastly __getattr__()
if it is provided.
a.x
ì ëí ëì¤í¬ë¦½í°ê° ë°ê²¬ëë©´, desc.__get__(a, type(a))
ë¡ í¸ì¶ë©ëë¤.
ì ì¡°íì ë¡ì§ì object.__getattribute__()
ì ììµëë¤. ë¤ìì ììí íì´ì¬ ë±ê°ë¬¼ì
ëë¤:
def find_name_in_mro(cls, name, default): "Emulate _PyType_Lookup() in Objects/typeobject.c" for base in cls.__mro__: if name in vars(base): return vars(base)[name] return default def object_getattribute(obj, name): "Emulate PyObject_GenericGetAttr() in Objects/object.c" null = object() objtype = type(obj) cls_var = find_name_in_mro(objtype, name, null) descr_get = getattr(type(cls_var), '__get__', null) if descr_get is not null: if (hasattr(type(cls_var), '__set__') or hasattr(type(cls_var), '__delete__')): return descr_get(cls_var, obj, objtype) # data descriptor if hasattr(obj, '__dict__') and name in vars(obj): return vars(obj)[name] # instance variable if descr_get is not null: return descr_get(cls_var, obj, objtype) # non-data descriptor if cls_var is not null: return cls_var # class variable raise AttributeError(name)
Note, there is no __getattr__()
hook in the __getattribute__()
code. That is why calling __getattribute__()
directly or with super().__getattribute__
will bypass __getattr__()
entirely.
Instead, it is the dot operator and the getattr()
function that are responsible for invoking __getattr__()
whenever __getattribute__()
raises an AttributeError
. Their logic is encapsulated in a helper function:
def getattr_hook(obj, name): "Emulate slot_tp_getattr_hook() in Objects/typeobject.c" try: return obj.__getattribute__(name) except AttributeError: if not hasattr(type(obj), '__getattr__'): raise return type(obj).__getattr__(obj, name) # __getattr__í´ëì¤ìì í¸ì¶Â¶
The logic for a dotted lookup such as A.x
is in type.__getattribute__()
. The steps are similar to those for object.__getattribute__()
but the instance dictionary lookup is replaced by a search through the classâs method resolution order.
ëì¤í¬ë¦½í°ê° ë°ê²¬ëë©´, desc.__get__(None, A)
ë¡ í¸ì¶ë©ëë¤.
The full C implementation can be found in type_getattro()
and _PyType_Lookup()
in Objects/typeobject.c.
The logic for superâs dotted lookup is in the __getattribute__()
method for object returned by super()
.
ì ì¡°í super(A, obj).m
ì obj.__class__.__mro__
ìì A
ë°ë¡ ë¤ìì ì¤ë ë² ì´ì¤ í´ëì¤ B
를 ê²ìí ë¤ì B.__dict__['m'].__get__(obj, A)
를 ë°íí©ëë¤. ëì¤í¬ë¦½í°ê° ìëë©´, m
ì´ ë³ê²½ëì§ ìì ìíë¡ ë°íë©ëë¤.
The full C implementation can be found in super_getattro()
in Objects/typeobject.c. A pure Python equivalent can be found in Guidoâs Tutorial.
The mechanism for descriptors is embedded in the __getattribute__()
methods for object
, type
, and super()
.
기ìµí´ì¼ í ì¤ìí ì¬íì ë¤ìê³¼ ê°ìµëë¤:
Descriptors are invoked by the __getattribute__()
method.
í´ëì¤ë object
, type
ëë super()
ë¡ë¶í° ì´ ì 차를 ììí©ëë¤.
Overriding __getattribute__()
prevents automatic descriptor calls because all the descriptor logic is in that method.
object.__getattribute__()
and type.__getattribute__()
make different calls to __get__()
. The first includes the instance and may include the class. The second puts in None
for the instance and always includes the class.
ë°ì´í° ëì¤í¬ë¦½í°ë íì ì¸ì¤í´ì¤ ëì ë리를 ëì²´í©ëë¤.
ë¹ ë°ì´í° ëì¤í¬ë¦½í°ë ì¸ì¤í´ì¤ ëì ëë¦¬ë¡ ëì²´ë ì ììµëë¤.
Sometimes it is desirable for a descriptor to know what class variable name it was assigned to. When a new class is created, the type
metaclass scans the dictionary of the new class. If any of the entries are descriptors and if they define __set_name__()
, that method is called with two arguments. The owner is the class where the descriptor is used, and the name is the class variable the descriptor was assigned to.
The implementation details are in type_new()
and set_names()
in Objects/typeobject.c.
Since the update logic is in type.__new__()
, notifications only take place at the time of class creation. If descriptors are added to the class afterwards, __set_name__()
will need to be called manually.
The following code is a simplified skeleton showing how data descriptors could be used to implement an object relational mapping.
íµì¬ ìì´ëì´ë ë°ì´í°ê° ì¸ë¶ ë°ì´í°ë² ì´ì¤ì ì ì¥ëë¤ë ê²ì ëë¤. íì´ì¬ ì¸ì¤í´ì¤ë ë°ì´í°ë² ì´ì¤ í ì´ë¸ì ëí í¤ë§ ë³´ì í©ëë¤. ëì¤í¬ë¦½í°ê° ì¡°íë ê°±ì ì ì²ë¦¬í©ëë¤:
class Field: def __set_name__(self, owner, name): self.fetch = f'SELECT {name} FROM {owner.table} WHERE {owner.key}=?;' self.store = f'UPDATE {owner.table} SET {name}=? WHERE {owner.key}=?;' def __get__(self, obj, objtype=None): return conn.execute(self.fetch, [obj.key]).fetchone()[0] def __set__(self, obj, value): conn.execute(self.store, [value, obj.key]) conn.commit()
We can use the Field
class to define models that describe the schema for each table in a database:
class Movie: table = 'Movies' # Table name key = 'title' # Primary key director = Field() year = Field() def __init__(self, key): self.key = key class Song: table = 'Music' key = 'title' artist = Field() year = Field() genre = Field() def __init__(self, key): self.key = key
모ë¸ì ì¬ì©íë ¤ë©´, 먼ì ë°ì´í°ë² ì´ì¤ì ì°ê²°íììì¤:
>>> import sqlite3 >>> conn = sqlite3.connect('entertainment.db')
ëí í ì¸ì ì ë°ì´í°ë² ì´ì¤ìì ë°ì´í°ë¥¼ 꺼ë´ë ë°©ë²ê³¼ ë°ì´í°ë¥¼ ê°±ì íë ë°©ë²ì ë³´ì¬ì¤ëë¤:
>>> Movie('Star Wars').director 'George Lucas' >>> jaws = Movie('Jaws') >>> f'Released in {jaws.year} by {jaws.director}' 'Released in 1975 by Steven Spielberg' >>> Song('Country Roads').artist 'John Denver' >>> Movie('Star Wars').director = 'J.J. Abrams' >>> Movie('Star Wars').director 'J.J. Abrams'ììí íì´ì¬ ë±ê°ë¬¼Â¶
ëì¤í¸ë¦½í° íë¡í ì½ì ê°ë¨íê³ í¥ë¯¸ë¡ì´ ê°ë¥ì±ì ì ê³µí©ëë¤. ëª ê°ì§ ì ì¤ ì¼ì´ì¤ë ì주 íí´ì ë´ì¥ ë구ì 미리 í¨í¤ì§ëììµëë¤. íë¡í¼í°, ì°ê²°ë ë©ìë, ì ì ë©ìë, í´ëì¤ ë©ìë ë° __slots__ë 모ë ëì¤í¬ë¦½í° íë¡í ì½ì 기ë°ì¼ë¡ í©ëë¤.
íë¡í¼í°Â¶property()
í¸ì¶ì ì´í¸ë¦¬ë·°í¸ì ì¡ì¸ì¤í ë í¨ì í¸ì¶ì í¸ë¦¬ê±° íë ë°ì´í° ëì¤í¬ë¦½í°ë¥¼ ìì±íë ê°ê²°í ë°©ë²ì
ëë¤. ìëª
ì ë¤ìê³¼ ê°ìµëë¤:
property(fget=None, fset=None, fdel=None, doc=None) -> property
ì¤ëª
(doc)ì ê´ë¦¬ëë ì´í¸ë¦¬ë·°í¸ x
를 ì ìíë ì¼ë°ì ì¸ ì¬ì©ë²ì ë³´ì¬ì¤ëë¤:
class C: def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "I'm the 'x' property.")
To see how property()
is implemented in terms of the descriptor protocol, here is a pure Python equivalent that implements most of the core functionality:
class Property: "Emulate PyProperty_Type() in Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel if doc is None and fget is not None: doc = fget.__doc__ self.__doc__ = doc def __set_name__(self, owner, name): self.__name__ = name def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)
property()
ë´ì¥ì ì¬ì©ì ì¸í°íì´ì¤ê° ì´í¸ë¦¬ë·°í¸ ì¡ì¸ì¤ë¥¼ íê°í í íì ë³ê²½ì´ ë©ìëì ê°ì
ì ì구í ë ëìì ì¤ëë¤.
ì를 ë¤ì´, ì¤íë ëìí¸ í´ëì¤ë Cell('b10').value
를 íµí´ ì
ê°ì ëí ì¡ì¸ì¤ë¥¼ íê°í ì ììµëë¤. íë¡ê·¸ë¨ì ëí íì ê°ì ì ì¡ì¸ì¤í ëë§ë¤ ì
ì´ ì¬ê³ì°ë ê²ì ì구í©ëë¤; íì§ë§, íë¡ê·¸ë머ë ì´í¸ë¦¬ë·°í¸ì ì§ì ì¡ì¸ì¤íë 기존 í´ë¼ì´ì¸í¸ ì½ëì ìí¥ì 미ì¹ê³ ì¶ì§ ììµëë¤. í´ê²°ì±
ì íë¡í¼í° ë°ì´í° ëì¤í¬ë¦½í°ë¡ value ì´í¸ë¦¬ë·°í¸ì ëí ì¡ì¸ì¤ë¥¼ ê°ì¸ë ê²ì
ëë¤:
class Cell: ... @property def value(self): "Recalculate the cell before returning value" self.recalc() return self._value
Either the built-in property()
or our Property()
equivalent would work in this example.
íì´ì¬ì ê°ì²´ ì§í¥ 기ë¥ì í¨ì ê¸°ë° íê²½ì 기ë°ì¼ë¡ í©ëë¤. ë¹ ë°ì´í° ëì¤í¬ë¦½í°ë¥¼ ì¬ì©íë©´, ë ê°ê° 매ëë½ê² ë³í©ë©ëë¤.
í´ëì¤ ëì ë리ì ì ì¥ë í¨ìë í¸ì¶ë ë ë©ìëë¡ ë°ëëë¤. ê°ì²´ ì¸ì¤í´ì¤ê° ë¤ë¥¸ ì¸ìë¤ ìì ì¶ê°ëë¤ë ì ììë§ ë©ìëê° ì¼ë° í¨ìì ë¤ë¦ ëë¤. ê´ë¡ì ë°ë¼, ì´ ì¸ì¤í´ì¤ë selfë¼ê³ íì§ë§ thisë ë¤ë¥¸ ì´ë¤ ë³ì ì´ë¦ë ë ì ììµëë¤.
ëëµ ë¤ìê³¼ ëë±í types.MethodType
ì ì¬ì©íì¬ ë©ìë를 ìëì¼ë¡ ë§ë¤ ì ììµëë¤:
class MethodType: "Emulate PyMethod_Type in Objects/classobject.c" def __init__(self, func, obj): self.__func__ = func self.__self__ = obj def __call__(self, *args, **kwargs): func = self.__func__ obj = self.__self__ return func(obj, *args, **kwargs) def __getattribute__(self, name): "Emulate method_getset() in Objects/classobject.c" if name == '__doc__': return self.__func__.__doc__ return object.__getattribute__(self, name) def __getattr__(self, name): "Emulate method_getattro() in Objects/classobject.c" return getattr(self.__func__, name) def __get__(self, obj, objtype=None): "Emulate method_descr_get() in Objects/classobject.c" return self
To support automatic creation of methods, functions include the __get__()
method for binding methods during attribute access. This means that functions are non-data descriptors that return bound methods during dotted lookup from an instance. Hereâs how it works:
class Function: ... def __get__(self, obj, objtype=None): "Simulate func_descr_get() in Objects/funcobject.c" if obj is None: return self return MethodType(self, obj)
ì¸í°í리í°ìì ë¤ì í´ëì¤ë¥¼ ì¤ííë©´ ì¤ì ë¡ í¨ì ëì¤í¬ë¦½í°ê° ìëíë ë°©ìì ë³´ì¬ì¤ëë¤:
class D: def f(self): return self class D2: pass
í¨ììë ë´ë¶ ê²ì¬ë¥¼ ì§ìíë ì ê·íë ì´ë¦ ì´í¸ë¦¬ë·°í¸ê° ììµëë¤:
>>> D.f.__qualname__ 'D.f'
Accessing the function through the class dictionary does not invoke __get__()
. Instead, it just returns the underlying function object:
>>> D.__dict__['f'] <function D.f at 0x00C45070>
Dotted access from a class calls __get__()
which just returns the underlying function unchanged:
>>> D.f <function D.f at 0x00C45070>
The interesting behavior occurs during dotted access from an instance. The dotted lookup calls __get__()
which returns a bound method object:
>>> d = D() >>> d.f <bound method D.f of <__main__.D object at 0x00B18C90>>
ë´ë¶ì ì¼ë¡, ì°ê²°ë ë©ìëë íë¶ í¨ìì ì°ê²°ë ì¸ì¤í´ì¤ë¥¼ ì ì¥í©ëë¤.
>>> d.f.__func__ <function D.f at 0x00C45070> >>> d.f.__self__ <__main__.D object at 0x00B18C90>
ì¼ë° ë©ìëìì selfê° ì´ëìì ì¤ëì§ ëë í´ëì¤ ë©ìëìì clsê° ì´ëìì ì¤ëì§ ê¶ê¸í ì ì´ ìë¤ë©´, ë°ë¡ ì´ê²ëë¤!
ë©ìëì ì¢ ë¥Â¶ë¹ ë°ì´í° ëì¤í¬ë¦½í°ë í¨ìì ë©ìë를 ë°ì¸ë©íë ì¼ë°ì ì¸ í¨í´ì ë³ííë ê°ë¨í ë©ì»¤ëì¦ì ì ê³µí©ëë¤.
To recap, functions have a __get__()
method so that they can be converted to a method when accessed as attributes. The non-data descriptor transforms an obj.f(*args)
call into f(obj, *args)
. Calling cls.f(*args)
becomes f(*args)
.
ì´ íë ì°ê²°ê³¼ ê°ì¥ ì ì©í ë ê°ì§ ë³íì ìì½í©ëë¤:
ì ì ë©ìë¶ë³í
ê°ì²´ìì í¸ì¶
í´ëì¤ìì í¸ì¶
í¨ì
f(obj, *args)
f(*args)
staticmethod
f(*args)
f(*args)
classmethod
f(type(obj), *args)
f(cls, *args)
ì ì ë©ìëë ë³ê²½ ìì´ íë¶ í¨ì를 ë°íí©ëë¤. c.f
ë C.f
í¸ì¶ì object.__getattribute__(c, "f")
ë object.__getattribute__(C, "f")
를 ì§ì ì¡°ííë ê²ê³¼ ëë±í©ëë¤. ê²°ê³¼ì ì¼ë¡, í¨ìë ê°ì²´ë í´ëì¤ìì ëì¼íê² ì¡ì¸ì¤ í ì ììµëë¤.
ì ì ë©ìëì ì í©í íë³´ë self
ë³ì를 참조íì§ ìë ë©ìëì
ëë¤.
For instance, a statistics package may include a container class for experimental data. The class provides normal methods for computing the average, mean, median, and other descriptive statistics that depend on the data. However, there may be useful functions which are conceptually related but do not depend on the data. For instance, erf(x)
is handy conversion routine that comes up in statistical work but does not directly depend on a particular dataset. It can be called either from an object or the class: s.erf(1.5) --> 0.9332
or Sample.erf(1.5) --> 0.9332
.
ì ì ë©ìëë ë³ê²½ ìì´ íë¶ í¨ì를 ë°ííë¯ë¡, ìì í¸ì¶ì í¥ë¯¸ë¡ì§ ììµëë¤:
class E: @staticmethod def f(x): return x * 10
>>> E.f(3) 30 >>> E().f(3) 30
ë¹ ë°ì´í° ëì¤í¬ë¦½í° íë¡í ì½ì ì¬ì©íë©´, ìì íì´ì¬ ë²ì ì staticmethod()
ë ë¤ìê³¼ ê°ìµëë¤:
import functools class StaticMethod: "Emulate PyStaticMethod_Type() in Objects/funcobject.c" def __init__(self, f): self.f = f functools.update_wrapper(self, f) def __get__(self, obj, objtype=None): return self.f def __call__(self, *args, **kwds): return self.f(*args, **kwds)
The functools.update_wrapper()
call adds a __wrapped__
attribute that refers to the underlying function. Also it carries forward the attributes necessary to make the wrapper look like the wrapped function: __name__
, __qualname__
, __doc__
, and __annotations__
.
ì ì ë©ìëì ë¬ë¦¬, í´ëì¤ ë©ìëë í¨ì를 í¸ì¶í기 ì ì í´ëì¤ ì°¸ì¡°ë¥¼ ì¸ì ëª©ë¡ ìì ì¶ê°í©ëë¤. ì´ íìì í¸ì¶ìê° ê°ì²´ë í´ëì¤ì¼ ë ê°ìµëë¤:
class F: @classmethod def f(cls, x): return cls.__name__, x
>>> F.f(3) ('F', 3) >>> F().f(3) ('F', 3)
ì´ ëìì í¨ìê° í´ëì¤ ì°¸ì¡° ë§ íìíê³ í¹ì ì¸ì¤í´ì¤ì ì ì¥ë ë°ì´í°ì ìì¡´íì§ ìì ë ì ì©í©ëë¤. í´ëì¤ ë©ìëì í ê°ì§ ì©ëë ëì²´ í´ëì¤ ìì±ì를 ë§ëë ê²ì
ëë¤. ì를 ë¤ì´, í´ëì¤ ë©ìë dict.fromkeys()
ë í¤ ë¦¬ì¤í¸ìì ì ëì
ë리를 ë§ëëë¤. ììí íì´ì¬ ëë± ë¬¼ì ë¤ìê³¼ ê°ìµëë¤:
class Dict(dict): @classmethod def fromkeys(cls, iterable, value=None): "Emulate dict_fromkeys() in Objects/dictobject.c" d = cls() for key in iterable: d[key] = value return d
ì´ì ê³ ì í¤ì ìë¡ì´ ëì ë리를 ë¤ìê³¼ ê°ì´ êµ¬ì± í ì ììµëë¤:
>>> d = Dict.fromkeys('abracadabra') >>> type(d) is Dict True >>> d {'a': None, 'b': None, 'r': None, 'c': None, 'd': None}
ë¹ ë°ì´í° ëì¤í¬ë¦½í° íë¡í ì½ì ì¬ì©íë©´, ìì íì´ì¬ ë²ì ì classmethod()
ë ë¤ìê³¼ ê°ìµëë¤:
import functools class ClassMethod: "Emulate PyClassMethod_Type() in Objects/funcobject.c" def __init__(self, f): self.f = f functools.update_wrapper(self, f) def __get__(self, obj, cls=None): if cls is None: cls = type(obj) return MethodType(self.f, cls)
The functools.update_wrapper()
call in ClassMethod
adds a __wrapped__
attribute that refers to the underlying function. Also it carries forward the attributes necessary to make the wrapper look like the wrapped function: __name__
, __qualname__
, __doc__
, and __annotations__
.
í´ëì¤ê° __slots__
를 ì ìíë©´, ì¸ì¤í´ì¤ ëì
ë리를 ì¬ë¡¯ê°ì ê³ ì ê¸¸ì´ ë°°ì´ë¡ ë°ê¿ëë¤. ì¬ì©ì ê´ì ìì ì¬ë¬ ê°ì§ í¨ê³¼ê° ììµëë¤:
1 - ì² ìê° ì못ë ì´í¸ë¦¬ë·°í¸ ëì
ì¼ë¡ ì¸í ë²ê·¸ë¥¼ ì¦ì ê°ì§í©ëë¤. __slots__
ì ì§ì ë ì´í¸ë¦¬ë·°í¸ ì´ë¦ ë§ íì©ë©ëë¤:
class Vehicle: __slots__ = ('id_number', 'make', 'model')
>>> auto = Vehicle() >>> auto.id_nubmer = 'VYE483814LQEX' Traceback (most recent call last): ... AttributeError: 'Vehicle' object has no attribute 'id_nubmer'
2 - ëì¤í¬ë¦½í°ê° __slots__
ì ì ì¥ë ê°ì¸ ì´í¸ë¦¬ë·°í¸ì ëí ì¡ì¸ì¤ë¥¼ ê´ë¦¬íë ë¶ë³ ê°ì²´ë¥¼ ë§ëë ë° ëìì´ ë©ëë¤:
class Immutable: __slots__ = ('_dept', '_name') # Replace the instance dictionary def __init__(self, dept, name): self._dept = dept # Store to private attribute self._name = name # Store to private attribute @property # Read-only descriptor def dept(self): return self._dept @property def name(self): # Read-only descriptor return self._name
>>> mark = Immutable('Botany', 'Mark Watney') >>> mark.dept 'Botany' >>> mark.dept = 'Space Pirate' Traceback (most recent call last): ... AttributeError: property 'dept' of 'Immutable' object has no setter >>> mark.location = 'Mars' Traceback (most recent call last): ... AttributeError: 'Immutable' object has no attribute 'location'
3 - ë©ëª¨ë¦¬ë¥¼ ì ì½í©ëë¤. 64ë¹í¸ 리ë
ì¤ ë¹ëìì ë ê°ì ì´í¸ë¦¬ë·°í¸ê° ìë ì¸ì¤í´ì¤ë __slots__
ê° ìì¼ë©´ 48ë°ì´í¸, ìì¼ë©´ 152ë°ì´í¸ë¥¼ ì¬ì©í©ëë¤. ì´ íë¼ì´ì¨ì´í¸(flyweight) ëìì¸ í¨í´ì ë§ì ìì ì¸ì¤í´ì¤ê° ë§ë¤ì´ì§ ëë§ ì¤ìí©ëë¤.
4. Improves speed. Reading instance variables is 35% faster with __slots__
(as measured with Python 3.10 on an Apple M1 processor).
5. Blocks tools like functools.cached_property()
which require an instance dictionary to function correctly:
from functools import cached_property class CP: __slots__ = () # Eliminates the instance dict @cached_property # Requires an instance dict def pi(self): return 4 * sum((-1.0)**n / (2.0*n + 1.0) for n in reversed(range(100_000)))
>>> CP().pi Traceback (most recent call last): ... TypeError: No '__dict__' attribute on 'CP' instance to cache 'pi' property.
__slots__
ì ì íí ìì íì´ì¬ ëë¡ì¸ ë²ì ì ë§ëë ê²ì ë¶ê°ë¥í©ëë¤. C 구조체ì ì§ì ì¡ì¸ì¤íê³ ê°ì²´ ë©ëª¨ë¦¬ í ë¹ì ì ì´í´ì¼ í기 ë문ì
ëë¤. ê·¸ë¬ë, ì¬ë¡¯ì ëí ì¤ì C êµ¬ì¡°ì²´ê° ê°ì¸ _slotvalues
리ì¤í¸ì ìí´ ëª¨ì¬ëë ê°ì¥ ì¶©ì¤í ì뮬ë ì´ì
ì êµ¬ì¶ í ì ììµëë¤. í´ë¹ ê°ì¸ 구조체ì ëí ì½ê¸°ì ì°ê¸°ë ë©¤ë² ëì¤í¬ë¦½í°ì ìí´ ê´ë¦¬ë©ëë¤:
null = object() class Member: def __init__(self, name, clsname, offset): 'Emulate PyMemberDef in Include/structmember.h' # Also see descr_new() in Objects/descrobject.c self.name = name self.clsname = clsname self.offset = offset def __get__(self, obj, objtype=None): 'Emulate member_get() in Objects/descrobject.c' # Also see PyMember_GetOne() in Python/structmember.c if obj is None: return self value = obj._slotvalues[self.offset] if value is null: raise AttributeError(self.name) return value def __set__(self, obj, value): 'Emulate member_set() in Objects/descrobject.c' obj._slotvalues[self.offset] = value def __delete__(self, obj): 'Emulate member_delete() in Objects/descrobject.c' value = obj._slotvalues[self.offset] if value is null: raise AttributeError(self.name) obj._slotvalues[self.offset] = null def __repr__(self): 'Emulate member_repr() in Objects/descrobject.c' return f'<Member {self.name!r} of {self.clsname!r}>'
The type.__new__()
method takes care of adding member objects to class variables:
class Type(type): 'Simulate how the type metaclass adds member objects for slots' def __new__(mcls, clsname, bases, mapping, **kwargs): 'Emulate type_new() in Objects/typeobject.c' # type_new() calls PyTypeReady() which calls add_methods() slot_names = mapping.get('slot_names', []) for offset, name in enumerate(slot_names): mapping[name] = Member(name, clsname, offset) return type.__new__(mcls, clsname, bases, mapping, **kwargs)
object.__new__()
ë©ìëë ì¸ì¤í´ì¤ ëì
ë리 ëì ì¬ë¡¯ì´ ìë ì¸ì¤í´ì¤ë¥¼ ë§ëë ê²ì ê´ì¥í©ëë¤. ë¤ìì ìì íì´ì¬ì ëëµì ì¸ ì뮬ë ì´ì
ì
ëë¤:
class Object: 'Simulate how object.__new__() allocates memory for __slots__' def __new__(cls, *args, **kwargs): 'Emulate object_new() in Objects/typeobject.c' inst = super().__new__(cls) if hasattr(cls, 'slot_names'): empty_slots = [null] * len(cls.slot_names) object.__setattr__(inst, '_slotvalues', empty_slots) return inst def __setattr__(self, name, value): 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c' cls = type(self) if hasattr(cls, 'slot_names') and name not in cls.slot_names: raise AttributeError( f'{cls.__name__!r} object has no attribute {name!r}' ) super().__setattr__(name, value) def __delattr__(self, name): 'Emulate _PyObject_GenericSetAttrWithDict() Objects/object.c' cls = type(self) if hasattr(cls, 'slot_names') and name not in cls.slot_names: raise AttributeError( f'{cls.__name__!r} object has no attribute {name!r}' ) super().__delattr__(name)
To use the simulation in a real class, just inherit from Object
and set the metaclass to Type
:
class H(Object, metaclass=Type): 'Instance variables stored in slots' slot_names = ['x', 'y'] def __init__(self, x, y): self.x = x self.y = y
ì´ ìì ìì, ë©í í´ëì¤ë x ì yì ëí ë©¤ë² ê°ì²´ë¥¼ ë¡ëíìµëë¤:
>>> from pprint import pp >>> pp(dict(vars(H))) {'__module__': '__main__', '__doc__': 'Instance variables stored in slots', 'slot_names': ['x', 'y'], '__init__': <function H.__init__ at 0x7fb5d302f9d0>, 'x': <Member 'x' of 'H'>, 'y': <Member 'y' of 'H'>}
ì¸ì¤í´ì¤ê° ë§ë¤ì´ì§ ë, ì´í¸ë¦¬ë·°í¸ê° ì ì¥ëë slot_values
리ì¤í¸ë¥¼ ê°ìµëë¤:
>>> h = H(10, 20) >>> vars(h) {'_slotvalues': [10, 20]} >>> h.x = 55 >>> vars(h) {'_slotvalues': [55, 20]}
ì² ìê° í리거ë ì§ì ëì§ ìì ì´í¸ë¦¬ë·°í¸ë ìì¸ë¥¼ ë°ììíµëë¤:
>>> h.xz Traceback (most recent call last): ... AttributeError: 'H' object has no attribute 'xz'
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4