Python dynamic properties and features

Michael Amin 2021-09-15 08:45:07

learn from 《 smooth python》

1. Use dynamic attributes to transform data

  • stay Python in , Data attribute And processing data Method Generally referred to as the attribute (attribute). Actually , Method is only a callable property
  • We can also create characteristic (property)
from urllib.request import urlopen
import warnings
import os
import json
URL = 'http://www.oreilly.com/pub/sc/osconfeed'
JSON = './osconfeed.json'
def load():
if not os.path.exists(JSON):
msg = 'downloading {} to {}'.format(URL, JSON)
warnings.warn(msg) # Send out a reminder 
with urlopen(URL) as remote, open(JSON, 'wb') as local:
# Use two context managers 
local.write(remote.read())
# Read and save remote files 
with open(JSON) as fp:
return json.load(fp)
feed = load()
print(feed)
print(sorted(feed['Schedule'].keys()))
for key, value in sorted(feed['Schedule'].items()):
print('{:3} {}'.format(len(value), key))
print(feed['Schedule']['speakers'][-1]['serial'])
# This syntax is too long ... How to improve 
from collections import abc
class FrozenJSON:
# A read-only interface , Use attribute notation to access JSON Class object 
def __init__(self, mapping):
self.__data = dict(mapping)
def __getattr__(self, name):
if hasattr(self.__data, name): # There are properties , obtain 
return getattr(self.__data, name)
# call keys And other methods are handled in this way 
else: # No, , structure FrozenJSON
return FrozenJSON.build(self.__data[name])
@classmethod # Alternative construction methods ,@classmethod Decorators are often used like this 
def build(cls, obj):
if isinstance(obj, abc.Mapping):
return cls(obj) # structure FrozenJSON
elif isinstance(obj, abc.MutableSequence):
# It's a sequence , For each element build
return [cls.build(item) for item in obj]
else:
return obj
raw_feed = load()
feed = FrozenJSON(raw_feed)
print(len(feed.Schedule.speakers))
print(sorted(feed.Schedule.keys()))
# ['conferences', 'events', 'speakers', 'venues']
print(feed.Schedule.events[-1].name)
# Why Schools Don't Use Open Source to Teach Programming
p = feed.Schedule.events[-1]
print(type(p))
# <class '__main__.FrozenJSON'>
print(p.name)
# Why Schools Don't Use Open Source to Teach Programming
print(p.speakers)
# [157509]
print(p.age)
# KeyError: 'age'
  • Handle invalid property name , for example Built in keywords , keyword.iskeyword
grad = FrozenJSON({
'name': 'Jim Bo', 'class': 1982})
# print(grad.class) # invalid syntax
print(getattr(grad, 'class')) # 1982

Modify the constructor of the class :

def __init__(self, mapping):
self.__data = {
}
for k,v in mapping.items():
if keyword.iskeyword(k): # If it is a keyword , Add underscore suffix 
k += "_"
self.__data[k] = v
  • Invalid naming ,str.isidentifier()
grad = FrozenJSON({
'2name': 'Jim Bo', 'class': 1982})
print(grad.2name) # SyntaxError: invalid syntax

Change of name

def __init__(self, mapping):
self.__data = {
}
for k,v in mapping.items():
if keyword.iskeyword(k):
k += "_"
if not k.isidentifier(): # Not a legal name , Change it 
k = "_" + k
self.__data[k] = v
print(grad._2name) # Jim Bo
  • __init__ The way is “ Initialization method ”. The real construction method is __new__( But you hardly need to write it yourself )
# Build the pseudo code of the object 
def object_maker(the_class, some_arg):
new_object = the_class.__new__(some_arg) # new Method can also return other class objects 
if isinstance(new_object, the_class):
the_class.__init__(new_object, some_arg)
return new_object
class FrozenJSON:
# A read-only interface , Use attribute notation to access JSON Class object 
def __new__(cls, arg): # The first parameter is the class itself 
if isinstance(arg, abc.Mapping):
return super().__new__(cls)
elif isinstance(arg, abc.MutableSequence):
return [cls(item) for item in arg]
else:
return arg
def __init__(self, mapping):
self.__data = {
}
for k, v in mapping.items():
if keyword.iskeyword(k):
k += "_"
if not k.isidentifier():
k = "_" + k
self.__data[k] = v
def __getattr__(self, name):
if hasattr(self.__data, name): # There are properties , obtain 
return getattr(self.__data, name)
# call keys And other methods are handled in this way 
else: # No, , structure FrozenJSON
return FrozenJSON(self.__data[name])

2. @property

https://www.liaoxuefeng.com/wiki/1016959663602400/1017502538658208
Please use @property Give me a Screen Object plus width and height attribute , And one. Read-only property resolution

class Screen(object):
def __init__(self):
self._w = 0
self._h = 0
self._r = 786432
@property # Turn the method into an attribute , Call without adding ()
def width(self):
return self._w
@property
def height(self):
return self._h
@width.setter # You can set property values , No, the method is a read-only property 
def width(self, v):
self._w = v
@height.setter
def height(self, v):
self._h = v
@property
def resolution(self):
return self._r
s = Screen()
s.width = 1024
s.height = 768
print('resolution =', s.resolution)
if s.resolution == 786432:
print(' The test passed !')
else:
print(' Test to fail !')
  • The characteristics are Class properties , But the of feature management is Instance attributes The access
class Class:
data = "class data attr"
@property
def prop(self):
return "prop value"
obj = Class()
print(vars(obj)) # {}, vars The function returns obj Of __dict__ attribute 
print(obj.data) # class data attr
obj.data = "changed"
print(vars(obj)) # {'data': 'changed'}
print(Class.data) # class data attr
# The example has been modified data, however Class properties have not been modified 
print(Class.prop) # <property object at 0x0000021A91E4A680>
print(obj.prop) # prop value
# obj.prop = "changed prop" # Report errors can't set attribute
obj.__dict__["prop"] = "changed prop1"
print(vars(obj)) # {'data': 'changed', 'prop': 'changed prop1'}
print(obj.prop) # prop value #
# Read obj.prop The read value method of the feature will still be run . Properties are not masked by instance properties 
Class.prop = "haha" # Cover Class.prop characteristic , Destroy property objects 
print(obj.prop) # changed prop1
# Now? ,obj.prop Get the instance property .
# Class.prop Not a feature , So it won't cover obj.prop.
print(obj.data) # changed
print(Class.data) # class data attr
Class.data = property(lambda self : "data prop value")
# Overwrite... With new features Class.data
print(obj.data) # data prop value
# obj.data By Class.data Characteristics mask 
del Class.data # Delete feature 
print(obj.data) # changed
# Restore as is ,obj.data Get the instance property data

obj.attr Such an expression Can't from obj Start looking for attr, But from obj.__class__ Start , and , Only if No, be known as attr When ,Python Will stay obj Find in examples .

This rule applies not only to features , It also applies to an entire class of descriptors —— Overlay descriptor (overriding descriptor)

2.1 help() file

Use the decorator to create property Object time , Value reading method ( Yes @property Decorator Methods ) The document string as a whole , Become a feature document

>>> class Foo:
@property
def bar(self):
''' documentation '''
return self.__dict__['bar']
@bar.setter
def bar(self, val):
self.__dict__['bar'] = val
>>> help(Foo)
Help on class Foo in module __main__:
class Foo(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)
|
| bar
| documentation
>>> help(Foo.bar)
Help on property:
documentation
  • Classical writing , Pass in doc Parameters
weight = property(get_weight, set_weight, doc='weight in kilograms')

3. Property factory function

In order to reduce writing getter,setter, You can use feature factory functions

def quantity(storage_name):
def qty_getter(instance):
return instance.__dict__[storage_name]
def qty_setter(instance, value):
if value > 0:
instance.__dict__[storage_name] = value
else:
raise ValueError("value must be > 0")
return property(qty_getter, qty_setter)
class LineItem:
weight = quantity('weight') # Use the property factory function to define weight Class properties 
price = quantity('price') # price attribute 
def __init__(self, description, weight, price):
self.description = description
self.weight = weight # Activate properties , Make sure not to be negative and 0
self.price = price
def subtotal(self):
return self.weight * self.price # Use the value stored in the property 
line1 = LineItem("name1", 8, 13.5)
print(line1.weight, line1.price) # 8 13.5
print(sorted(vars(line1).items()))
# [('description', 'name1'), ('price', 13.5), ('weight', 8)]

weight characteristic Cover 了 weight Instance attributes , So right. self.weight or obj.weight Of Each reference consists of Property function processing , Only direct access __dict__ Properties can skip Feature processing logic

4. Property deletion

del operation , Deleting attributes is rare , however python Support this operation

class BlackKnight:
def __init__(self):
self.members = ['an arm',
'another arm',
'a leg',
'another leg']
self.phrases = ["'Tis but a scratch.",
"It's just a flesh wound.",
"I'm invincible!",
"All right, we'll call it a draw."]
@property
def member(self):
print("next member is:")
return self.members[0]
@member.deleter
def member(self):
text = 'BLACK KNIGHT (loses {})\n-- {}'
print(text.format(self.members.pop(0), self.phrases.pop(0)))
knight = BlackKnight()
print(knight.member)
# next member is:
# an arm
del knight.member
# BLACK KNIGHT (loses an arm)
# -- 'Tis but a scratch.
del knight.member
# BLACK KNIGHT (loses another arm)
# -- It's just a flesh wound.
del knight.member
# BLACK KNIGHT (loses a leg)
# -- I'm invincible!
del knight.member
# BLACK KNIGHT (loses another leg)
# -- All right, we'll call it a draw.
del knight.member
# IndexError: pop from empty list
  • Classical writing ,fdel Parameter setting delete function
member = property(member_getter, fdel=member_deleter)
  • If you don't use features , It can also realize low-level special __delattr__ Method treatment Delete attribute The operation of

5. Handle important properties and functions of properties

  • __class__ Reference to the class to which the object belongs ( namely obj.__class__ And type(obj) The action phase of Same as )
    Python Some special methods of , for example __getattr__, Look only in the class of the object , Instead of looking for
  • __dict__ A mapping , Stores the writable properties of an object or class .
    Yes __dict__ Object of property , Anytime Set new properties at will
    If the class has __slots__ attribute , An example of it There may be no __dict__ attribute
  • __slots__
    Class can define this property , Restrict the properties of an instance
    __slots__ attribute The value of is a tuple of strings , Indicates the allowed attributes
    If __slots__ There is no '__dict__', Then the instance of this class does not __dict__ attribute , Instances are only allowed to have properties with the specified name

5.1 Built in functions that handle properties

  • dir([object])
    List most properties of the object ,dir Functions also Not listed Class , for example __mro__、__bases__ and __name__
>>> dir(Foo)
['__class__', '__delattr__', '__dict__', '__dir__',
'__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__',
'__ne__', '__new__', '__reduce__', '__reduce_ex__',
'__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', '__weakref__', 'bar']
  • getattr(object, name[, default])
    from object Get in object name Attribute corresponding to string
    The property obtained may come from The class or superclass to which the object belongs
    If No attribute specified ,getattr function Throw out AttributeError abnormal , Or return default The value of the parameter ( If this parameter is set )
  • hasattr(object, name), Call the function above , See if it returns the same
  • setattr(object, name, value), A new attribute may be created , perhaps Overwrite existing properties
  • vars([object]), return object Object's __dict__ attribute
    If the class to which the instance belongs defines __slots__ attribute , example No, __dict__ attribute , that vars function Can't handle That example

5.2 Special methods for handling properties

  • Use Order number Or built-in getattr、hasattr and setattr function Access properties Metropolis Trigger The corresponding... In the following list Special methods

  • however , direct By way of example __dict__ attribute Reading and writing attribute Not trigger These special methods , This is usually used Skip special methods

  • Special methods It won't be Instance property with the same name cover

Please bring the original link to reprint ,thank
Similar articles

2021-09-15

2021-09-15